views:

135

answers:

5

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?

+1  A: 

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.

LBushkin
Hm, but can they add it on to an existing object? That would be an interesting abuse :)
Jan Limpens
I@illdev: With Moq it wouldn't be necessary to add it onto an existing object - you would just establish the mock to also have the given property. You can't dynamically add methods or properties to any existing class, anyway. You could perhaps use .NET 4.0 dynamic objects, but that would require the caller to use `dynamic` as well.
LBushkin
The poster didn't infer that his type had to change dynamically. The return type of his "converter" method is "object". This is definitely doable in .NET 3.5 and lower
Steve
My use is at a pipeline like thing where a certain type (and 100s of implementations of that base type) always passes. Instead of manually patching every implementation (cannot touch the basetype and would like to avoid to create an intermediary), I would just want to inject these properties.
Jan Limpens
@illdev: That's easy routing, but what moq can't do is create an instance that is both Something and IWithId, you would have to define a third type that inherits from both for it to be recognized as both.
Jimmy Hoffa
@Steve, but how?
Jan Limpens
@Jimmy, at the time, I want to strap on that interface, the object is already created.
Jan Limpens
He doesn't just want the test to work. He wants this to work in production code outside of a mocking or unit test framework.
Dave White
A: 

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.

Dan Bryant
Welcome back to unmanaged world. The developer would have to be SURE they removed any object entries from the dictionary when they were done with them - in order to, essentially, release the object (free it for garbage collection).
Steve
@Steve, good point, that should go on the Disadvantages list. This is basically the way that Attached Dependency Properties work in WPF, so I'll have to do some investigation to see how MS solves this problem.
Dan Bryant
@Steve, I couldn't find any information on how MS solves the problem, but it can be resolved using weak references and periodically purging the dictionary of records to instances that have been collected. Interestingly, this is the first time I've encountered a good use case for weak references.
Dan Bryant
+2  A: 

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;
    }
}
Jan Limpens
This is so cool in so may levels!
Jan Limpens
+1  A: 

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!");
}
Igor Zevaka
That's bad luck for me. In my case the instances do not implement any interface ... when you are saying, that they should - how does that make sense? Doesn't that impose, that the interface is implemented as well?Anyway, if you compare the linfu version below - I think it is way more elegant....
Jan Limpens
This is Dynamic Proxy restriction and is to do with the way it creates proxies. The only way to create a proxy based on an existing instance is to call CreateInterfaceProxyWithTarget, but the caveat is that the instance must implement an interface specified by the type param. And I do agree, the linfu version is much cleaner.
Igor Zevaka
A: 

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.

Krzysztof Koźmic
Sounds interesting - you don't happen to have a code sample somewhere?
Jan Limpens
I could produce one... I guess
Krzysztof Koźmic
does the accept mean you figured it out, and don't want me to create a sample for you?
Krzysztof Koźmic
No, just that that answers my question. I still would be glad to get an example. If I could go with DP2, I could spare myself to heave to recompile nhibernate to make it work with the released linfu version :).
Jan Limpens