Duck Typing DynamicObject 派生

发布于 2024-09-12 09:31:05 字数 2774 浏览 8 评论 0原文

我编写了一个类,允许派生类指定其哪些属性可以延迟加载。代码是:

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;
        }
    }
}

和一个派生类:

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

这对于获取和设置属性值来说非常有效,但是当我隐式转换时我得到一个 RuntimeBinderException ,或者当我显式转换 SelfHydratingEntity 回到 T 时得到一个 InvalidCastException 。

我知道你可以重写DynamicObject.TryConvert 方法,但我想知道这个方法中到底要放什么。我今天读了很多有关鸭子类型的内容,并尝试了几个库,但没有一个适用于这个特定的场景。我今天尝试过的所有库都使用 Reflection.Emit 生成一个包装类,该类调用“get_”和“set_”方法,并自然地使用反射在包装实例上查找这些方法。 SelfHydratingEntity 当然没有定义“get_”和“set_”方法。

所以,我想知道这种事情是否可能。有什么方法可以将 SelfHydratingEntity 的实例强制转换为 T 吗?我正在寻找这样的东西:

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);

I wrote a class that allows a derivate to specify 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);

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

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

发布评论

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

评论(2

林空鹿饮溪 2024-09-19 09:31:05

您看过这个 Duck Typing 项目吗?看起来不错。我刚刚找到了一个很好的例子 毛里西奥。它使用温莎城堡动态代理来拦截方法

调用使用Mauricio的代码,下面的代码就像梦一样

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

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);
    }
}
ぃ弥猫深巷。 2024-09-19 09:31:05

即兴界面
https://github.com/ekonbenefits/impromptu-interface

可以将接口静态转换到派生的对象上动态对象。

impromptu-interface
https://github.com/ekonbenefits/impromptu-interface

Can static cast interfaces onto objects derived from DynamicObject.

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