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

Passing C# parameters which can "fit" an interface, but do not actually implement it

注意:我知道这在实践中是一个糟糕的想法;我只是好奇 CLR 允许你做什么,目标是创建某种 'modify a class after creating it' 预处理器。

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

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

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

interface IGreetable {
    string Greet();
} 

// ...

void PrintGreeting(IGreetable g) => Console.WriteLine(g.Greet());

class Person 没有明确实现 IGreetable,但 可以 在不对其方法进行任何修改的情况下实现。

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

我不相信这是可能的。编译器需要查看明确实现​​接口或 class 的内容,以便编译器可以确认所有内容均已实现。

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

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

尝试使用库Impromptu-Interface

[The Impromptu-Interface] framework to allow you to wrap any object (static or dynamic) with a static interface even though it didn't inherit from it. It does this by emitting cached dynamic binding code inside a proxy.

这允许你做这样的事情:

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

您可以使用 dynamic 包装器对象自行连接,但在包装内会失去类型安全性 class:

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();
}

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

所谓的使用"type classes"。类型 类 本质上允许您为类型定义行为,就好像它显式实现了接口一样,而不需要实际要求它这样做。您可以阅读更多相关信息 here

这可能很快就会变得很容易。类型 classes 可能会作为 shapes 引入 C#,您可以在其中定义 class 的功能并针对 [=22] 编写代码=]shape 然后将您的代码与任何匹配的类型一起使用,而无需该代码的作者声明任何内容,就像您描述的那样。

现在 C# 中最接近的事情可能是 foreach 如何处理具有 GetEnumerator() 的类型返回具有 MoveNext() 和 [=13= 的类型的对象] 即使它们没有实现 IEnumerable 等,只是当这是编译器处理的内置概念时,您可以在此处定义它们。

有趣的是,它还可以让您定义静态成员。

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

class GreetablePerson : Person, IGreetable { }

大功告成。

当编译器构建 GreetablePerson class 时,来自 Person 的方法最终会隐式实现接口,所有的一切 "just works." 唯一令人恼火的是外部代码必须实例化 GreetablePerson 对象,但是在标准的面向对象术语中,GreetablePerson 的实例是 Person 的实例,所以在我看来这就像一个所问问题的有效答案。

如果要求发生变化,您也有预先存在的 Person 实例,那么像 Impromptu-Interface 这样的东西可能会变得更有吸引力,但即便如此,您可能还是想考虑给 GreetablePerson 一个构造函数来自 Person 的副本。从那里选择最佳前进路径需要获得有关人员 class 的要求和实际实施细节的更多详细信息。