Разверните статически типизированный объект в динамический объект - PullRequest
3 голосов
/ 10 октября 2011

Я использую dynamic объекты для своих моделей представлений, так как считаю ненужными использование чего-то вроде Automapper и считаю этот подход более гибким и легким. Я использую конструктор из импровизированный интерфейс , как это:

private dynamic New = Builder.New();

private dynamic GetViewModel(Product p)
{
    var viewModel = New.Product( id : p.Id, name : p.Name );
    viewModel.AdditionalProperty = "some additional data";
    return viewModel;
}

Есть несколько сценариев, в которых «расширение» реального объекта было бы лучше, чем переназначение всех свойств одно за другим, аналогично тому, как вы это делали бы в JavaScript, используя jQuery.extend()

private dynamic GetViewModel(Product p)
{
    var viewModel = //create base dynamic object, that has all the members of p.
    viewModel.AdditionalProperty = "some additional data";
    return viewModel;
}

Этого можно достичь, используя ExpandoObject в сочетании с рефлексией и итерацией по всем элементам, но я хотел бы знать, есть ли более чистое / аккуратное решение.

Ответы [ 2 ]

4 голосов
/ 11 октября 2011

В итоге я реализовал это следующим образом:

public class ExpandedObject : DynamicObject
{
    private readonly IDictionary<string, object> expando = new ExpandoObject();

    public ExpandedObject(object o)
    {            
        foreach (var propertyInfo in o.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance))
        {
            this.expando[propertyInfo.Name] = Impromptu.InvokeGet(o, propertyInfo.Name);
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {            
        return this.expando.TryGetValue(binder.Name, out result);
    }

    public override bool  TrySetMember(SetMemberBinder binder, object value)
    {
        this.expando[binder.Name] = value;
        return true;
    }
}

и тесты:

[TestFixture]
public class ExpandedObjectTest
{
    [Test]
    public void Can_add_new_properties_to_expanded_object()
    {
        dynamic expanded = new ExpandedObject(new object());
        var data = "some additional data";
        expanded.data = data;
        Assert.AreEqual(data, expanded.data);
    }

    [Test]
    public void Copies_existing_properties()
    {            
        var obj = new { id = 5 };            
        dynamic expanded = new ExpandedObject(obj);            
        Assert.AreEqual(obj.id, expanded.id);            
    }
}

Это использует Impromptu.InvokeGet() вместо PropertyInfo.GetValue(), потому что Impromptu.InvokeGet() используетDLR и, таким образом, примерно в 2,5 раза быстрее, чем использование, чем отражение в моих тестах.В целом, это работает достаточно быстро, и накладные расходы для до 10 000 объектов практически не существуют.

Я должен отметить, что это не будет работать для расширения других ExpandoObject или подобных, но это не должно быть действительно необходимо в любом случае.1013 *

1 голос
/ 10 октября 2011

Вы можете создать динамический объект, который объединяет два или более объектов:

class CombineDynamic : DynamicObject
{
    private readonly object[] m_objects;

    public CombineDynamic(params object[] objects)
    {
        m_objects = objects;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        var callSite = CallSite<Func<CallSite, object, object>>.Create(binder);

        foreach (var o in m_objects)
        {
            try
            {
                result = callSite.Target(callSite, o);
                return true;
            }
            catch (RuntimeBinderException)
            {}
        }

        return base.TryGetMember(binder, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        // the binder from argument uses compile time type from call site,
        // which is object here; because of that, setting of properties that 
        // aren't of type object wouldn't work if we used that binder directly
        var fixedBinder = Binder.SetMember(
            CSharpBinderFlags.None, binder.Name, typeof(CombineDynamic),
            new[]
            {
                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
            });

        var callSite =
            CallSite<Action<CallSite, object, object>>.Create(fixedBinder);

        foreach (var o in m_objects)
        {
            try
            {
                callSite.Target(callSite, o, value);
                return true;
            }
            catch (RuntimeBinderException)
            {}
        }

        return base.TrySetMember(binder, value);
    }
}

И использовать его так:

dynamic viewModel = new CombineDynamic(product, new ExpandoObject());
viewModel.AdditionalProperty = "additional data";

Когда вы динамически получаете или устанавливаете свойство, оно сначалапытается сделать это на первом объекте, затем на втором и т. д. до тех пор, пока он не будет успешным.1010 * типа int, код viewModel.Id = "42"; будет успешным.Но это установит свойство на ExpandoObject.Поэтому, если вы попытаетесь получить viewModel.Id после этого, он вернет int из product.Id, который не был изменен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...