C# 在运行时更改类方法

4
我需要扩展一个实例的行为,但我无法访问该实例的原始源代码。例如:
/* I don't have the source code for this class, only the runtime instance */
Class AB
{
  public void execute();
}

在我的代码中,我想拦截每个调用执行的操作,进行一些处理后再调用原始的执行操作,类似于:

/* This is how I would like to modify the method invokation */
SomeType m_OrgExecute;

{
    AB a = new AB();
    m_OrgExecute = GetByReflection( a.execute );
    a.execute = MyExecute;
}

void MyExecute()
{
    System.Console.Writeln( "In MyExecute" );
    m_OrgExecute();
}

这可行吗?

有没有人能提供解决这个问题的方案?

10个回答

5
看起来你需要使用装饰者模式
class AB
{
   public void execute() {...}
}

class FlaviosABDecorator : AB
{
   AB decoratoredAB;

   public FlaviosABDecorator (AB decorated)
   {
       this.decoratedAB = decorated;
   }

   public void execute()
   {
       FlaviosExecute();  //execute your code first...
       decoratedAB.execute();
   }

   void FlaviosExecute() {...}
}

然后您需要修改使用AB对象的代码。

//original code
//AB someAB = new AB();

//new code
AB originalAB = new AB();
AB someAB = new FlaviosABDecorotor(originalAB);

/* now the following code "just works" but adds your method call */

1
这有一个严重的缺点:所有编程使用AB的第三方代码将继续使用原始类。 - ivan_pozdeev

3

无法直接通过反射等方式来实现此操作。

要像这样注入自己的代码,您需要创建一个修改过的程序集版本,并使用某种形式的代码注入。您不能在运行时“更改”任意程序集的方法。


2
我建议您看一下PostSharp。它可以“重连”现有的编译程序集,以添加您需要的前后处理。我不能百分之百确定它是否能够满足您的需求,但很可能可以。 http://www.sharpcrafters.com/aop.net

1
这需要将程序集添加特性,因为这就是PostSharp确定要重写什么的方式。 - Reed Copsey
@Reed:你可以在PostSharp中使用[assembly:]级别的属性。话虽如此,这些属性可能需要放在程序集的AssemblyInfo.cs文件中。 - TrueWill

2

1
由于我更喜欢组合而不是继承(如果类被密封,继承可能不是一个选项),因此我会像这样在自己的类中包装AB,名为FlaviosAB...
public class FlaviosAB
{
    private AB _passThrough;
    public FlaviosAB(){
        _passThrough = new AB();
    }

    public void execute()
    {
        //Your code...
        Console.WriteLine("In My Execute");
        //Then call the passThrough's execute.
        _passThrough.execute();
    }
}

这样可以避免原始类被密封的任何问题。根据调用者需要AB上有多少其他公共方法,可能需要通过包装器表达更多内容。 - John K
@jdk:可能需要更多,但问题中没有指定,所以我没有包括它 :) 另外,你的 gravatar 看起来就像我的纹身 :) - Jason Punyon
如果你的纹身图片已经发布在互联网上,那么我就把它作为我的Gravatar盗用了。我只能给你的答案点+1一次,否则我会为这个酷炫的纹身提供第二个! :) - John K

0
如果AB没有被封闭,那么你可以继承这个类并覆盖这个方法。在这种情况下使用new关键字。
class ABChild : AB {
    public new void execute() {
        System.Console.Writeln( "In MyExecute" );
    }
}

根据评论,你应该在ABChild类中调用这个new方法:
void Invoke() {
    ABChild a = new ABChild();
    a.execute();
}

希望能对你有所帮助!


1
如果调用execute()的调用者在使用AB,这段代码将无法运行。它会在AB上调用原始方法,除非你将其强制转换为ABChild。 - RationalGeek

0

您可以使用包装类:

Class ABWrapper
{
  private AB m_AB;

  ABWrapper( AB ab )
  {
    m_AB = new AB();
  }

  public void execute()
  {
    // Do your stuff, then call original method
    m_AB.execute();
  }
}

AB实现了一个接口时,这是一个很好的方法(尽管你没有提到)。在这种情况下,ABWrapper应该实现相同的接口。当您使用工厂甚至依赖注入来创建您的AB实例时,您可以轻松地用您的包装器替换它们。


0
你可以继承这个类并重写 execute() 方法(如果这个类没有被密封,而且该方法至少不是私有的)。

0

你可以实现一个动态代理。在这里找到更多信息。

基本上,你需要扩展基类并覆盖一些方法。现在你需要重新分配被调用的对象为你的实例,并且所有的调用将首先通过你的对象。


0

或许这个Stackoverflow thread中的面向切面编程(AOP)解决方案之一会派上用场...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接