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

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

Ответ

Duck typing (утиная типизация) — это стиль динамической типизации, при котором объект считается подходящим для использования в определённом контексте, если он реализует все необходимые методы и свойства (имеет нужное "поведение"), а не на основе его явного наследования от конкретного класса или реализации интерфейса. Фраза происходит от идеи: "Если что-то ходит как утка и крякает как утка, то это, вероятно, и есть утка".

В C# утиная типизация реализуется несколькими способами:

  1. Ключевое слово dynamic: Полностью отключает проверку типов на этапе компиляции.

    public void MakeSound(dynamic animal)
    {
        // Проверка наличия метода Quack() произойдет во время выполнения.
        animal.Quack(); // RuntimeBinderException, если метода нет
    }
    // Вызов с любым объектом, имеющим метод Quack(), сработает.
    MakeSound(new RealDuck());
    MakeSound(new ToyDuck());
  2. Явные интерфейсы (статическая альтернатива): Предпочтительный способ в C#, когда контракт известен заранее. Это не утиная типизация в чистом виде, но решает ту же задачу — определение по поведению.

    public interface ISoundMaker { void MakeSound(); }
    public class Duck : ISoundMaker { public void MakeSound() => Console.WriteLine("Quack!"); }
    public class Dog : ISoundMaker { public void MakeSound() => Console.WriteLine("Woof!"); }
    
    public void PerformSound(ISoundMaker maker) => maker.MakeSound();
  3. Reflection: Позволяет проверить наличие членов объекта программно.

    public bool CanQuack(object obj)
    {
        var method = obj.GetType().GetMethod("Quack");
        return method != null && method.ReturnType == typeof(void) && !method.GetParameters().Any();
    }

Плюсы:

  • Высокая гибкость и возможность работы с объектами, типы которых не связаны общим предком.

Минусы (для dynamic и Reflection):

  • Потеря безопасности типов на этапе компиляции.
  • Риск ошибок времени выполнения (RuntimeBinderException).
  • Снижение производительности.
  • Отсутствие поддержки IntelliSense в IDE.

Вывод: В C# для статического контракта используйте интерфейсы. К dynamic прибегайте только при необходимости, например, при работе с COM, динамическими языками или JSON-объектами с неизвестной структурой.