Предоставление свойств ExpandoObject

У меня есть ExpandoObject, который я отправляю методу внешней библиотеки, который принимает объект. Из того, что я видел, эта внешняя библиотека использует TypeDescriptor.GetProperties внутри, и это, кажется, вызывает некоторые проблемы с моим ExpandoObject.

Вместо этого я мог бы использовать анонимный объект, и это, похоже, работает, но мне гораздо удобнее использовать ExpandoObject.

Нужно ли мне создать свой собственный DynamicObject и позаботиться о нем самостоятельно, внедрив ICustomTypeDescriptor, или я что-то здесь упускаю.

Идеи?


Обновить

Помимо ответа Somedave ниже (согласно комментариям), я добавил этот класс

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

person Johan Leino    schedule 15.05.2013    source источник
comment
У вас есть список имен собственности на вашем конце?   -  person Ben Reich    schedule 15.05.2013
comment
не раньше времени (время компиляции)... следовательно, ExpandoObject   -  person Johan Leino    schedule 15.05.2013


Ответы (2)


На самом деле реализовать 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; }
        }
    }
}
person daveaglick    schedule 15.05.2013
comment
Это здорово, но на самом деле я отправляю свой ExpandoObject во внешнюю библиотеку, а ExpandoObj запечатан, поэтому я не могу наследовать от него. Итак, как мне тогда сказать ему использовать мою собственную реализацию ICustomTypeDescriptor...? - person Johan Leino; 15.05.2013
comment
А, я не совсем понял проблему. Я думаю, что хитрость будет заключаться в создании одного дополнительного класса, реализации TypeDescriptionProvider. У него есть метод GetTypeDescriptor() (с несколькими перегрузками), который возвращает ICustomTypeDescriptor для данного объекта или типа. Подготовьте это, чтобы вернуть экземпляр ExpandoTypeDescriptor выше. Затем зарегистрируйте провайдера с помощью TypeDescriptor.AddProvider(). Я думаю, что это должно заставить все работать вместе. Имеет ли это смысл? - person daveaglick; 15.05.2013
comment
Я отмечу ваш ответ как правильный, поскольку он работал с тем, что вы добавили в комментариях ... Я отредактирую свой вопрос, чтобы выделить это. спасибо!! - person Johan Leino; 15.05.2013

Единственный способ, которым я мог заставить код OP работать с кодом 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);
}
person BillW    schedule 18.09.2020