Duck Typing Производное от DynamicObject

Я написал класс, который позволяет производному классу указать, какие из его свойств могут быть загружены отложенно. Код:

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, когда я неявно привожу, либо InvalidCastException с явным приведением SelfHydratingEntity обратно к T.

Я знаю, что вы можете переопределить метод 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);

person Michael Nero    schedule 28.07.2010    source источник
comment
Что происходит, когда вы пытаетесь выполнить приведение к IPerson? Не работает не очень помогает.   -  person Joe White    schedule 28.07.2010
comment
Обновленный пост, чтобы быть более конкретным   -  person Michael Nero    schedule 28.07.2010


Ответы (2)


Вы видели этот проект Duck Typing. Это выглядит довольно хорошо. Я только что нашел отличный пример из Маурисио. Он использует динамический прокси-сервер Виндзорского замка для перехвата вызовов методов.

Используя код Маурисио, следующий код работает как сон

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);
    }
}
person Rohan West    schedule 28.07.2010
comment
Я использовал это решение с некоторыми небольшими изменениями: 1. Проверьте свойство IInvocation.Method.Name, чтобы узнать, является ли оно методом получения или установки свойства; 2. Если вызов является методом получения или установки свойства, получить свойство из динамического целевого поля; 3. В противном случае вызовите метод, представленный IInvocation.Method. - person Michael Nero; 17.08.2010

импровизированный-интерфейс

Может статически приводить интерфейсы к объектам, производным от DynamicObject.

person jbtule    schedule 28.02.2011
comment
Тоже очень хороший ответ. К сожалению, на вопрос может быть только один ответ, иначе я бы также отметил ваш ответ как принятый. Большое спасибо за пост! - person Michael Nero; 04.03.2011
comment
импровизированный интерфейс теперь находится по адресу: github.com/ekonbenefits/impromptu-interface. - person David Glass; 18.11.2015