【问题标题】:Make object dynamically implement an interface in code使对象在代码中动态实现接口
【发布时间】:2011-03-30 16:00:47
【问题描述】:

我想让这个测试通过 - 有人知道怎么做吗?

public class Something
{
    public string Name {get; set}
}

public interface IWithId
{
    public Guid Id {get; set}
}

public class IdExtender 
{
    public static Object Extend(object toExtend)
    {
        ...?
    }
}

public class Tests 
{
    [Test]
    public void Should_extend_any_object()
    {
        var thing = new Something { Name = "Hello World!"};
        var extended = IdExtender.Extend(thing);
        Assert.IsTrue(extended is IWithId);
        Assert.IsTrue(extended.Id is Guid);
        Assert.IsTrue(extened.Name == "Hello World!");
    }
}

我想这样的事情可以用城堡动态代理,linfu等来完成......但是如何?

【问题讨论】:

  • 如果有办法做到这一点,我很好奇..
  • 我可以回答这个问题,我以前做过,但我不想被一些单行词和一两个参考文档所击败,如果我要经历破解打开视觉工作室并整理解决方案的麻烦......所以我会等一下,如果在几个小时内没有好的答案,我会提交一个答案。
  • 这会扩展实例还是整个类型?
  • @Jimmy Hoffa,只有实例
  • 我认为 .NET 中没有任何方法可以做到这一点。你有一个看起来像“装饰器”的模式,但它已经坏了。我认为你想做的和你创造的并不一致。你能带我们回到“你想要完成什么”吗?

标签: c# castle-dynamicproxy cop


【解决方案1】:

使用 Castle DP(显然)

嗯 - 您必须创建一个新对象才能返回,因为您不能让现有类型在程序执行时获得新接口。

为此,您需要创建一个代理,然后将现有对象的状态复制到代理上。 DP不这样做OOTB。在 v2.5 中,您可以将新的类代理与 target 一起使用,但这只有在类型上的所有属性都是虚拟的情况下才有效。

无论如何。您可以通过将代理与实现该属性的现有对象混合,使新类型获得IWithId 接口。然后对接口成员的调用将被转发到对象。

或者,您可以将其作为附加接口提供来实现,并让拦截器充当实现者的角色。

【讨论】:

  • 听起来很有趣——你没有碰巧有代码示例吗?
  • 我可以制作一个...我猜
  • 接受是否意味着你想通了,不希望我为你创建样本?
  • 不,这回答了我的问题。我仍然很乐意举个例子。如果我可以使用 DP2,我可以腾出时间重新编译 nhibernate 以使其与已发布的 linfu 版本一起工作:)。
  • 我迷失在“你可以让新类型获得IWithId...”这里的语言变得有点模棱两可和混乱。通过您所描述的示例,这将是一个更有力的答案。
【解决方案2】:

现在我会像这样使用 linfu:

public class IdExtender 
{
    public static Object Extend(object toExtend)
    {
        var dyn = new DynamicObject(toExtend);
        dyn.MixWith(new WithId {
                                Id = Guid.New()
                               });
        var extended = dyn.CreateDuck<IWithId>(returnValue.GetType().GetInterfaces());
        return extended;
    }
}

【讨论】:

  • 在 google 上找到 - DynamicObject 不能被实例化,只是从
【解决方案3】:

为什么不使用mocking framework like Moq 或RhinoMocks。它们允许您动态实现接口,而无需直接创建代理。

有了起订量,你可以写:

var mock = new Mock<IWithId>();  // mock the interface
mock.Setup(foo => foo.Name).Returns("Hello World"); // setup a property

除此之外,您还必须做一些复杂的工作才能得到您想要的。据我所知,您不能在运行时向现有类添加方法或属性。你可以返回一个从传入的类型动态继承的新对象的实例。Castle 肯定允许这样做,但所需的代码不是特别优雅或简单。

【讨论】:

  • 嗯,但他们可以将其添加到现有对象上吗?那将是一个有趣的滥用:)
  • I@illdev:有了 Moq,就没有必要将它添加到现有对象上 - 您只需建立模拟以具有给定的属性。无论如何,您不能动态地将方法或属性添加到任何现有类。您也许可以使用 .NET 4.0 动态对象,但这需要调用者也使用 dynamic
  • 发帖人并没有推断出他的类型必须动态改变。他的“converter”方法的返回类型是“object”。这在 .NET 3.5 及更低版本中绝对可行
  • 我的用途是像管道一样,某种类型(以及该基本类型的 100 种实现)总是通过。我不想手动修补每个实现(不能触及基本类型并且希望避免创建中介),我只想注入这些属性。
  • @illdev:这很容易路由,但是 moq 不能做的是创建一个既是 Something 又是 IWithId 的实例,您必须定义从两者继承的第三种类型才能被识别两者兼而有之。
【解决方案4】:

撇开如何动态附加属性或接口的问题不谈,听起来您正在尝试做的是用额外的数据来扩充现有的类。此问题的一个非常典型的解决方案是使用Dictionary&lt;Something, SomethingExtra&gt; 并将其存储在维护映射的某个服务类中。现在,当您需要访问 SomethingExtra 时,您只需向服务类询问相关信息即可。

优点:

  1. 与使用反射和动态代理生成的解决方案相比,该实现更易于理解和维护。

  2. 如果需要从被扩展的类型派生,类不能被密封。在密封类中,外部关联信息可以正常工作。

  3. 您可以关联信息,而无需负责构建增强对象。您可以在任何地方创建实例并关联新信息。

缺点:

  1. 您需要注入维护映射的服务实例。如果您使用手动注入(通常通过构造函数传递),则可以通过 IoC 框架执行此操作,如果没有其他替代方案,则可以通过 Singleton 静态访问器。

  2. 如果您有大量实例并且正在快速创建/删除它们,则字典开销可能会变得很明显。我怀疑与代理实现相比,在开销变得明显之前,您需要一些非常重的负载。

【讨论】:

  • 欢迎回到非托管世界。开发人员必须确保他们在完成处理后从字典中删除了任何对象条目 - 本质上是为了释放对象(释放它以进行垃圾收集)。
  • @Steve,好点,这应该在缺点列表中。这基本上是附加依赖属性在 WPF 中的工作方式,所以我必须做一些调查,看看 MS 是如何解决这个问题的。
  • @Steve,我找不到有关 MS 如何解决问题的任何信息,但可以使用弱引用并定期将记录字典清除到已收集的实例中来解决它。有趣的是,这是我第一次遇到弱引用的好用例。
【解决方案5】:

实际上我上周已经回复了similar question。我还写了一个小库来做这件事。它可以从我的blog 获得,我无耻地插入。

使用 Castle Dynamic Proxy 几乎可以实现您所追求的目标。唯一的限制是现有实例必须实现一个接口,并且您感兴趣的所有属性/方法都可以通过该接口使用。

public static TIntf CreateMixinWithTarget<TIntf>(TIntf target, params object[] instances) where TIntf : class{

    ProxyGenerator generator = new ProxyGenerator();
    ProxyGenerationOptions options = new ProxyGenerationOptions();

    instances.ToList().ForEach(obj => options.AddMixinInstance(obj));

    return generator.CreateInterfaceProxyWithTarget <TIntf>(target, options);
}

[Test]
public void Should_extend_any_object()
{
    var thing = new Something { Name = "Hello World!"};
    var extended = CreateMixinWithTarget<ISomething>(thing, new WithId(), new GuidImpl());
    Assert.IsTrue(extended is IWithId);
    Assert.IsTrue(extended.Id is Guid);
    Assert.IsTrue(extened.Name == "Hello World!");
}

【讨论】:

  • 我运气不好。在我的情况下,实例没有实现任何接口......当你说它们应该 - 这有什么意义?这不是强加的,接口也被实现了吗?无论如何,如果你比较下面的 linfu 版本 - 我认为它更优雅....
  • 这是动态代理限制,与它创建代理的方式有关。基于现有实例创建代理的唯一方法是调用 CreateInterfaceProxyWithTarget,但需要注意的是实例必须实现类型参数指定的接口。我同意,linfu 版本更干净。
  • @IgorZevaka 我是否正确理解您所说的无法动态创建从SomeConcreteType 继承并实现ISomeInterface 的代理?似乎应该可以,因为它可以创建没有目标的接口代理。
猜你喜欢
  • 2012-07-22
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 2019-02-16
  • 2011-10-18
  • 2010-12-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多