Я сделал именно это с помощью интерфейса ICustomTypeDescriptor и словаря.
Реализация ICustomTypeDescriptor для динамических свойств:
Недавно у меня появилось требование привязать представление сетки к объекту записи, который может иметь любое количество свойств, которые можно добавлять и удалять во время выполнения. Это должно было позволить пользователю добавить новый столбец в набор результатов для ввода дополнительного набора данных.
Этого можно достичь, используя каждую «строку» данных в качестве словаря, где ключ является именем свойства, а значение является строкой или классом, который может хранить значение свойства для указанной строки. Конечно, объекты List of Dictionary не могут быть привязаны к сетке. Здесь на помощь приходит ICustomTypeDescriptor.
Создав класс-оболочку для словаря и сделав его привязанным к интерфейсу ICustomTypeDescriptor, можно изменить поведение при возврате свойств для объекта.
Взгляните на реализацию класса строки данных ниже:
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
#region Methods
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection(null);
}
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return new EventDescriptorCollection(null);
}
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return new EventDescriptorCollection(null);
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<propertydescriptor> properties = new List<propertydescriptor>();
foreach (string key in this.Keys)
{
properties.Add(new TestResultPropertyDescriptor(key));
}
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor oPropertyDescriptor in pdc)
{
properties.Add(oPropertyDescriptor);
}
return new PropertyDescriptorCollection(properties.ToArray());
}
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion Methods
}
Примечание: в методе GetProperties я мог кэшировать дескрипторы PropertyDescriptors после чтения для повышения производительности, но поскольку я добавляю и удаляю столбцы во время выполнения, я всегда хочу, чтобы они перестраивались.
Вы также заметите в методе GetProperties, что дескрипторы свойств, добавленные для словарных статей, имеют тип TestResultPropertyDescriptor. Это настраиваемый класс дескриптора свойства, который управляет тем, как свойства устанавливаются и извлекаются. Взгляните на реализацию ниже:
public class TestResultPropertyDescriptor : PropertyDescriptor
{
#region Properties
public override Type ComponentType
{
get { return typeof(Dictionary<string, TestResultValue>); }
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
#endregion Properties
#region Constructor
public TestResultPropertyDescriptor(string key)
: base(key, null)
{
}
#endregion Constructor
#region Methods
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
}
public override void ResetValue(object component)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
}
public override void SetValue(object component, object value)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion Methods
}
Основные свойства этого класса - GetValue и SetValue. Здесь вы можете увидеть, что компонент приводится как словарь, а значение ключа внутри него устанавливается или извлекается. Важно, чтобы словарь в этом классе был того же типа, что и в классе-оболочке Row, в противном случае приведение не будет выполнено. При создании дескриптора передается ключ (имя свойства), который используется для запроса словаря для получения правильного значения.
Взято из моего блога по адресу:
ICustomTypeDescriptor Реализация для динамических свойств