TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3348 248
День 2798. #Оффтоп
Утиная Типизация в C# с Помощью Перехватчиков. Часть 2
Некоторое время назад я выложил пост от Steven Giesel о реализации утиной типизации в C# с помощью перехватчиков (interceptors). Тогда это был лишь прототип с множеством недоработок. Теперь же это полноценная библиотека: IfItQuacks.

TL;DR
Объявите интерфейс, пометьте метод атрибутом [DuckTyped] и передавайте в него любой подходящий объект — без базового класса, без явной реализации интерфейса и без каких-либо атрибутов на самих типах:
using IfItQuacks;

public interface INamed
{
string Name { get; }
}

public class Person { public string Name => "Steven"; }
public class Mallard { public string Name => "Donald"; }

public partial class Greeter
{
[DuckTyped]
public string Greet(
INamed first,
INamed second,
string greeting = "Hello")
=> $"{greeting}, {first.Name} and {second.Name}!";
}

Console.WriteLine(new Greeter()
.Greet(new Person(), new Mallard()));
// Hello, Steven and Donald!

Ни Person, ни Mallard не знают об INamed. Генератор кода на этапе компиляции проверяет наличие у обоих типов свойства Name и перенаправляет вызов через небольшой сгенерированный адаптер. Никакой рефлексии, никакого dynamic. Если типы несовместимы, анализатор выдаст ошибку.

Можно также явно привести объекты к интерфейсу, чтобы хранить их:
List<INamed> names = [
Duck.As<INamed>(new Person()),
Duck.As<INamed>(new Mallard())
];

Подробности можно найти в документации. Далее кратко основные моменты.

Анонимные типы — это «утки»
Анонимные типы отлично «крякают» (по сути, то же самое, что предлагает TypeScript):
new Greeter().Greet(
new { Name = "Steven" },
new { Name = "Donald" });


По сути, можно писать свои тесты с «мгновенными моками»:
public interface IPerson
{
string Name { get; }
int Age { get; }
}

public partial class AgeChecker
{
[DuckTyped]
public bool IsAdult(IPerson person)
=> person.Age >= 18;
}

[Test]
public void Should_detect_adults()
{
var checker = new AgeChecker();

Assert.That(
checker.IsAdult(new { Name = "Steven", Age = 90 }), Is.True);
Assert.That(
checker.IsAdult(new { Name = "Baby Duck", Age = 1 }), Is.False);
}


Либо:
IPerson stub = 
Duck.As<IPerson>(new { Name = "Donald", Age = 90 });

List<IPerson> family =
[
Duck.As<IPerson>(new { Name = "Huey", Age = 10 }),
Duck.As<IPerson>(new { Name = "Dewey", Age = 10 }),
Duck.As<IPerson>(new { Name = "Louie", Age = 10 }),
];


Идентичность сохраняется
Адаптеры перенаправляют вызовы методов Equals, GetHashCode и ToString к обёрнутому экземпляру, благодаря чему они корректно работают в словарях и множествах. Кроме того, можно получить доступ к исходному объекту:
var person = new Person();
Duck.As<INamed>(person)
.Equals(Duck.As<INamed>(person)); // true
Duck
.Unwrap(Duck.As<INamed>(person)) is Person; // true


А также поддерживаются:
- Любой интерфейс, включая интерфейсы фреймворков: Duck.As<IDisposable>(x), параметры IEnumerable<T> и т.п.;
- Параметры интерфейса, не требующие утиной типизации (например, ILogger), продолжают принимать реализации, значения null и значения по умолчанию;
- Обобщённые интерфейсы (IContainer<T>) и обобщённые методы [DuckTyped] с выведением аргументов типа;
- События, индексаторы и реализация интерфейса по умолчанию;
- Делегаты, лямбда-выражения и группы методов для интерфейсов с одним методом;
- Статические абстрактные члены и операторы с помощью ограничений утиной типизации (where T : IAddable<T>) - включая обобщённую математику;
- Методы экземпляра и статические методы, ref/out, params, значения по умолчанию и именованные аргументы;
- Нулевая настройка: установите пакет, никаких изменений в файлах проекта не требуется.

Источник: https://steven-giesel.com/blogPost/41599817-4457-441e-b874-1a5c67f4e7cc/if-it-quacks-part-2
  • 👍 1
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →