views:

63

answers:

1

I wrote a class that allows a derivate to which of its properties can be lazy loaded. The code is:

public abstract class SelfHydratingEntity<T> : DynamicObject where T : class {
    private readonly Dictionary<string, LoadableBackingField> fields;

    public SelfHydratingEntity(T original) {
        this.Original = original;
        this.fields = this.GetBackingFields().ToDictionary(f => f.Name);
    }

    public T Original { get; private set; }

    protected virtual IEnumerable<LoadableBackingField> GetBackingFields() {
        yield break;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        LoadableBackingField field;
        if (this.fields.TryGetValue(binder.Name, out field)) {
            result = field.GetValue();
            return true;
        } else {
            var getter = PropertyAccessor.GetGetter(this.Original.GetType(), binder.Name);
            result = getter(this.Original);
            return true;
        }
    }

    public override bool TrySetMember(SetMemberBinder binder, object value) {
        LoadableBackingField field;
        if (this.fields.TryGetValue(binder.Name, out field)) {
            field.SetValue(value);
            return true;
        } else {
            var setter = PropertyAccessor.GetSetter(this.Original.GetType(), binder.Name);
            setter(this.Original, value);
            return true;
        }
    }
}

And a derivate class:

public class SelfHydratingPerson : SelfHydratingEntity<IPerson> {
    private readonly IDataRepository dataRepository;

    public SelfHydratingDerivate(IDataRepository dataRepository, IPerson person)
        : base(person) {
        this.dataRepository = dataRepository
    }

    protected override IEnumerable<LoadableBackingField> GetBackingFields() {
        yield return new LoadableBackingField("Address", () => this.dataRepository.Addresses.Get(this.Original.AddressID));
    }
}

This works perfectly fine for getting and settings property values, but I get a either a RuntimeBinderException when I implicitly cast or an InvalidCastException with an explicitly cast SelfHydratingEntity back to T.

I know that you can override the DynamicObject.TryConvert method, but I'm wondering what exactly to put in this method. I've read a lot about duck typing today, and have tried out several libraries, but none of them work for this particular scenario. All of the libraries I've tried today generate a wrapper class using Reflection.Emit that makes calls to "get_" and "set_" methods and naturally use reflection to find these methods on the wrapped instance. SelfHydratingEntity of course doesn't have the "get_" and "set_" methods defined.

So, I'm wondering if this kind of thing is even possible. Is there any way to cast an instance of SelfHydratingEntity to T? I'm looking for something like this:

var original = GetOriginalPerson();
dynamic person = new SelfHydratingPerson(new DataRepository(), original);

string name = person.Name;    // Gets property value on original
var address = person.Address; // Gets property value using LoadableBackingField registration

var iPerson = (IPerson)person;
- or -
var iPerson = DuckType.As<IPerson>(person);
+1  A: 

Have you seen this Duck Typing project. It looks pretty good. I have just found a great example from Mauricio. It uses the Windsor Castle dynamic proxy to intercept method calls

Using the code from Mauricio the following code works like a dream

class Program
{
    static void Main(string[] args)
    {
        dynamic person = new { Name = "Peter" };
        var p = DuckType.As<IPerson>(person);

        Console.WriteLine(p.Name);
    }
}

public interface IPerson
{
    string Name { get; set; }
}

public static class DuckType
{
    private static readonly ProxyGenerator generator = new ProxyGenerator();

    public static T As<T>(object o)
    {
        return generator.CreateInterfaceProxyWithoutTarget<T>(new DuckTypingInterceptor(o));
    }
}

public class DuckTypingInterceptor : IInterceptor
{
    private readonly object target;

    public DuckTypingInterceptor(object target)
    {
        this.target = target;
    }

    public void Intercept(IInvocation invocation)
    {
        var methods = target.GetType().GetMethods()
            .Where(m => m.Name == invocation.Method.Name)
            .Where(m => m.GetParameters().Length == invocation.Arguments.Length)
            .ToList();
        if (methods.Count > 1)
            throw new ApplicationException(string.Format("Ambiguous method match for '{0}'", invocation.Method.Name));
        if (methods.Count == 0)
            throw new ApplicationException(string.Format("No method '{0}' found", invocation.Method.Name));
        var method = methods[0];
        if (invocation.GenericArguments != null && invocation.GenericArguments.Length > 0)
            method = method.MakeGenericMethod(invocation.GenericArguments);
        invocation.ReturnValue = method.Invoke(target, invocation.Arguments);
    }
}
Rohan West
I used this solution with some small changes:1. Check the IInvocation.Method.Name property to see if it's a property getter or setter method; 2. If "invocation" is a property getter or setter, retrieve the property from the dynamic "target" field; 3. Otherwise, invoke the method represented by IInvocation.Method
mnero0429