Проверка типа: typeof, GetType или есть?

1095

Я видел, как многие люди используют следующий код:

Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Но я знаю, что вы тоже можете это сделать:

if (obj1.GetType() == typeof(int))
    // Some code here

Или это:

if (obj1 is int)
    // Some code here

Лично я чувствую, что последнее самое чистое, но есть ли что-то, что мне не хватает? Какой из них лучше всего использовать, или это личные предпочтения?

  • 23
    Не забудь as !
  • 77
    as не на самом деле тип проверки , хотя ...
Показать ещё 4 комментария
Теги:

14 ответов

1460
Лучший ответ

Все разные.

  • typeof принимает имя типа (которое вы указываете во время компиляции).
  • GetType получает тип экземпляра экземпляра.
  • is возвращает true, если экземпляр находится в дереве наследования.

Пример

class Animal { } 
class Dog : Animal { }

void PrintTypes(Animal a) { 
    print(a.GetType() == typeof(Animal)) // false 
    print(a is Animal)                   // true 
    print(a.GetType() == typeof(Dog))    // true
}

Dog spot = new Dog(); 
PrintTypes(spot);

Как насчет typeof(T)? Это также разрешено во время компиляции?

Да. T всегда является типом выражения. Помните, что общий метод - это в основном целая куча методов с соответствующим типом. Пример:

string Foo<T>(T object) { return typeof(T).Name; }

Animal probably_a_dog = new Dog();
Dog    definitely_a_dog = new Dog();

Foo(probably_a_dog); // this calls Foo<Animal> and returns "Animal"
Foo<Animal>(probably_a_dog); // this is exactly the same as above
Foo<Dog>(probably_a_dog); // !!! This will not compile. The parameter expects a Dog, you cannot pass in an Animal.

Foo(definitely_a_dog); // this calls Foo<Dog> and returns "Dog"
Foo<Dog>(definitely_a_dog); // this is exactly the same as above.
Foo<Animal>(definitely_a_dog); // this calls Foo<Animal> and returns "Animal". 
Foo((Animal)definitely_a_dog); // this does the same as above, returns "Animal"
  • 25
    Ах, так что, если у меня есть класс Ford, производный от Car, и экземпляр Ford, проверка «is Car» в этом случае будет правдой. Имеет смысл!
  • 1
    Чтобы уточнить, я знал об этом, но я прокомментировал, прежде чем вы добавили пример кода. Я хотел попытаться добавить ясность английского языка к вашему и без того превосходному ответу.
Показать ещё 7 комментариев
169

Используйте typeof, когда вы хотите получить тип во время компиляции. Используйте GetType, когда вы хотите получить тип во время выполнения. Редко случаются случаи использования is, так как он выполняет бросок, и в большинстве случаев вы в конечном итоге литье переменной.

Существует четвертый вариант, который вы не учли (особенно если вы собираетесь применить объект к тому типу, который вы найдете); то есть использовать as.

Foo foo = obj as Foo;

if (foo != null)
    // your code here

Это использует только один, тогда как этот подход:

if (obj is Foo)
    Foo foo = (Foo)obj;

требуется два.

  • 4
    С изменениями в .NET 4 делает is до сих пор выполняет бросок?
  • 6
    Этот ответ правильный? Правда ли, что вы действительно можете передать экземпляр в typeof ()? Мой опыт был Нет. Но я полагаю, что в общем случае проверка экземпляра может происходить во время выполнения, тогда как проверка класса должна выполняться во время компиляции.
Показать ещё 2 комментария
56

1.

Type t = typeof(obj1);
if (t == typeof(int))

Это незаконно, потому что typeof работает только с типами, а не с переменными. Я предполагаю, что obj1 является переменной. Таким образом, typeof статичен и выполняет свою работу во время компиляции, а не во время выполнения.

2.

if (obj1.GetType() == typeof(int))

Это верно, если obj1 - это точно тип int. Если obj1 выводится из int, условие if будет ложным.

3.

if (obj1 is int)

Это верно, если obj1 является int, или если он получен из класса с именем int, или если он реализует интерфейс с именем int.

  • 0
    Думая о 1, вы правы. И все же я видел это в нескольких примерах кода здесь. Это должно быть Type t = obj1.GetType ();
  • 4
    Да, я так думаю. "typeof (obj1)" не компилируется, когда я пытаюсь это сделать.
Показать ещё 3 комментария
39
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

Это ошибка. Оператор typeof в С# может принимать только имена типов, а не объекты.

if (obj1.GetType() == typeof(int))
    // Some code here

Это будет работать, но, возможно, не так, как вы ожидали. Для типов значений, как вы показали здесь, это приемлемо, но для ссылочных типов оно возвращает только true, если тип был тем же самым типом, а не что-то еще в иерархии наследования. Например:

class Animal{}
class Dog : Animal{}

static void Foo(){
    object o = new Dog();

    if(o.GetType() == typeof(Animal))
        Console.WriteLine("o is an animal");
    Console.WriteLine("o is something else");
}

Это напечатает "o is something else", потому что тип o равен Dog, а не Animal. Вы можете сделать эту работу, однако, если вы используете метод IsAssignableFrom класса Type.

if(typeof(Animal).IsAssignableFrom(o.GetType())) // note use of tested type
    Console.WriteLine("o is an animal");

Однако этот метод все еще оставляет серьезную проблему. Если ваша переменная равна нулю, вызов GetType() вызовет исключение NullReferenceException. Чтобы сделать работу правильно, выполните следующие действия:

if(o != null && typeof(Animal).IsAssignableFrom(o.GetType()))
    Console.WriteLine("o is an animal");

При этом у вас есть эквивалентное поведение ключевого слова is. Следовательно, если это поведение, которое вы хотите, вы должны использовать ключевое слово is, которое является более читаемым и более эффективным.

if(o is Animal)
    Console.WriteLine("o is an animal");

В большинстве случаев ключевое слово is все еще не то, что вы действительно хотите, потому что обычно этого недостаточно, чтобы знать, что объект имеет определенный тип. Обычно вы хотите использовать этот объект как экземпляр этого типа, который требует также его литья. И поэтому вы можете написать код такого типа:

if(o is Animal)
    ((Animal)o).Speak();

Но это делает CLR проверкой типа объекта до двух раз. Он проверит его один раз, чтобы удовлетворить оператор is, и если o действительно является Animal, мы заставляем его снова проверять подтверждение акта.

Более эффективно это сделать:

Animal a = o as Animal;
if(a != null)
    a.Speak();

Оператор as - это приведение, которое не генерирует исключение, если оно терпит неудачу, вместо этого возвращается null. Таким образом, CLR проверяет тип объекта только один раз, и после этого нам просто нужно выполнить нулевую проверку, что более эффективно.

Но будьте осторожны: многие люди попадают в ловушку с as. Поскольку это не вызывает исключений, некоторые люди считают его "безопасным" броском, и они используют его исключительно, избегая регулярных бросков. Это приводит к следующим ошибкам:

(o as Animal).Speak();

В этом случае разработчик явно предполагает, что o всегда будет Animal, и пока их предположение верно, все работает нормально. Но если они ошибаются, то, что в итоге получается, это NullReferenceException. При регулярном приведении они вместо этого получили бы InvalidCastException, что позволило бы более точно идентифицировать проблему.

Иногда эта ошибка может быть трудно найти:

class Foo{
    readonly Animal animal;

    public Foo(object o){
        animal = o as Animal;
    }

    public void Interact(){
        animal.Speak();
    }
}

Это еще один случай, когда разработчик явно ожидает, что o будет Animal каждый раз, но это не очевидно в конструкторе, в котором используется приведение as. Это не очевидно, пока вы не перейдете к методу Interact, где ожидается, что поле Animal будет положительно назначено. В этом случае вы не только получаете ошибочное исключение, но и не генерируете его до тех пор, пока оно не будет намного позже, чем когда произошла фактическая ошибка.

Вкратце:

  • Если вам нужно только знать, имеет ли объект какой-либо тип, используйте is.

  • Если вам нужно рассматривать объект как экземпляр определенного типа, но вы точно не знаете, что объект будет такого типа, используйте as и проверьте null.

  • Если вам нужно рассматривать объект как экземпляр определенного типа, и объект должен быть такого типа, используйте обычный листинг.

  • 0
    что не так с этим, если (o является Animal) ((Animal) o) .Speak (); ? Можете ли вы дать более подробную информацию?
  • 2
    @batmaci: он в ответе - он вызывает две проверки типов. Первый раз это o is Animal , который требует, чтобы CLR проверил, является ли тип переменной o Animal . Во второй раз он проверяет, когда он приводит оператор ((Animal)o).Speak() . Вместо того, чтобы проверять дважды, проверьте один раз, используя as .
11

Я имел Type -property для сравнения с и не мог использовать is (например, my_type is _BaseTypetoLookFor), но я мог бы использовать их:

base_type.IsInstanceOfType(derived_object);
base_type.IsAssignableFrom(derived_type);
derived_type.IsSubClassOf(base_type);

Обратите внимание, что IsInstanceOfType и IsAssignableFrom возвращают true при сравнении тех же типов, где IsSubClassOf вернет false. И IsSubclassOf не работает на интерфейсах, где работают два других. (См. Также этот вопрос и ответ.)

public class Animal {}
public interface ITrainable {}
public class Dog : Animal, ITrainable{}

Animal dog = new Dog();

typeof(Animal).IsInstanceOfType(dog);     // true
typeof(Dog).IsInstanceOfType(dog);        // true
typeof(ITrainable).IsInstanceOfType(dog); // true

typeof(Animal).IsAssignableFrom(dog.GetType());      // true
typeof(Dog).IsAssignableFrom(dog.GetType());         // true
typeof(ITrainable).IsAssignableFrom(dog.GetType()); // true

dog.GetType().IsSubclassOf(typeof(Animal));            // true
dog.GetType().IsSubclassOf(typeof(Dog));               // false
dog.GetType().IsSubclassOf(typeof(ITrainable)); // false
7

Я предпочитаю is

Тем не менее, если вы используете is, вы, вероятно, не используете наследование должным образом.

Предположим, что Person: Entity, и что Animal: Entity. Фид - это виртуальный метод в Entity (чтобы сделать Neil счастливым)

class Person
{
  // A Person should be able to Feed
  // another Entity, but they way he feeds
  // each is different
  public override void Feed( Entity e )
  {
    if( e is Person )
    {
      // feed me
    }
    else if( e is Animal )
    {
      // ruff
    }
  }
}

Вместо

class Person
{
  public override void Feed( Person p )
  {
    // feed the person
  }
  public override void Feed( Animal a )
  {
    // feed the animal
  }
}
  • 1
    Правда, я бы никогда не сделал первого, зная, что Персона происходит от Животного.
  • 3
    Последний также не использует наследование. Foo должен быть виртуальным методом Entity, который переопределяется в Person и Animal.
Показать ещё 4 комментария
5

Если вы используете С# 7, то настало время для обновления Andrew Hare отличный ответ. Соответствие шаблону ввело приятный ярлык, который дает нам типизированную переменную в контексте оператора if, не требуя отдельного объявления/литья и проверки:

if (obj1 is int integerValue)
{
    integerValue++;
}

Это выглядит довольно неудобно для одного актера, подобного этому, но действительно сияет, когда у вас есть много возможных типов, входящих в вашу рутину. Ниже приведен старый способ избежать двойного литья:

Button button = obj1 as Button;
if (button != null)
{
    // do stuff...
    return;
}
TextBox text = obj1 as TextBox;
if (text != null)
{
    // do stuff...
    return;
}
Label label = obj1 as Label;
if (label != null)
{
    // do stuff...
    return;
}
// ... and so on

Работа над сокращением этого кода как можно больше, а также избежание дублирования отбросов одного и того же объекта всегда беспокоили меня. Вышеупомянутое прекрасно сжато с учетом соответствия шаблону следующим образом:

if (obj1 is Button button)
{
    // do stuff...
}
else if (obj1 is TextBox text)
{
    // do stuff...
}
else if (obj1 is Label label)
{
    // do stuff...
}
// ... and so on
  • 1
    В этом случае рекомендуется использовать оператор switch с сопоставлением с образцом .
  • 0
    Как бы вы справились с этим? В этом конкретном блоке кода? if (obj1 is int integerValue) { integerValue++; }
Показать ещё 1 комментарий
5

Я считаю, что последний также рассматривает наследование (например, Dog is Animal == true), что лучше в большинстве случаев.

2

Это зависит от того, что я делаю. Если мне нужно значение bool (скажем, чтобы определить, будет ли я передан int), я буду использовать is. Если мне действительно нужен тип по какой-либо причине (скажем, чтобы перейти к другому методу), я буду использовать GetType().

  • 1
    Хорошая точка зрения. Я забыл упомянуть, что добрался до этого вопроса после просмотра нескольких ответов, в которых для проверки типа использовался оператор if.
1

Используется для получения объекта System.Type для типа. Выражение typeof принимает следующий вид:

System.Type type = typeof(int);

Example:

    public class ExampleClass
    {
       public int sampleMember;
       public void SampleMethod() {}

       static void Main()
       {
          Type t = typeof(ExampleClass);
          // Alternatively, you could use
          // ExampleClass obj = new ExampleClass();
          // Type t = obj.GetType();

          Console.WriteLine("Methods:");
          System.Reflection.MethodInfo[] methodInfo = t.GetMethods();

          foreach (System.Reflection.MethodInfo mInfo in methodInfo)
             Console.WriteLine(mInfo.ToString());

          Console.WriteLine("Members:");
          System.Reflection.MemberInfo[] memberInfo = t.GetMembers();

          foreach (System.Reflection.MemberInfo mInfo in memberInfo)
             Console.WriteLine(mInfo.ToString());
       }
    }
    /*
     Output:
        Methods:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Members:
        Void SampleMethod()
        System.String ToString()
        Boolean Equals(System.Object)
        Int32 GetHashCode()
        System.Type GetType()
        Void .ctor()
        Int32 sampleMember
    */

В этом примере используется метод GetType для определения типа, который используется для определения результата численного вычисления. Это зависит от требований к хранилищу результирующего числа.

    class GetTypeTest
    {
        static void Main()
        {
            int radius = 3;
            Console.WriteLine("Area = {0}", radius * radius * Math.PI);
            Console.WriteLine("The type is {0}",
                              (radius * radius * Math.PI).GetType()
            );
        }
    }
    /*
    Output:
    Area = 28.2743338823081
    The type is System.Double
    */
0

Последний является более чистым, более очевидным, а также проверяет подтипы. Остальные не проверяют наличие полиморфизма.

-2

Вы можете использовать оператор typeof() в С#, но вам нужно вызвать пространство имен с помощью System.IO; Вы должны использовать ключевое слово "есть", если вы хотите проверить тип.

  • 6
    typeof не определен в пространстве имен, это ключевое слово. System.IO имеет к этому никакого отношения.
-3
if (c is UserControl) c.Enabled = enable;
  • 4
    Пожалуйста, отредактируйте с дополнительной информацией. Ответы «только код» и «попробуй это» не приветствуются, поскольку они не содержат контента для поиска и не объясняют, почему кто-то должен «попробовать это».
-3

Тестирование производительности typeof() vs GetType():

using System;
namespace ConsoleApplication1
    {
    class Program
    {
        enum TestEnum { E1, E2, E3 }
        static void Main(string[] args)
        {
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test1(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            {
                var start = DateTime.UtcNow;
                for (var i = 0; i < 1000000000; i++)
                    Test2(TestEnum.E2);
                Console.WriteLine(DateTime.UtcNow - start);
            }
            Console.ReadLine();
        }
        static Type Test1<T>(T value) => typeof(T);
        static Type Test2(object value) => value.GetType();
    }
}

Результаты в режиме отладки:

00:00:08.4096636
00:00:10.8570657

Результаты в режиме выпуска:

00:00:02.3799048
00:00:07.1797128
  • 1
    Не следует использовать DateTime.UtcNow для оценки производительности. С вашим кодом, но с классом Stopwatch я получил постоянно противоположные результаты для режима отладки. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. Режим релиза такой же, как и ожидалось
  • 0
    @AlexeyShcherbak Разница между секундомером и DateTime.Now не может быть более 10-20 мс, проверьте код еще раз. И мне плевать на миллисекунды в моем тесте. Также мой код будет на несколько строк кода длиннее с секундомером.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню