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

Ответ

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-объектами с неизвестной структурой.

Ответ 18+ 🔞

А, утиная типизация! Ну это ж классика, блядь. Сейчас объясню на пальцах, чтобы даже мартышке было понятно.

Представь, у тебя есть функция, которая просит: «Дай мне штуку, которая крякает». И тебе похуй, что это за объект — живая утка, резиновая игрушка или сосед Вася после пятой рюмки. Главное, чтобы при вызове .Quack() эта хрень издала нужный звук. Если ходит как утка и крякает как утка — пофиг на её паспорт и родословную. Вот и вся философия, в рот меня чих-пых!

В C# это можно провернуть несколькими способами, конечно. Но смотри, тут есть подводные грабли, о которые можно ебальник разбить.

Первый способ — через dynamic. Это как дать обезьяне гранату: мощно, но непредсказуемо.

public void ЗаставитьКрякнуть(dynamic птица)
{
    // Компилятор тут просто отдыхает. Он поверит на слово.
    птица.Quack(); // А вот тут, во время работы, может быть пиздец, если метода нет.
}

// Вызываешь с чем угодно
ЗаставитьКрякнуть(new НастоящаяУтка()); // Крякнет — красота.
ЗаставитьКрякнуть(new ИгрушечнаяУтка()); // И это сработает, если метод есть.
ЗаставитьКрякнуть(new Кот()); // RuntimeBinderException, блядь! Кот не крякает, мудак!

Плюс — гибкость овердохуища. Минус — безопасность типов на этапе компиляции летит в пизду. IDE тебе тоже не подскажет нихуя, будешь тыкать пальцем в небо.

Второй способ — интерфейсы. Это как раз умный, взрослый подход. Не совсем чистая «утиность», но задача решается.

public interface IЗвук { void ИздатьЗвук(); }

public class Утка : IЗвук { public void ИздатьЗвук() => Console.WriteLine("Кря-кря!"); }
public class Собака : IЗвук { public void ИздатьЗвук() => Console.WriteLine("Гав-гав!"); }

public void Концерт(IЗвук артист) => артист.ИздатьЗвук();

Всё чётко, компилятор проверяет, IntelliSense работает. Но нужно, чтобы классы заранее договорились реализовывать этот интерфейс.

Третий способ — рефлексия. Это уже для мазохистов и ситуаций, когда ты вообще нихуя не знаешь об объекте.

public bool УмеетКрякать(object подозрительныйОбъект)
{
    var метод = подозрительныйОбъект.GetType().GetMethod("Quack");
    // Проверяем, есть ли метод, без параметров и возвращает void
    return метод != null && метод.ReturnType == typeof(void) && !method.GetParameters().Any();
}

Медленно, муторно, но иногда без этого никуда.

Итог, чувак: если знаешь контракт заранее — пили интерфейсы, не мудри. Если работаешь с какой-то внешней, ебучей динамической хуйней (типа JSON из интернета или скриптами) — тогда dynamic или рефлексия. Но помни: как только отключаешь проверку типов, ты идешь по охуенно тонкому льду. Один неверный шаг — и RuntimeBinderException тебя ебнет прямо в сраку.