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

1513

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

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

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

jasonh
источник
28
Не забывайте as!
RCIX
82
asхотя на самом деле это не проверка типов ...
Джейсон
49
asэто, безусловно, форма проверки типов, каждая isтакая же, как есть! Он эффективно используется isза кулисами и повсеместно используется в MSDN там, где он улучшает чистоту кода по сравнению с ним is. Вместо того, чтобы проверять isсначала, вызов asустанавливает типизированную переменную, которая готова к использованию: если она пуста, ответьте соответственно; в противном случае продолжайте. Конечно то, что я видел и использовал совсем немного.
Закконе
15
Существует значительная разница в производительности в пользу as/ is(описана в stackoverflow.com/a/27813381/477420 ), если предположить, что его семантические работы подходят для вашего случая.
Алексей Левенков
@ samusarin это не «использует» отражение. GetTypeМетод вы ссылаетесь в System.Reflection.Assembly- совершенно другим способом и не имеет значения здесь.
Кирк Волл,

Ответы:

1850

Все разные.

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

пример

class Animal { } 
class Dog : Animal { }

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

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

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

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

string Foo<T>(T parameter) { 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"
Джимми
источник
29
Ах, так что, если у меня есть класс Ford, производный от Car, и экземпляр Ford, проверка «is Car» в этом случае будет верной. Имеет смысл!
Джейсон
2
Чтобы уточнить, я знал об этом, но я прокомментировал, прежде чем вы добавили пример кода. Я хотел бы попытаться добавить ясность английского в ваш и без того отличный ответ.
Джейсон
12
@Shimmy, если typeof вычисляется во время компиляции, а GetType () оценивается во время выполнения, то имеет смысл, что GetType () подвергается небольшому снижению производительности
Седрик Мамо
как насчет нового Dog (). GetType () это Animal Или typeof (Dog) это Animal, он просто выдает предупреждение, а не ошибку?
Прерак К
7
@PrerakK new Dog().GetType() is Animalвозвращает false (и вашу другую версию), поскольку .GetType()возвращает объект типа Type, а Typeне является Animal.
Мартен
195

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

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

Foo foo = obj as Foo;

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

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

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

требует два .

Обновление (январь 2020 г.):

  • Начиная с C # 7+ , теперь вы можете использовать inline, поэтому подход «is» теперь может быть реализован и в одном.

Пример:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}
Эндрю Хэйр
источник
4
С изменениями в .NET 4 все isеще выполняет приведение?
ахстил
6
Этот ответ правильный? Правда ли, что вы действительно можете передать экземпляр в typeof ()? Мой опыт был Нет. Но я думаю, что в общем случае верно, что проверка экземпляра может происходить во время выполнения, тогда как проверка класса должна быть выполнимой во время компиляции.
Джон Кумбс
4
@jon (через 4 года после вашего q.), нет, вы не можете передать экземпляр typeof(), и этот ответ не предполагает, что вы можете. Вместо этого вы передаете тип, т. Е. typeof(string)Работает, typeof("foo")а не.
Авель
Я не верю, что isвыполняет актерский состав как таковую, скорее специальную операцию в IL.
Абатищев
3
Теперь мы можем сделатьif (obj is Foo foo) { /* use foo here */ }
Иван Гарсия
71

1.

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

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

2.

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

Это trueесли obj1точно типа int. Если obj1происходит от int, условие if будет false.

3.

if (obj1 is int)

Это, trueесли obj1is int, или если оно происходит от вызываемого класса int, или если он реализует вызываемый интерфейс int.

Скотт Лэнгхэм
источник
Думая о 1, вы правы. И все же я видел это в нескольких примерах кода здесь. Это должно быть Type t = obj1.GetType ();
Джейсон
4
Да, я так думаю. "typeof (obj1)" не компилируется, когда я пытаюсь это сделать.
Скотт Лэнгхэм
4
Невозможно получить из System.Int32 или любого другого типа значения в C #
reggaeguitar
Можете ли вы сказать, что было бы typeof (typeof (system.int32))
Сана
1
@ Сана, почему бы тебе не попробовать :) Я думаю, ты получишь экземпляр System.Type, который представляет тип System.Type! Документация для typeof находится здесь: docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
Скотт Лэнгхем,
53
Type t = typeof(obj1);
if (t == typeof(int))
    // Some code here

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

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.

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

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

Если вы используете C # 7, то пришло время обновить великолепный ответ Эндрю Хэра. Сопоставление с образцом ввело хороший ярлык, который дает нам типизированную переменную в контексте оператора 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

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

switch (obj1)
{
    case Button button:
        // do stuff...
        break;
    case TextBox text:
        // do stuff...
        break;
    case Label label:
        // do stuff...
        break;
    // and so on...
}

РЕДАКТИРОВАТЬ: Обновлен более длинный новый метод, чтобы использовать переключатель в соответствии с комментарием Палека.

JoelC
источник
1
В этом случае рекомендуется использовать switchоператор с сопоставлением с образцом .
Палек
Как бы вы справились с собой? В этом конкретном блоке кода? if (obj1 is int integerValue) { integerValue++; }
Бен Вертонген
Бен, если я пойму твой вопрос, у меня будет просто инструкция else для обработки других случаев, так как ты не можешь поместить нецелое число в целочисленную переменную. :)
JoelC
14

Я имел 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
Серьезный Yahoo
источник
9

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

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

Предположим, что Персона: Сущность, а это Животное: Сущность. Feed - это виртуальный метод в Entity (чтобы сделать Нила счастливым)

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
  }
}
bobobobo
источник
1
Правда, я бы никогда не сделал первого, зная, что Человек происходит от Животного.
Джейсон
3
Последний тоже не использует наследование. Foo должен быть виртуальным методом Entity, который переопределяется в Person и Animal.
Нил Уильямс
2
@ Bobobobo Я думаю, что вы имеете в виду «перегрузка», а не «наследование».
жк.
@lc: Нет, я имею в виду наследование. Первый пример - это своего рода неправильный способ (использование is ) для получения другого поведения. Второй пример использует перегрузку yes, но избегает использования is .
Бобобобо
1
Проблема с примером в том, что он не будет масштабироваться. Если вы добавили новые сущности, которые нужно было съесть (например, насекомое или монстр), вам нужно было бы добавить новый метод в классе сущностей, а затем переопределить его в подклассах, которые будут его кормить. Это не более предпочтительно, чем список, если (сущность - это X), иначе (сущность - это Y) ... Это нарушает LSP и OCP, наследование, вероятно, не лучшее решение проблемы. Некоторая форма делегирования, вероятно, предпочтительнее.
ebrown
5

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

StriplingWarrior
источник
2

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

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

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

thecoop
источник
0

Используется для получения объекта 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
    */
Мухаммед Авайс
источник
-4
if (c is UserControl) c.Enabled = enable;
Paulos02
источник
4
Пожалуйста, отредактируйте с дополнительной информацией. Ответы «только код» и «попробуй это» не приветствуются, потому что они не содержат контента для поиска и не объясняют, почему кто-то должен «попробовать это».
августа
Ваш ответ не имеет отношения к вопросу.
Menxin
-5

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

androidrill
источник
7
typeofне определен в пространстве имен, это ключевое слово. System.IOне имеет к этому никакого отношения.
Артуро Торрес Санчес
-5

Тест производительности typeof () против 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. Режим релиза такой же, как и ожидалось
Алексей Щербак
@AlexeyShcherbak Разница между секундомером и DateTime.Now не может быть более 10-20 мс, проверьте ваш код еще раз. И мне плевать на миллисекунды в моем тесте. Также мой код будет на несколько строк длиннее с секундомером.
Александр Васильев
1
Это плохая практика в целом, а не в вашем конкретном случае.
Алексей Щербак
4
@AlexanderVasilyev Количество строк кода никогда не должно использоваться в качестве аргумента, чтобы сделать что-то документально вводящее в заблуждение. Как видно из msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimeне следует использовать, если вы беспокоитесь о временах ниже 100 мс , так как он использует временные рамки ОС. По сравнению с тем Stopwatch, в котором используются процессоры Tick, разрешение, используемое DateTimeв Win7, составляет колоссальные 15 мс.
Eric Wu