Avatar ·

Type Checking в C#: typeof, GetType() или is — что и когда использовать?

Всем привет. Я начинающий C# разработчик и постоянно путаюсь в проверках типов. Есть три способа: `typeof`, метод `GetType()` и оператор `is`. Например, у меня есть такой код: ```csharp object obj = "Hello"; // Вариант 1 if (obj.GetType() == typeof(string)) Console.WriteLine("Это строка через GetType"); // Вариант 2 if (obj is string) Console.WriteLine("Это строка через is"); // Вариант 3 Type t = typeof(string); ``` Вроде бы все три проверяют тип, но я замечаю странное поведение. Когда я использую `is` с nullable типами, он ведет себя не так, как `GetType()`. Например: ```csharp int? x = 5; if (x is int) // true Console.WriteLine("x is int"); if (x.GetType() == typeof(int)) // тоже true Console.WriteLine("x.GetType == typeof(int)"); ``` Но если x = null, то `x is int?` выдает false, а `GetType()` вообще выбросит NullReferenceException. И еще момент: я читал, что `typeof` работает только с типами во время компиляции, а `GetType()` — во время выполнения. Но когда это действительно важно? Объясните, пожалуйста, в чем разница между этими тремя подходами, какие у каждого плюсы и минусы, и в каких ситуациях что лучше использовать. Особенно интересует производительность и работа с наследованием (например, если у меня есть базовый класс и наследники). <<>>

Основные различия между typeof, GetType() и is

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

1. typeof — статическая проверка на этапе компиляции

typeof — это оператор, который возвращает объект Type для известного на этапе компиляции типа. Он не требует экземпляра объекта:

Type stringType = typeof(string); // Всегда System.String
Type intType = typeof(int);       // Всегда System.Int32

Важно: typeof не учитывает наследование. typeof(BaseClass) == typeof(DerivedClass) всегда false, даже если есть цепочка наследования.

2. GetType() — динамическая проверка во время выполнения

Метод GetType() вызывается на экземпляре объекта и возвращает его фактический тип во время выполнения:

object obj = "Hello";
Type runtimeType = obj.GetType(); // System.String

// Классический пример с наследованием:
class Animal { }
class Dog : Animal { }

Animal pet = new Dog();
Console.WriteLine(pet.GetType() == typeof(Animal)); // False! Фактический тип - Dog
Console.WriteLine(pet.GetType() == typeof(Dog));    // True

Критическое замечание: GetType() никогда не возвращает nullable тип. Если переменная имеет тип int? и содержит значение, GetType() вернет typeof(int). Если значение null — будет NullReferenceException.

3. is — проверка совместимости типов

Оператор is проверяет, можно ли преобразовать объект к указанному типу (учитывая наследование и интерфейсы):

object obj = "Hello";
bool isString = obj is string; // true
bool isObject = obj is object; // true (все типы наследуются от object)
bool isIComparable = obj is IComparable; // true (string реализует IComparable)

// С наследованием:
Animal pet = new Dog();
bool isAnimal = pet is Animal; // true
bool isDog = pet is Dog;       // true

is также корректно работает с nullable типами:

int? x = 5;
bool isInt = x is int;  // true (распаковка nullable)
bool isNull = x is null; // false

int? y = null;
bool isInt2 = y is int;  // false
bool isNull2 = y is null; // true

Когда что использовать?

  • typeof — когда нужно получить Type для известного типа (например, для передачи в рефлексию, создания экземпляров через Activator, сравнения с известным типом без наследования).
  • GetType() — когда нужно узнать точный тип объекта во время выполнения (например, для логирования, сериализации, проверки на конкретный класс без учета наследования).
  • is — в 90% случаев. Когда нужно проверить, является ли объект экземпляром определенного типа или его наследника. Особенно в связке с pattern matching: if (obj is string s) { Console.WriteLine(s.Length); }.

Производительность

is быстрее, чем GetType() == typeof(), потому что:

  • GetType() требует вызова виртуального метода и получения объекта Type.
  • is — это IL-инструкция isinst, которая выполняется очень быстро.
  • typeof — это константа времени компиляции, но сравнение типов через == все равно медленнее, чем is.

Итоговые рекомендации

  1. Используйте is для проверки совместимости типов (наследование, интерфейсы) — это самый читаемый и производительный вариант.
  2. Используйте typeof когда тип известен на этапе компиляции и вам нужен объект Type (рефлексия, атрибуты).
  3. Используйте GetType() когда нужно получить точный тип объекта (не учитывая наследование), например для сравнения с конкретным классом.
  4. Избегайте obj.GetType() == typeof(SomeType) если можно использовать is — это и быстрее, и безопаснее с nullable.
<<>>

Дополнение: Pattern Matching в современном C#

В C# 7.0+ появился мощный pattern matching, который делает is еще более удобным. Рекомендую использовать его вместо старых подходов:

// Старый способ
if (obj is string)
{
    string s = (string)obj;
    Console.WriteLine(s.Length);
}

// Новый способ (C# 7+)
if (obj is string s)
{
    Console.WriteLine(s.Length); // s уже объявлена и приведена
}

// Проверка на null
if (obj is null) { /* obj == null */ }

// Проверка с дополнительным условием (C# 9+)
if (obj is string { Length: > 5 } longString)
{
    Console.WriteLine($"Длинная строка: {longString}");
}

// Switch expression с типами
string result = obj switch
{
    int i => $"Целое: {i}",
    string str => $"Строка: {str}",
    null => "null",
    _ => "Другой тип"
};

Также стоит помнить, что typeof и GetType() могут давать разные результаты для generic-типов. Например, typeof(List<>) — это открытый generic, а new List<int>().GetType() — закрытый (List<int>). Для generic-типов лучше использовать is или проверку через GetGenericTypeDefinition().

И последнее: никогда не используйте GetType() на потенциально null-объектах без проверки — это гарантированный NullReferenceException.