Проверка типа: typeof, GetType или есть? - PullRequest
1370 голосов
/ 11 июня 2009

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

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

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

Ответы [ 14 ]

1684 голосов
/ 11 июня 2009

Все разные.

  • 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 всегда является типом выражения. Помните, универсальный метод - это в основном целая куча методов с соответствующим типом. Пример: * 1 023 *

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"
179 голосов
/ 11 июня 2009

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

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

Foo foo = obj as Foo;

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

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

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

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

67 голосов
/ 11 июня 2009

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.

46 голосов
/ 11 июня 2009
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.

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

13 голосов
/ 15 мая 2012

У меня было свойство 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
11 голосов
/ 01 февраля 2018

Если вы используете 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...
}

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

8 голосов
/ 11 июня 2009

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

Тем не менее, если вы используете - это , скорее всего не при правильном наследовании.

Предположим, что Персона: Сущность, а это Животное: Сущность. 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
  }
}
5 голосов
/ 11 июня 2009

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

2 голосов
/ 11 июня 2009

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

0 голосов
/ 12 августа 2016
if (c is UserControl) c.Enabled = enable;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...