传递 C# 参数,这些参数可以"fit"接口,但实际上并未实现它



注意:我知道这在实践中是一个糟糕的想法;我只是好奇 CLR 允许您做什么,目标是创建某种"创建类后修改类"预处理器。

假设我有以下类,它是在另一个程序集中定义的,所以我无法更改它。

class Person {
public string Greet() => "Hello!";
}

我现在定义一个接口和一个方法,如下所示:

interface IGreetable {
string Greet();
} 
// ...
void PrintGreeting(IGreetable g) => Console.WriteLine(g.Greet());

Person没有显式实现IGreetable,但它可以不对其方法进行任何修改。

有了这个,有什么方法,使用反射,DLR或其他任何东西,可以在不修改上述任何代码的情况下成功地将Person实例传递给PrintGreeting

尝试使用库即兴界面

[Impromptu-Interface] 框架,允许您使用静态接口包装任何对象(静态或动态),即使它不是从它继承的。它通过在代理内发出缓存的动态绑定代码来实现此目的。

这允许您执行以下操作:

var person = new Person();
var greeter = person.ActLike<IGreetable>();

您可以使用dynamic包装器对象自己连接它,但您将在包装类中失去类型安全性:

class GreetableWrapper : IGreetable
{
private dynamic _wrapped;
public GreetableWrapper(dynamic wrapped)
{
_wrapped = wrapped;
}
public string Greet()
{
return _wrapped.Greet();
}
}
static void PrintGreeting(IGreetable g) => Console.WriteLine(g.Greet());
static void Main(string[] args)
{
PrintGreeting(new GreetableWrapper(new Person()));
Console.ReadLine();
}

这可能很快就会变得很容易。类型类可以作为形状引入 C#,您可以在其中定义类的功能和针对该形状的代码,然后将代码用于匹配的任何类型,而无需该代码的作者声明任何内容,就像您描述的那样。

现在 C# 中最接近的事情可能是foreach如何处理具有GetEnumerator()返回具有MoveNext()的类型的对象,即使它们不实现IEnumerableCurrent,但这是编译器处理的内置概念,在这里您可以定义它们。

有趣的是,它还允许您定义静态成员。

我不相信这是不可能的。编译器需要看到显式实现接口或类的内容,以便编译器可以确认所有内容都已实现。

如果您可以使用重定向来做到这一点,则可能无法实现某些内容。这与 .NET 采用的安全方法背道而驰。

一个选项是在人身上创建一个包装类并将这个包装传递给方法,包装器需要显式实现接口。

如果你可以控制外部代码,并且愿意包装对象(似乎这里所有的答案都换行),那么动态绑定和像Impromptu-Interface这样的库在我看来对于本质上是一行的东西来说有很多麻烦。

class GreetablePerson : Person, IGreetable { }

大功告成。

当编译器构建 GreetablePerson 类时,来自 Person 的方法最终会执行接口的隐式实现,并且一切"正常工作"。 唯一的烦恼是外部代码必须实例化GreetablePerson对象,但在标准的面向对象术语中,GreetablePerson的实例Person的实例,所以在我看来,这似乎是对所问问题的有效答案。

如果更改了要求,以便您还具有预先存在的 Person 实例,那么像 Impromptu-Interface 这样的东西可能会变得更诱人,但即便如此,您可能还需要考虑GreetablePerson提供一个从 Person 复制的构造函数。 从那里选择最佳前进路径需要获取有关相关 Person 类的要求和实际实现详细信息的更多详细信息。

在某种不相关的不,这是其他语言中通常做的事情,例如Scala和Haskell。

它被称为使用所谓的"类型类"。类型类实质上允许您定义类型的行为,就像它显式实现接口一样,而实际上不需要它这样做。您可以在此处阅读有关它的更多信息。

相关内容

最新更新