Что такое утиная типизация (duck typing)?

«Что такое утиная типизация (duck typing)?» — вопрос из категории ООП, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Утиная типизация — это концепция динамической типизации, при которой возможность использования объекта определяется не его явным типом (классом или интерфейсом), а наличием у него требуемых методов и свойств. Если объект имеет метод Quack(), его можно использовать как утку, даже если он не реализует интерфейс IDuck.

Контраст с номинативной типизацией (C#/Java): В строго типизированных языках совместимость типов проверяется на этапе компиляции по именам типов или интерфейсов.

Эмуляция в C# (где нет прямой поддержки):

  1. Использование dynamic (простейший способ, C# 4.0+):

    public void MakeDuckSound(dynamic duck)
    {
        // Проверка произойдёт во время выполнения (RuntimeBinderException при отсутствии метода)
        duck.Quack();
    }
    
    var realDuck = new RealDuck(); // Имеет метод Quack()
    var fakeDuck = new { Quack = new Action(() => Console.WriteLine("Quack!")) }; // Анонимный тип с методом
    var notADuck = new object();
    
    MakeDuckSound(realDuck); // OK
    MakeDuckSound(fakeDuck); // OK
    MakeDuckSound(notADuck); // RuntimeBinderException!
  2. Использование рефлексии (более гибко, но медленнее):

    public bool TryMakeDuckSound(object potentialDuck)
    {
        var quackMethod = potentialDuck.GetType().GetMethod("Quack", Type.EmptyTypes);
        if (quackMethod != null)
        {
            quackMethod.Invoke(potentialDuck, null);
            return true;
        }
        return false;
    }
  3. Использование сопоставления шаблонов (C# 8.0+, более безопасно):

    public void MakeDuckSound(object potentialDuck)
    {
        if (potentialDuck is IQuackable duck) // Проверка на интерфейс
        {
            duck.Quack();
        }
    }
    // Требует явного определения интерфейса
    public interface IQuackable { void Quack(); }

Плюсы и минусы:

  • Гибкость: Позволяет работать с объектами, тип которых неизвестен на этапе компиляции.
  • Опасность: Потеря безопасности типов, ошибки обнаруживаются только во время выполнения.
  • Производительность: Использование dynamic и рефлексии медленнее прямых вызовов.