看着这个问题,我想知道是否可以使用c#的暗反射方式来实现类似的功能。
假设我有这样的代码:
public class Foo
{
public void FooPrint() // can't change this implementation
{
Console.Write("Foo");
}
}
public class Bar
{
public Foo foo = new Foo();
public Bar()
{
//do some reflection magic with member foo here ?
}
public void FooPrintRewritten()
{
Console.Write("Haha, only for Bar.foo.");
}
}
class Program
{
static void Main(string[] args)
{
Foo a = new Foo();
a.FooPrint(); // should still print "Foo"
Bar bar = new Bar();
bar.foo.FooPrint(); // should print "Haha, only for Bar.foo."
}
}
我在内联注释中所要求的是可能的吗?是否有一种方法可以将方法调用重新绑定到仅针对特定变量的另一个方法?
是的,我知道这很荒谬,不,这不应该在生产代码中使用。这是出于好奇
其他答案已经提出了在功能级别上可以实现您想要的方法,这可以说是同样的事情,但我将直接解决问题:除了Bar.Bar()
的实现之外,这是否可以在问题中不改变代码,保持Bar.foo
类型为Foo
并且不改变Foo
?
答案是否定的。你不能改变单个对象的方法表,这就是你在这里要求的。方法表是类型的一部分,而不是实例。如果表达式f
的类型是Foo
,并且FooPrint
是Foo
的非虚方法,则调用f.FooPrint()
将始终解析为Foo.FooPrint
。更糟糕的是,编译器可能会选择内联调用,因为这显然是一个安全的优化*。你黑暗的反射方式现在在哪里?
实现这一点的唯一方法是说服编译器对Foo.FooPrint
的调用应该特别处理,考虑到实例。有几种方法可以这样做:
-
Foo.FooPrint
可以作为委托。委托调用的目标是特定于委托实例的。 -
Foo.FooPrint
可以作为虚方法、抽象方法或接口方法。所有这些都是根据实例的运行时类型进行解析的。只需从Foo
中派生一个类,然后就可以了。 -
Foo
可以继承MarshalByRefObject
。通常所说的MBRO是由抖动特殊处理的,因为(顾名思义)调用可能需要封送回。特别是,如果Foo
是一个MBRO,你可以为它创建一个RealProxy
,它将提供一个透明的代理,几乎在所有方面都类似于一个真实的、实际的Foo
,直到GetType()
,除了你可以选择如何实际处理调用。
所有这些方法都被各种模拟/拦截器/代理库使用,所有这些方法都需要一些更改为Foo
。唯一不需要对Foo
进行(文本)更改的方法是那些重写所涉及的IL的方法,如PostSharp或Microsoft Fakes,但出于这个问题的目的,我认为这是作弊。
*从技术上讲,c#标准没有提到方法表或允许的内联方式,因为这些是实现细节,但它确实说Foo.FooPrint
总是只以一种方式解决,而不考虑实例(除了它不能是null
)。
实际上,如果您需要分离实现来维护原则(取决于您对SOLID ofc的严格程度),则在SOLID的SRP下存在这种类型的行为。
它被称为拦截器模式,由mock库(如Moq)使用。
可以看看下面这篇关于如何使用模式的文章:c#:当你可以拦截
这是在实例化类时重写(虚拟)方法的另一种方法
class Program
{
static void Main(string[] args)
{
Foo a = new Foo();
a.FooPrint(); // should still print "Foo"
Bar bar = new Bar();
bar.foo.FooPrint(); // should print "Haha, only for Bar.foo."
Console.Read();
}
}
public class Foo
{
public Action FooPrint = () => Console.WriteLine("Foo");
}
public class Bar
{
public Foo foo = new Foo()
{
FooPrint = () => Console.WriteLine("Haha, only for Bar.foo.")
};
}
这篇文章解释了如何在实例化类时设置重写权限。
但是,它使用Func,这需要一个包含非void返回类型的方法,这就是为什么你要使用Action来代替,正如这里所解释的
使用相同的用法:
public class Wrapper : Foo
{
public new void FooPrint()
{
Console.Write("Haha, only for Bar.foo.");
}
}
public class Bar
{
public Wrapper foo = new Wrapper();
}