让对象在代码中动态实现接口

发布于 2024-09-14 22:44:38 字数 674 浏览 7 评论 0原文

我想让这个测试通过 - 有人知道如何做到这一点吗?

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 等来完成......但是如何呢?

I want to make this test pass - anyone got an idea how to do that?

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!");
    }
}

I guess something like this could be done with castle dynamic proxy, linfu,etc... but how?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

执笔绘流年 2024-09-21 22:44:38

使用 Castle DP(显然)

好吧 - 您将必须创建一个新对象来返回,因为在程序执行时您无法让现有类型获得新接口。

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

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

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

Using Castle DP (obviously)

Well - you will have to create a new object to return since you can't have an existing type gain a new interface as your program is executing.

To do this you will need to create a proxy and then replicate the state of your pre-existing object onto the proxy. DP does not do that OOTB. In v2.5 you could use the new class proxy with target but that would work only if all the properties on the type were virtual.

Anyway. You can make the new type gain the IWithId interface either by mixing in the proxy with an existing object that implement the property. Then the calls to members of the interface will be forwarded to the object.

Alternatively you can provide it as additional interface to implement and have an interceptor fill in the role of implementor.

晒暮凉 2024-09-21 22:44:38

现在我将像这样使用 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;
    }
}

For now I am going with linfu like this:

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;
    }
}
意犹 2024-09-21 22:44:38

为什么不使用模拟框架,例如 Moq 或 RhinoMocks。它们允许您动态地实现接口,而无需直接创建代理。

使用起订量,您可以这样写:

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

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

Why not use a mocking framework like Moq or RhinoMocks. They allow you to dynamically implement interfaces without the hastle of creating a proxy directly.

With Moq you could write:

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

Aside from that, you would have to do some complicated work to get what you want. As far as I know, you cannot add method or properties to an existing class at runtime. You could return an instance of a new object that dynamically inherits from the type passed in. Castle definitely allows this, but the code necessary is not especially elegant, or simple.

感情旳空白 2024-09-21 22:44:38

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

优点:

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

  2. 如果您需要从正在扩展的类型派生,则无法密封该类。外部关联信息与密封类配合得很好。

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

缺点:

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

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

Setting aside the question of how to dynamically attach a property or interface, it sounds like what you're attempting to do is augment existing classes with additional data. A very typical solution to this problem is to use a Dictionary<Something, SomethingExtra> and store it in some service class that maintains the mapping. Now, when you need access to SomethingExtra, you just ask the service class for the associated information.

Advantages:

  1. The implementation is easier to understand and maintain than a solution using reflection and dynamic proxy generation.

  2. If you need to derive from the type being extended, the class can't be sealed. Associating information externally works fine with sealed classes.

  3. You can associate information without having to be responsible for the construction of the augmented object. You can take instances created anywhere and associate the new information.

Disadvantages:

  1. You need to inject the service instance that maintains the mapping. You can do this via an IoC framework if you're using one, manual injection (usually passed through a constructor) or, if there's no other alternative, via a Singleton static accessor.

  2. If you have a very large number of instances and they are being rapidly created/removed, the Dictionary overhead could become noticeable. I suspect you would need some very heavy load before the overhead becomes noticeable compared to a proxying implementation.

我不在是我 2024-09-21 22:44:38

我实际上已经回答了 类似的问题。我还写了一个小库来做这件事。它可以从我的 博客 获取,我在我无耻地插电。

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

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!");
}

I have actually answered a similar question last week. I have also written a little library that does this one thing. It is available from my blog, which I am shamelessly plugging.

What you are after is almost achievable using Castle Dynamic Proxy. The only constraint is that the existing instance must implement an interface and all the properties/methods you are interested in are available through that interface.

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!");
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文