C# 将静态类型的对象展开为动态对象

C# 将静态类型的对象展开为动态对象,c#,dynamic,impromptu-interface,C#,Dynamic,Impromptu Interface,我在视图模型中使用dynamic对象,因为我发现使用Automapper之类的工具所带来的开销是不必要的,而且这种方法更加灵活和轻量级。我使用的生成器如下所示: private dynamic New = Builder.New(); private dynamic GetViewModel(Product p) { var viewModel = New.Product( id : p.Id, name : p.Name ); viewModel.AdditionalProp

我在视图模型中使用
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;
}
dynamic viewModel = new CombineDynamic(product, new ExpandoObject());
viewModel.AdditionalProperty = "additional data";
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;
    }
}
在一些情况下,“扩展”实际对象比逐个重新映射所有属性更好,类似于在JavaScript中使用
jQuery.extend()


这应该可以通过使用
ExpandoObject
结合反射和迭代所有成员来实现,但我想知道是否有更干净/整洁的解决方案。

您可以创建结合两个或多个对象的动态对象:

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);
    }
}
当您动态获取或设置属性时,它首先尝试在第一个对象上执行此操作,然后在第二个对象上执行此操作,以此类推,直到成功为止


这样做(至少)有一个奇怪的行为:例如,如果
Product
具有
Id
类型
int
的属性,则代码
viewModel.Id=“42”将成功。但是它会在
ExpandoObject
上设置属性。因此,如果在此之后尝试检索
viewModel.Id
,它将从
product.Id
返回未修改的
int

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;
}
dynamic viewModel = new CombineDynamic(product, new ExpandoObject());
viewModel.AdditionalProperty = "additional data";
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;
    }
}
这使用了
Impromptu.InvokeGet()
而不是
PropertyInfo.GetValue()
,因为
Impromptu.InvokeGet()
使用DLR,因此比使用我的测试中的反射快2.5倍。总的来说,这种方法运行速度相当快,而且几乎不存在多达10000个对象的开销


我应该注意,这对扩展其他
ExpandoObject
或类似对象不起作用,但无论如何,这应该不是必需的。

我很可能不会仅仅根据性能来做出这样的决定,但你确定
dynamic
比使用Automapper快吗?问题并不真正与性能有关,只是更轻更灵活。一次创建的视图模型从来不会超过100个。不过,这更像是一个代理,而不是我想要的。但它应该给我指出正确的方向:)是的,你可以说它是一个代理。你不喜欢那有什么?我宁愿它做一个浅拷贝到另一个对象中。但是使用公共属性上的反射应该很容易实现。是的,你可以这样做。但是为什么呢?这对我来说似乎太复杂了。