Перевірка типу: typeof, GetType чи є?


1512

Я бачив, як багато людей використовують такий код:

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

Особисто я вважаю, що останній є найчистішим, але чи є щось, чого мені не вистачає? Який із них найкраще використовувати, чи це особисті переваги?


27
Не забувайте as!
RCIX

82
asнасправді не перевіряємо тип, хоча ...
Jasonh

49
asце, безумовно, форма перевірки типу, кожен біт стільки, скільки isє! Він ефективно використовується isза кадром, і використовується в усьому світі в MSDN в місцях, де він покращує чистоту коду порівняно з is. Замість того, щоб перевірити isспочатку, виклик asвстановлює введену змінну, яка готова до використання: Якщо вона є нульовою, відповідь належним чином; в іншому випадку продовжуйте. Звичайно щось, що я бачив і використовував зовсім небагато.
Zaccone

15
Існує значна різниця в ефективності на користь as/ is(висвітлено в stackoverflow.com/a/27813381/477420 ), припускаючи, що його семантичний твір працює для вашої справи.
Олексій Левенков

@samusarin він не "використовує" рефлексію. GetTypeМетод ви посилаєтеся в System.Reflection.Assembly- зовсім іншим способом і не має значення тут.
Кірк Волл

Відповіді:


1847

Всі різні.

  • 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)? Чи це також вирішено під час компіляції?

Так. Т завжди є типом виразу. Пам'ятайте, що загальний метод - це, як правило, ціла купа методів відповідного типу. Приклад:

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, який походить від автомобіля, і примірник Ford, перевірка "is Car" на цьому екземплярі буде правдою. Має сенс!
Jasonh

2
Для уточнення я це знав, але я прокоментував, перш ніж ви додали зразок коду. Я хотів спробувати додати трохи зрозумілої англійської мови до вашої вже чудової відповіді.
Jasonh

12
@Shimmy, якщо typeof оцінюється під час компіляції, а GetType () оцінюється під час виконання, то має сенс, що GetType () зазнає невеликого хіта на продуктивність
Cedric Mamo

як щодо нової собаки (). GetType () - це Animal Or typeof (Собака) - це Animal, вона просто попереджає, а не помилку?
Прерак К

7
@PrerakK new Dog().GetType() is Animalповертає false (і вашу іншу версію), оскільки .GetType()повертає об'єкт типу Type, а Typeне an 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+ , тепер ви можете додавати вбудований текст, тому підхід "є" тепер може бути виконаний і в одному складі.

Приклад:

if(obj is Foo newLocalFoo)
{
    // For example, you can now reference 'newLocalFoo' in this local scope
    Console.WriteLine(newLocalFoo);
}

4
Зі змінами в .NET 4 isвсе ще виконується акторський склад ?
ahsteele

6
Чи правильна ця відповідь? Це правда, що ви дійсно можете передавати екземпляр у typeof ()? Мій досвід був ні. Але я думаю, що це правда, що перевірка екземпляра може статися під час виконання, тоді як перевірка класу повинна бути виконана під час компіляції.
Джон Кумбс

4
@jon (через 4 роки після вашого запитання), ні, ви не можете передати екземпляр 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, умова, якщо буде false.

3.

if (obj1 is int)

Це trueякщо obj1є int, або якщо воно походить від класу, який називається int, або якщо він реалізує інтерфейс, званий int.


Подумавши про 1, ти маєш рацію. І все ж я бачив це в кількох зразках коду тут. Він повинен бути Type t = obj1.GetType ();
jasonh

4
Так, я так думаю. "typeof (obj1)" не компілюється, коли я його намагаюся.
Скотт Ленгем

4
Неможливо вивести з System.Int32 або будь-якого іншого типу значення в C #
reggaeguitar

Ви можете сказати, що було б typeof (typeof (system.int32))
Сана,

1
@Sana, чому б не спробувати :) Я б уявив, що ти повернеш екземпляр 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

Це спрацює, але, можливо, не так, як ви очікували. Для типів значень, як ви показали тут, це прийнятно, але для посилальних типів воно поверне справжнє лише, якщо тип був би точно таким же типом, а не чимось іншим в ієрархії спадкування. Наприклад:

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.

  • Якщо вам потрібно розглянути об'єкт як екземпляр певного типу, а об'єкт повинен бути такого типу, використовуйте звичайний склад.


що з цим погано, якщо (o - тварина) ((тварина) o). говорити (); ? Ви можете, будь ласка, надати більше деталей?
batmaci

2
@batmaci: це відповідь - це викликає перевірку двох типів. Перший раз - o is Animalце вимагає, щоб CLR перевірив, чи є тип змінної oan Animal. Вдруге він перевіряє, коли це робить виписка ((Animal)o).Speak(). Замість перевірки двічі, перевірте один раз, використовуючи as.
siride

Я знайшов це абсолютно чудовим поясненням, дякую за уточнення!
Пол Еффорд

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...
}

EDIT: Оновлено довший новий метод використання перемикача відповідно до коментаря Палека.


1
У цьому випадку доцільно використовувати switchоператор із узгодженням шаблону .
Палець

Як би ти впорався з не так? У цьому конкретному кодовому блоці? if (obj1 is int integerValue) { integerValue++; }
Бен Вертонген

Бен, якщо я розумію ваше запитання, у мене буде просто інше твердження для обробки інших випадків, оскільки ви не можете поставити не ціле число в цілу змінну. :)
JoelC

14

Я мав Type-властивість для порівняння і не міг використовувати 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

9

Я вважаю за краще це

Однак, якщо ви використовуєте це , ви, ймовірно, не використовуєте спадщину належним чином.

Припустимо, що Людина: Сутність, а тварина: Сутність. 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
  }
}

1
Щоправда, я б ніколи не робив колишнього, знаючи, що Особа походить від Тварин.
jasonh

3
Останній теж не використовує спадщину. Foo повинен бути віртуальним методом Сутності, який переосмислюється в Особистість і Тварина.
Ніл Вільямс

2
@bobobobo Я думаю, ви маєте на увазі "перевантаження", а не "успадкування".
лк.

@ lc: Ні, я маю на увазі спадщину. Перший приклад є свого роду неправильним шляхом ( з допомогою IS ) , щоб отримати різну поведінку. Другий приклад використовує перевантаження так, але уникає використання є .
bobobobo

1
Проблема з прикладом полягає в тому, що він не буде масштабуватися. Якщо ви додали нові сутності, які потрібно їсти (наприклад, комаха чи монстр), вам потрібно буде додати новий метод у класі Entity, а потім замінити його в підкласах, які б його годували. Це не є більш кращим, ніж список, якщо (сутність є X) інше, якщо (сутність є Y) ... Це порушує LSP і OCP, успадкування, ймовірно, не є найкращим рішенням проблеми. Певна форма делегації, ймовірно, буде кращою.
ebrown

5

Я вважаю, що останній також дивиться на спадщину (наприклад, собака - тварина == правда), що в більшості випадків краще.


2

Це залежить від того, що я роблю. Якщо мені потрібно значення bool (скажімо, щоб визначити, чи я буду передавати до int), я буду використовувати is. Якщо мені дійсно потрібен тип з якихось причин (скажімо, перейти до якогось іншого методу), я буду використовувати GetType().


1
Гарна думка. Я забув згадати, що до цього питання я потрапив, переглянувши кілька відповідей, які використовували оператор if для перевірки типу.
Jasonh

0

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


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;

4
Відредагуйте додаткові відомості. Відповіді, що містять лише коди, та "спробувати це" не рекомендують, оскільки вони не містять вмісту, який можна шукати, і не пояснюють, чому хтось повинен "спробувати це".
аборисон

Ваша відповідь не пов'язана з питанням.
menxin

-5

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


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 для заходів щодо ефективності. З вашим кодом, але із класом секундомір, я отримав стійкі протилежні результати для режиму налагодження. UseTypeOf: 00: 00: 14.5074469 UseGetType: 00: 00: 10.5799534. Режим випуску такий самий, як і очікувалося
Олексій Щербак

@AlexeyShcherbak Різниця між секундоміром та DateTime.Зараз не може бути більше 10-20 мс, ще раз перевірте свій код. І я не переймаюся мілісекундами в своєму тесті. Також мій код буде на кілька рядків коду довше за допомогою секундоміра.
Олександр Васильєв

1
це погана практика взагалі, а не у вашому конкретному випадку.
Олексій Щербак

4
@AlexanderVasilyev Кількість рядків коду ніколи не повинна використовуватися як аргумент, щоб зробити щось документально введене в оману. Як видно з msdn.microsoft.com/en-us/library/system.datetime(v=vs.110).aspx , DateTimeне слід використовувати, якщо ви стурбовані часом менше 100 мс , оскільки він використовує часовий кадр ОС. Порівняно з тим Stopwatch, що використовує процесори Tick, роздільна здатність, яка використовується DateTimeв Win7, становить колосальні 15 мс.
Ерік Ву
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.