如何将任何C#对象转换为ExpandoObject?

26

我已经读了很多关于如何使用ExpandoObject动态创建对象的文章,它是通过添加属性来实现的。但是我还没有找到如何从已有的非动态C#对象开始进行相同的操作。

例如,我有这个简单的类:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Telephone { get; set; }
}
我想将它转换为ExpandoObject,这样我就可以根据它已经拥有的内容添加或删除属性,而不必从头开始重新构建相同的东西。这可行吗?
编辑:标记为重复的问题显然不是这个问题的重复。

也许你可以创建一个包含Person类所有属性的Dictionary<string, object>,并将其转换为动态对象?https://dev59.com/G2Uo5IYBdhLWcg3wtRbh - Jompa234
@HansPassant 我不需要调用getter和setter。就像一个简单的DTO一样,我只想将键(属性名称)和值复制到ExpandoObject中。 - Gigi
@PanagiotisKanavos 我不能假设我可以从DynamicObject继承。这个类甚至可能不是我的代码。 - Gigi
2
@PanagiotisKanavos,恕我直言,请不要给我这种“过于宽泛”的废话。我的问题非常明确,可以得到非常具体的答案。如果您对自己所说的内容有信心,可以发布一个答案。但是没有理由在问问题时过于迂腐,因为这已经成为 Stack Overflow 上的一个大问题。 - Gigi
1
正确的,那些其他的不是重复的。而且我的搜索结果首先建议了这个。所以欢迎来到 SO 的巨大缺点,它的不合理的踩票。 - oddbear
显示剩余6条评论
2个回答

41

可以这样做:

var person = new Person { Id = 1, Name = "John Doe" };

var expando = new ExpandoObject();
var dictionary = (IDictionary<string, object>)expando;

foreach (var property in person.GetType().GetProperties())
    dictionary.Add(property.Name, property.GetValue(person));

9
关于这个解决方案,我想澄清一件事情:这不是使Person成为一个可扩展的对象,而是将Person的属性复制到一个新的可扩展对象中。如果你执行expando.Id = 2,那么person.Id的值仍然是1。 - Scott Chamberlain
1
@Scott Chamberlain。是的。你说得对。 - Valerii
1
我的问题确实是如何基于现有对象的值创建ExpandoObject,所以我认为这很好。 - Gigi
4
如果你看 ExpandoObject,你会发现它实现了 IDictionary<string, object> 接口。当你在运行时观察该对象时,你可以看到 Results View,这是该字典的枚举。所以这里发生的是:你创建了一个空的 ExpandoObject,然后使用向上转型的方式手动操作其 IDictionary 表单的实例。即使看起来你只是修改了一个字典实例,但实际上是直接修改了 expando 对象。最终,你已经手动添加了属性到 expando 实例中。 - Suamere

9
你无法将一个 Person 类转换为扩展对象(expando object)。但是,你可以创建一个包含 Person 并转发所有字段的包装器 DynamicObject
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace SandboxConsole
{
    public class ExpandoWrapper : DynamicObject
    {
        private readonly object _item;
        private readonly Dictionary<string, PropertyInfo> _lookup = new Dictionary<string, PropertyInfo>(StringComparer.InvariantCulture);
        private readonly Dictionary<string, PropertyInfo> _ignoreCaseLookup = new Dictionary<string, PropertyInfo>(StringComparer.InvariantCultureIgnoreCase);

        private readonly Dictionary<string, Box> _lookupExtra = new Dictionary<string, Box>(StringComparer.InvariantCulture);
        private readonly Dictionary<string, Box> _ignoreCaseLookupExtra = new Dictionary<string, Box>(StringComparer.InvariantCultureIgnoreCase);

        private class Box
        {
            public Box(object item)
            {
                Item = item;
            }
            public object Item { get; }
        }

        public ExpandoWrapper(object item)
        {
            _item = item;
            var itemType = item.GetType();
            foreach (var propertyInfo in itemType.GetProperties())
            {
                _lookup.Add(propertyInfo.Name, propertyInfo);
                _ignoreCaseLookup.Add(propertyInfo.Name, propertyInfo);
            }
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = null;
            PropertyInfo lookup;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookup.TryGetValue(binder.Name, out lookup);
            }
            else
            {
                _lookup.TryGetValue(binder.Name, out lookup);
            }

            if (lookup != null)
            {
                result = lookup.GetValue(_item);
                return true;
            }

            Box box;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookupExtra.TryGetValue(binder.Name, out box);
            }
            else
            {
                _lookupExtra.TryGetValue(binder.Name, out box);
            }

            if (box != null)
            {
                result = box.Item;
                return true;
            }

            return false;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            PropertyInfo lookup;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookup.TryGetValue(binder.Name, out lookup);
            }
            else
            {
                _lookup.TryGetValue(binder.Name, out lookup);
            }

            if (lookup != null)
            {
                lookup.SetValue(_item, value);
                return true;
            }

            var box = new Box(value);
            _ignoreCaseLookupExtra[binder.Name] = box;
            _lookupExtra[binder.Name] = box;

            return true;
        }
    }
}

使用示例:

using System;

namespace SandboxConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person() {Id = 1};
            dynamic wrapper = new ExpandoWrapper(person);

            wrapper.Id = 2;
            wrapper.NewField = "Foo";

            Console.WriteLine(wrapper.Id);
            Console.WriteLine(person.Id);
            Console.WriteLine(wrapper.NewField);
        }
    }
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        public string Telephone { get; set; }
    }
}

我在想像这样的东西 :) - Valerii
3
使用这个示例,JsonConvert.SerializeObject(wrapper)变成了{} - Suamere
@Suamere 嗯,这有点像是一个hack的解决方法,我并不惊讶会出现错误。 - Scott Chamberlain
@ScottChamberlain,如何解决序列化问题? - MSS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接