将静态类型对象扩展为动态对象

发布于 2024-12-09 02:59:42 字数 838 浏览 0 评论 0原文

我在视图模型中使用动态对象,因为我发现使用 Automapper 之类的东西不必要的开销,并且发现这种方法更加灵活和轻量。我正在使用 impromptu-interface 中的构建器,如下所示:

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 结合反射并迭代所有成员,但我想知道是否有更干净/整洁的解决方案。

I am using dynamic objects for my view models, as I find the overhead from using something like Automapper unnecessary and find this approach a lot more flexible and lightweight. I am using the builder from impromptu-interface like this:

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

There are a few scenarios where "expanding" the actual object would be better then remapping all the properties one by one, similar to how you would do in JavaScript using 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;
}

This should be achievable using ExpandoObject combined with reflection and iterating through all the members, but I would like to know if there's a cleaner/neater solution.

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

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

发布评论

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

评论(2

っ左 2024-12-16 02:59:42

我最终像这样实现它:

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 或类似对象不起作用,但这实际上并不是必要的。

I ended up implementing it like this:

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

and the tests:

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

This makes use of Impromptu.InvokeGet() instead of PropertyInfo.GetValue() because Impromptu.InvokeGet() uses the DLR and as such about 2.5x faster than using than reflection from my tests. Overall this works reasonably fast and the overhead for upto 10,000 objects is almost nonexistant.

I should note that this won't work to expand other ExpandoObject or similar, but this should not really be necessary anyway.

甜警司 2024-12-16 02:59:42

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

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

当您动态获取或设置属性时,它首先尝试在第一个对象上执行此操作,然后在第二个对象上执行此操作,依此类推,直到成功为止。

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

You could create dynamic object that combines two or more objects:

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

And use it like this:

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

When you get or set a property dynamically, it first tries to do that on the first object, then on the second etc., until it succeeds.

Doing it like this has (at least) one weird behavior: If, for example, Product had property Id of type int, the code viewModel.Id = "42"; would succeed. But it would set the property on the ExpandoObject. So if you tried to retrieve viewModel.Id after that, it would return the int from product.Id, which wasn't modified.

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