注意:我知道这在实践中是一个糟糕的想法;我只是好奇 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()
的类型的对象,即使它们不实现IEnumerable
等Current
,但这是编译器处理的内置概念,在这里您可以定义它们。
有趣的是,它还允许您定义静态成员。
我不相信这是不可能的。编译器需要看到显式实现接口或类的内容,以便编译器可以确认所有内容都已实现。
如果您可以使用重定向来做到这一点,则可能无法实现某些内容。这与 .NET 采用的安全方法背道而驰。
一个选项是在人身上创建一个包装类并将这个包装传递给方法,包装器需要显式实现接口。
如果你可以控制外部代码,并且愿意包装对象(似乎这里所有的答案都换行),那么动态绑定和像Impromptu-Interface这样的库在我看来对于本质上是一行的东西来说有很多麻烦。
class GreetablePerson : Person, IGreetable { }
大功告成。
当编译器构建 GreetablePerson 类时,来自 Person 的方法最终会执行接口的隐式实现,并且一切"正常工作"。 唯一的烦恼是外部代码必须实例化GreetablePerson
对象,但在标准的面向对象术语中,GreetablePerson
的实例是Person
的实例,所以在我看来,这似乎是对所问问题的有效答案。
如果更改了要求,以便您还具有预先存在的 Person 实例,那么像 Impromptu-Interface 这样的东西可能会变得更诱人,但即便如此,您可能还需要考虑GreetablePerson
提供一个从 Person 复制的构造函数。 从那里选择最佳前进路径需要获取有关相关 Person 类的要求和实际实现详细信息的更多详细信息。
在某种不相关的不,这是其他语言中通常做的事情,例如Scala和Haskell。
它被称为使用所谓的"类型类"。类型类实质上允许您定义类型的行为,就像它显式实现接口一样,而实际上不需要它这样做。您可以在此处阅读有关它的更多信息。