Avatar ·

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

Всем привет! Я начинающий C# разработчик и никак не могу разобраться с проверкой типов в C#. Есть три способа: оператор `typeof`, метод `GetType()` и оператор `is`. Но когда какой применять? Вот пример, который меня смущает: ```csharp object obj = "Hello, world!"; // Способ 1 if (obj.GetType() == typeof(string)) Console.WriteLine("Это строка (GetType)"); // Способ 2 if (obj is string) Console.WriteLine("Это строка (is)"); // Способ 3 Type t = typeof(string); if (obj.GetType() == t) Console.WriteLine("Это строка (typeof)"); ``` Все три варианта работают, но я чувствую, что это не взаимозаменяемые вещи. Например, я слышал, что `typeof` работает только с типами на этапе компиляции, а `GetType()` — во время выполнения. А `is` вообще какой-то магический — он вроде и проверяет, и приводит тип одновременно. Подскажите, пожалуйста: 1. В чём принципиальная разница между `typeof`, `GetType()` и `is`? 2. Когда какой использовать, чтобы код был правильным и эффективным? 3. Есть ли подводные камни? Заранее спасибо! <<>>

Разбираем три способа проверки типов в C#

Отличный вопрос! Действительно, на первый взгляд эти три конструкции кажутся похожими, но у них совершенно разное назначение. Давайте разберём каждую подробно.

1. Оператор typeof

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

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

// НЕЛЬЗЯ: typeof(obj) — синтаксическая ошибка!

Главное правило: typeof используется, когда вы точно знаете тип на этапе написания кода и вам нужно получить его метаданные (например, для рефлексии или сравнения).

2. Метод GetType()

GetType() — это метод, определённый в базовом классе System.Object. Он возвращает реальный тип объекта во время выполнения. Это критически важно для полиморфизма.

object obj = "Hello";
Console.WriteLine(obj.GetType()); // Выведет: System.String

object num = 42;
Console.WriteLine(num.GetType()); // Выведет: System.Int32

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

Animal pet = new Dog();
Console.WriteLine(pet.GetType()); // Выведет: Dog, а не Animal!

GetType() всегда показывает фактический тип объекта, а не тип переменной, через которую вы к нему обращаетесь.

3. Оператор is

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

object obj = "Hello";

if (obj is string)      // true — строка совместима с string
    Console.WriteLine("Это строка");

if (obj is object)      // true — любой тип совместим с object
    Console.WriteLine("Это object");

if (obj is int)         // false — строку нельзя привести к int
    Console.WriteLine("Это int");

// Современный синтаксис (C# 7+): pattern matching
if (obj is string text)
    Console.WriteLine($"Длина строки: {text.Length}");

Обратите внимание: is не просто проверяет, но и может сразу присвоить значение в новую переменную (pattern matching).

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

  • typeof — когда вам нужен Type для известного типа на этапе компиляции. Пример: передача в методы рефлексии, создание экземпляров через Activator.CreateInstance, сравнение с результатом GetType().
  • GetType() — когда нужно узнать точный тип объекта во время выполнения. Пример: логирование, сериализация, проверка, является ли объект экземпляром конкретного класса (не производного).
  • is — когда нужно проверить совместимость типов (включая наследование и интерфейсы) и/или выполнить безопасное приведение. Это самый частый вариант в обычном коде.

Подводные камни и важные нюансы

  • Разница между is и GetType() при наследовании:
    class Base { }
    class Derived : Base { }
    
    object obj = new Derived();
    
    Console.WriteLine(obj is Base);       // true — Derived совместим с Base
    Console.WriteLine(obj.GetType() == typeof(Base)); // false — реальный тип Derived
  • Value types и boxing:
    int number = 42;
    object boxed = number;
    
    Console.WriteLine(boxed.GetType() == typeof(int));  // true
    Console.WriteLine(boxed is int);                    // true (распаковка)
    Console.WriteLine(typeof(int) == boxed.GetType());  // true
  • Производительность:
    • is — самый быстрый, часто оптимизируется JIT.
    • GetType() + == — медленнее из-за виртуального вызова.
    • typeof — константа времени компиляции, но сравнение с GetType() всё равно включает вызов метода.

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

В 90% случаев вам нужен оператор is — он интуитивно понятен, безопасен и производителен. GetType() используйте только когда вам действительно нужен точный тип (например, для хеширования или сравнения с запретом наследования). typeof — для рефлексии и статических проверок.

<<>>

Дополнение: когда использовать GetType() вместо is

Коллега выше отлично описал основы. Хочу добавить важный практический пример, где GetType() незаменим, а is может привести к ошибке.

Рассмотрим сценарий с переопределением Equals():

public class Person
{
    public string Name { get; set; }
    
    public override bool Equals(object obj)
    {
        // НЕПРАВИЛЬНО: если класс унаследуют, то объект наследника
        // пройдёт проверку is, но это разные типы!
        if (obj is Person other)
            return Name == other.Name;
        return false;
    }
}

public class Employee : Person { }

// Теперь:
Person p = new Person { Name = "John" };
Employee e = new Employee { Name = "John" };
Console.WriteLine(p.Equals(e)); // true — хотя это разные типы!

Если логика требует строгого совпадения типов (а не совместимости), нужно использовать GetType():

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;
    Person other = (Person)obj;
    return Name == other.Name;
}

Также GetType() полезен при построении словарей-мультиметодов или в системах плагинов, где нужно точно идентифицировать тип объекта. Запомните простое правило: is — для совместимости, GetType() — для точной идентификации.