Веб-версияОткрыть в Telegram

ПостДень 2784. #Оффтоп

14 сентября 2026
.
.NET Разработчик
День 2784. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков Если это ходит как утка и крякает как утка — значит, это утка. Применим утиную типизацию и заставим это работать в C# с помощью перехватчиков! Примечание: этот пост написан в образовательных целях, но вы смело можете использовать описанный подход в проде 😉 В TypeScript можно сделать вот так: class A { Do(): void { console.log("A.Do"); } } class B { Do(): void { console.log("B.Do"); } } function foo(a: { Do(): void }) { a.Do(); } Попробуем сделать что-то подобное в C#: public class A { public void Do() => Console.WriteLine("A.Do"); } public class B { public void Do() => Console.WriteLine("B.Do"); } public void Foo(??? a) => a.Do(); У классов A и B нет ничего общего: ни базового класса, ни общего интерфейса. Нужно придумать некую «форму» (обозначим её как ???) — нечто такое, что позволит успешно скомпилировать вызовы Foo(new A()) и Foo(new B()) и обеспечит вызов нужного метода Do() в каждом случае, при этом вообще не затрагивая сами классы A и B. Кроме того, запрещено использовать следующие подходы: - dynamic (хотя это и невероятно крутая штука); - аргумент типа object с последующим приведением типов через is или as; - модификацию классов A или B (например, добавление общего базового класса или интерфейса). Перехватчики Начиная с .NET 8, в C# появилась экспериментальная функция компилятора — перехватчики. Генератор исходного кода может создать метод, пометить его атрибутом [InterceptsLocation], указав конкретное место вызова в вашем коде, и компилятор незаметно перенаправит вызов на этот метод. Это происходит без каких-либо затрат ресурсов во время выполнения, так как всё разрешается на этапе компиляции. Обычно это реализуется через сочетание пользовательских атрибутов (например, DuckType и DuckShape), создаваемых генератором кода, и логики перехватчика: система находит все места вызовов и выполняет необходимые действия. Таким образом, с точки зрения использования нам всё равно потребуется нечто вроде интерфейса (или свойства) для «описания формы»: [DuckShape] public interface IDoable { void Do(); } public static partial class Ops { [DuckTyped] public static void Foo(IDoable a) => a.Do(); } Вы пишете Foo(IDoable a) => a.Do() - именно так, как вам хочется. Атрибут [DuckShape] помечает IDoable как структурный контракт, а не как интерфейс, который нужно реализовывать вручную. Атрибут [DuckTyped] сообщает генератору: «Вот настоящая логика; сделай так, чтобы её можно было вызывать с любым объектом, подходящим по структуре». Полный код генератора/перехватчика на GitHub, а здесь рассмотрим наиболее интересные фрагменты. Так или иначе, каждый вызов сводится к следующей обобщённой конструкции: [InterceptsLocation(1, "…")] public static void Interceptor_1(A value) { Ops.Foo((IDoable)(new ShapeAdapter_IDoable_A(value))); } ShapeAdapter_IDoable_A - это небольшая генерируемая структура только для чтения, которая реализует IDoable, перенаправляя сразу в A: internal readonly struct ShapeAdapter_IDoable_A : IDoable { private readonly A _value; public ShapeAdapter_IDoable_A(A value) => _value = value; public void Do() => _value.Do(); } Вот и весь фокус. Это ведёт к незначительным накладным расходам (возможно инлайнинг поможет с этим). Таким образом, следующий код работает прекрасно: Ops.Foo(new A()); // "A.Do" Ops.Foo(new B()); // "B.Do" Это также работает для свойств: [DuckShape] public interface INameable { string Name { get; set; } } public class Person { public string Name { get; set; } = ""; } [DuckTyped] public static string Greet(INameable n) => $"Hello, {n.Name}!"; Greet(new Person { Name = "Jon" }); // "Hello, Jon!" Это можно оформить в NuGet, если кто-то увидит в этом реальный смысл. Итого Полезно ли это? Вероятно, нет — но не в этом главная цель. Хотя язык не поддерживает это напрямую, подобное поведение можно в определённой мере «эмулировать». Источник: https://steven-giesel.com/blogPost/5170e165-29e8-437a-b5ce-446a84943809/quack-quack-ducktyping-in-c-with-interceptors
10 · 1.4K ·

Рядом в ленте

..NET РазработчикДень 2782. #ЗаметкиНаПолях Паттерн «Производитель-потребитель» c System.Threading.Channels. Продолжение Начало Производитель: конечная точка, передающая задачи ..NET РазработчикДень 2783. #ЗаметкиНаПолях Паттерн «Производитель-потребитель» c System.Threading.Channels. Окончание Начало Продолжение Настройка и запуск нескольких потребите
это сообщение
..NET РазработчикДень 2785. #ЗаметкиНаПолях Обеспечиваем Изоляцию Тенантов с Помощью PostgreSQL Механизм защиты на уровне строк (Row-Level Security, RLS) в PostgreSQL добавляет ..NET РазработчикОпрос
..NET Разработчик.NET Разработчик@NetDeveloperDiary · канал · Технологии
6 750подписчиков1 355средний охват поста
Лента площадки Открыть в Telegram

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем