【问题标题】:Wrapping existing objects to intercept method/property calls in .NET包装现有对象以拦截 .NET 中的方法/属性调用
【发布时间】:2009-12-19 04:08:14
【问题描述】:

我有一种情况,我想拦截对 .NET 中属性的调用。我一直在看 Castle 中的 DynamicProxy,它似乎工作正常。但似乎为了使用它,我必须从一个新对象开始,这意味着我不能做这样的事情:

MyType myType = new MyType();
myType.Property = "Test";

...

MyType wrappedMyType = proxyBuilder.Wrap(myType, new MyInterceptor());
wrappedMyType.Property = "Test2";

我只是错过了什么吗?

编辑:

天哪,它当然应该是 WrappedMyType。大错。对不起。 :(

【问题讨论】:

  • 我不知道这是否可行,但在我看来,使用静态类型语言会相当困难。
  • 你不能,原因如下:kozmic.pl/archive/2009/12/02/…
  • @Krzysztof 我宁愿有一个代理生成器,它强制我将所有内容标记为“更可见”而不是保护为虚拟而不是失去包装现有对象的能力。

标签: .net castle-dynamicproxy


【解决方案1】:

它不是那样工作的,它不会以任何方式改变原始对象。

这样想。让我们考虑搬到中国,为一家中国公司工作,这只会将你的工资支付到中国银行的中国银行账户。

所以,您需要获得一个中国银行账户。问题是,您要使用的银行不会说英语,所以您遇到了问题。

如果可用,您可以调用代理服务、翻译服务,它会代表您致电银行。你对这个代理人说的任何话,都会被翻译成中文,然后告诉银行官员。他/她用中文回复的任何内容都会被翻译成英文,然后和你说。

实际上,您现在可以在与您的银行交谈时沿着通信线路做一些事情

但是,它不会让您的银行官员说英语。

您的示例中的代理对象不会修改基础对象。每当您调用代理对象上的方法时,它们会依次调用底层对象上的方法,并可能在此过程中进行处理。

但是如果你回避代理对象,什么都没有改变。

【讨论】:

  • 对不起,在我的代码示例中犯了一个错误,完全改变了它的含义。 :(
  • 没关系,但现在我不明白这个问题。您想要的究竟是什么,以及没有发生或正在发生的事情是什么?
  • 我只是想知道为什么没有自动代理生成器具有这种功能。抱歉没有说清楚。
  • 很抱歉,但我在之前的评论中试图表达的观点是我不明白您的问题,因此“这种功能”毫无意义大部头书。你具体想做什么?注意,如果你想让你的代理拦截器被底层对象的所有方法调用调用,当在代理对象上调用拟合方法时,完全可以这样做,但代理对象只能处理虚拟方法(或通过接口)。
【解决方案2】:

您不能这样做,这是有充分理由的。这并非特定于温莎城堡。问题是您无法保证这些方法被标记为virtual,因此您会遇到不一致的情况,即有些状态来自包装对象,而有些状态来自代理对象。

想想下面这个非常简单的例子:

abstract class AbstractPerson {
    public int Age { get; protected set; }
    public abstract void Birthday();
}

class Person : AbstractPerson {
    public Person(int age) { Age = age; }
    public override Birthday() { Age++; }
}

假设我们要为AbstractPerson 创建一个代理来拦截Birthday

class PersonProxy : AbstractPerson {
    readonly AbstractPerson wrappedPerson;

    public PersonProxy(AbstractPerson person) { 
        wrappedPerson = person;
    }
    public override void Birthday() {
        DoInterceptors();
        wrappedPerson.Birthday();
    }
    public void DoInterceptors() { 
        // do interceptors 
    }
}

请注意,我们不能覆盖 Age,因为它没有被标记为 virtual。这就是令人讨厌的状态不一致的来源:

Person knuth = new Person(71);
PersonProxy proxy = new PersonProxy(knuth);
Console.WriteLine(knuth.Age);
knuth.Birthday();
Console.WriteLine(knuth.Age);
Console.WriteLine(proxy.Age);

这将打印出来

71
72
0

到控制台。发生了什么?因为Age 没有被标记为虚拟,所以我们的代理对象不能覆盖基本行为并调用wrappedPerson.Age。此示例甚至表明,将 Age = wrappedPerson.Age 添加到 PersonProxy 的构造函数将无济于事。我们的代理并不是真正的代理。这就是你不能包装现有对象的原因。

【讨论】:

  • 您的示例不适合现有对象的子集吗?为什么不拒绝适合您的示例的代理对象并允许代理所有其他情况?
【解决方案3】:

PostSharp可能对你有用,具体取决于你想对“拦截”做什么,以及你是否可以修改原始代码。

要使此选项成为可行的选项,您必须能够向要拦截的原始属性添加属性。 (我猜这在您的情况下不是一个选项,但不能确定。)如果您能够做到这一点,您可以创建一个属性(从 OnMethodBoundaryAspect 派生),它可以设置“ReturnValue”和'FlowBehavior' 这样您就可以有效地拦截呼叫。

【讨论】:

    【解决方案4】:

    您可能可以使用 System.Reflection.Emit.TypeBuilder 执行此操作,但这并不容易,并且可能不适用于所有类型。例如,您不能在密封类型上执行此操作,因为为了保持正常使用您的类型的能力,您必须在您构建的类型中从它继承,并且您必须覆盖或隐藏基类上的每个属性.最重要的是,当您覆盖它以引发事件或其他内容时,您必须将 IL 发送到属性集方法的主体中。

    所有这些都是可能的,但并不容易,也不完美。使用另一种解决方案可能会更好。 我确实喜欢这种东西,所以也许如果我有时间我会用代码示例更新这个答案(对不起,我应该是“工作”)。

    更新:我越是想这件事就不会发生。也许如果您尝试包装的对象总是实现一个接口并且您只想拦截这些成员。我考虑过为此发布一个样本,但我认为它会污染这个问题。最好的情况是您最终会遇到 Jason 的回答中描述的情况。

    【讨论】:

      猜你喜欢
      • 2015-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-05
      • 1970-01-01
      • 2019-10-01
      • 2014-10-11
      相关资源
      最近更新 更多