В TypeScript достаточно, чтобы у объекта был нужный метод:
function foo(a: { Do(): void }) {
a.Do();
}
🔜 В C# так напрямую нельзя:
class A
{
public void Do() => Console.WriteLine("A.Do");
}
class B
{
public void Do() => Console.WriteLine("B.Do");
}
🔜 У A и B нет общего интерфейса. Но хочется вызвать:
Foo(new A());
Foo(new B());
И здесь на сцену выходят interceptors — экспериментальная возможность компилятора C#.
🌞 Идея:
[DuckShape]
public interface IDoable
{
void Do();
}
public static partial class Ops
{
[DuckTyped]
public static void Foo(IDoable value)
=> value.Do();
}
📍 Source generator создаёт адаптер:
internal readonly struct ShapeAdapter_IDoable_A : IDoable
{
private readonly A _value;
public ShapeAdapter_IDoable_A(A value)
=> _value = value;
public void Do() => _value.Do();
}
А interceptor подменяет вызов и оборачивает A или B в подходящий адаптер.
🔴 В итоге:
Ops.Foo(new A()); // A.Do
Ops.Foo(new B()); // B.Do
Работает и с properties — например, можно описать INameable с Name { get; set; }, а затем передать обычный Person, не меняя его код.
⚠️ Но это скорее интересный PoC, чем готовая замена интерфейсам:
— interceptors остаются экспериментальной возможностью;
— нужен source generator;
— есть ограничения и не покрытые edge cases;
— появляется дополнительная магия на этапе компиляции.
🔗 Источник
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#sharp_view
