【问题标题】:Exposing properties of an ExpandoObject公开 ExpandoObject 的属性
【发布时间】:2013-05-15 14:06:20
【问题描述】:

我有一个 ExpandoObject 我要发送到一个外部库方法,该方法需要一个对象。据我所知,这个外部库在内部使用 TypeDescriptor.GetProperties,这似乎会导致我的 ExpandoObject 出现一些问题。

我可以使用匿名对象,这似乎可行,但使用 ExpandoObject 对我来说更方便。

我是否需要构建自己的 DynamicObject 并通过实现 ICustomTypeDescriptor 自己处理它,还是我在这里遗漏了什么。

想法?


更新

除了下面 somedave 的答案(根据 cmets),我添加了这个类

public class ExpandoObjectTypeDescriptionProvider : TypeDescriptionProvider
{
    private static readonly TypeDescriptionProvider m_Default = TypeDescriptor.GetProvider(typeof(ExpandoObject));

    public ExpandoObjectTypeDescriptionProvider()
        :base(m_Default)
    {
    }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
    {
        var defaultDescriptor = base.GetTypeDescriptor(objectType, instance);

        return instance == null ? defaultDescriptor :
            new ExpandoObjectTypeDescriptor(instance);
    }
}

并像这样注册它:

dynamic parameters = new ExpandoObject();
TypeDescriptor.AddProvider(new ExpandoObjectTypeDescriptionProvider(), parameters);

【问题讨论】:

  • 你有房产名列表吗?
  • 不提前(编译时间)...因此是 ExpandoObject

标签: c# .net dynamic expandoobject


【解决方案1】:

实现 ICustomTypeDescriptor 实际上并不难。这是我从使用 WinForms 属性网格(使用 TypeDescriptor 和 PropertyDescriptor)所做的一些工作中改编而来的一些示例代码。诀窍是还实现一个适当的 PropertyDescriptor 类,您可以从ICustomTypeDescriptor.GetProperties() 传回该类。值得庆幸的是,ExpandoObject 通过实现IDictionary<string, object> 来动态检索它的键和值,使这变得非常简单。请记住,这可能会或可能不会正常工作(我还没有测试过),它可能不适用于具有大量嵌套属性的 ExpandoObjects。

public class ExpandoTypeDescriptor : ICustomTypeDescriptor
{
    private readonly ExpandoObject _expando;

    public ExpandoTypeDescriptor(ExpandoObject expando)
    {
        _expando = expando;
    }

    // Just use the default behavior from TypeDescriptor for most of these
    // This might need some tweaking to work correctly for ExpandoObjects though...

    public string GetComponentName()
    {
        return TypeDescriptor.GetComponentName(this, true);
    }

    public EventDescriptor GetDefaultEvent()
    {
        return TypeDescriptor.GetDefaultEvent(this, true);
    }

    public string GetClassName()
    {
        return TypeDescriptor.GetClassName(this, true);
    }

    public EventDescriptorCollection GetEvents(Attribute[] attributes)
    {
        return TypeDescriptor.GetEvents(this, attributes, true);
    }

    EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
    {
        return TypeDescriptor.GetEvents(this, true);
    }

    public TypeConverter GetConverter()
    {
        return TypeDescriptor.GetConverter(this, true);
    }

    public object GetPropertyOwner(PropertyDescriptor pd)
    {
        return _expando;
    }

    public AttributeCollection GetAttributes()
    {
        return TypeDescriptor.GetAttributes(this, true);
    }

    public object GetEditor(Type editorBaseType)
    {
        return TypeDescriptor.GetEditor(this, editorBaseType, true);
    }

    public PropertyDescriptor GetDefaultProperty()
    {
        return null;
    }

    // This is where the GetProperties() calls are
    // Ignore the Attribute for now, if it's needed support will have to be implemented
    // Should be enough for simple usage...

    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(new Attribute[0]);
    }

    public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        // This just casts the ExpandoObject to an IDictionary<string, object> to get the keys
        return new PropertyDescriptorCollection(
            ((IDictionary<string, object>)_expando).Keys
            .Select(x => new ExpandoPropertyDescriptor(((IDictionary<string, object>)_expando), x))
            .ToArray());
    }

    // A nested PropertyDescriptor class that can get and set properties of the
    // ExpandoObject dynamically at run time
    private class ExpandoPropertyDescriptor : PropertyDescriptor
    {
        private readonly IDictionary<string, object> _expando;
        private readonly string _name;

        public ExpandoPropertyDescriptor(IDictionary<string, object> expando, string name)
            : base(name, null)
        {
            _expando = expando;
            _name = name;
        }

        public override Type PropertyType
        {
            get { return _expando[_name].GetType(); }
        }

        public override void SetValue(object component, object value)
        {
            _expando[_name] = value;
        }

        public override object GetValue(object component)
        {
            return _expando[_name];
        }

        public override bool IsReadOnly
        {
            get
            {
                // You might be able to implement some better logic here
                return false;
            }
        }

        public override Type ComponentType
        {
            get { return null; }
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override void ResetValue(object component)
        {
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        public override string Category
        {
            get { return string.Empty; }
        }

        public override string Description
        {
            get { return string.Empty; }
        }
    }
}

【讨论】:

  • 这很好,但我实际上将我的 ExpandoObject 发送到外部库并且 ExpandoObj 是密封的,所以我不能从它继承。那么我该如何告诉它使用我自己的 ICustomTypeDescriptor 实现......?
  • 啊,我没有完全理解这个问题。我认为诀窍是创建一个额外的类,一个TypeDescriptionProvider 实现。它有一个方法GetTypeDescriptor()(有几个重载),它为给定的对象或类型返回一个ICustomTypeDescriptor。装配它以返回上述ExpandoTypeDescriptor 的实例。然后使用TypeDescriptor.AddProvider() 注册提供程序。我认为这应该让所有事情都一起工作。这有意义吗?
  • 我会将您的答案标记为正确答案,因为它适用于您在 cmets 中添加的内容...我将编辑我的问题以突出显示这一点。 tnx!!
【解决方案2】:

让 OP 的代码与 Marc 的 ExpandoTypeDescriptor 代码一起工作的唯一方法是修改 OP 对 GetTypeDescriptor 的调用,将返回值转换为 ExpandoObject。

public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
     var defaultDescriptor = base.GetTypeDescriptor(objectType, instance);

     return instance == null ? defaultDescriptor :
            new ExpandoTypeDescriptor((ExpandoObject)instance);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-11
    • 2011-06-23
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    相关资源
    最近更新 更多