Как создать динамические свойства в С#?
Я ищу способ создания класса с набором статических свойств. Во время выполнения я хочу иметь возможность добавлять другие динамические свойства к этому объекту из базы данных. Я также хотел бы добавить функции сортировки и фильтрации для этих объектов.
Как это сделать на С#?
Ответы
Ответ 1
Вы можете использовать словарь, скажем
Dictionary<string,object> properties;
Я думаю, что в большинстве случаев, когда что-то подобное делается, это делается так.
В любом случае вы ничего не выиграете от создания "реального" свойства с помощью set и get accessors, поскольку он будет создан только во время выполнения, и вы не будете использовать его в своем коде...
Вот пример, показывающий возможную реализацию фильтрации и сортировки (без проверки ошибок):
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication1 {
class ObjectWithProperties {
Dictionary<string, object> properties = new Dictionary<string,object>();
public object this[string name] {
get {
if (properties.ContainsKey(name)){
return properties[name];
}
return null;
}
set {
properties[name] = value;
}
}
}
class Comparer<T> : IComparer<ObjectWithProperties> where T : IComparable {
string m_attributeName;
public Comparer(string attributeName){
m_attributeName = attributeName;
}
public int Compare(ObjectWithProperties x, ObjectWithProperties y) {
return ((T)x[m_attributeName]).CompareTo((T)y[m_attributeName]);
}
}
class Program {
static void Main(string[] args) {
// create some objects and fill a list
var obj1 = new ObjectWithProperties();
obj1["test"] = 100;
var obj2 = new ObjectWithProperties();
obj2["test"] = 200;
var obj3 = new ObjectWithProperties();
obj3["test"] = 150;
var objects = new List<ObjectWithProperties>(new ObjectWithProperties[]{ obj1, obj2, obj3 });
// filtering:
Console.WriteLine("Filtering:");
var filtered = from obj in objects
where (int)obj["test"] >= 150
select obj;
foreach (var obj in filtered){
Console.WriteLine(obj["test"]);
}
// sorting:
Console.WriteLine("Sorting:");
Comparer<int> c = new Comparer<int>("test");
objects.Sort(c);
foreach (var obj in objects) {
Console.WriteLine(obj["test"]);
}
}
}
}
Ответ 2
Если вам нужно это для привязки данных, вы можете сделать это с помощью пользовательской модели дескриптора... путем реализации ICustomTypeDescriptor
, TypeDescriptionProvider
и/или TypeCoverter
вы можете создать свои собственные экземпляры PropertyDescriptor
во время выполнения. Для отображения свойств используются элементы управления, такие как DataGridView
, PropertyGrid
и т.д.
Чтобы привязываться к спискам, вам нужны ITypedList
и IList
; для базовой сортировки: IBindingList
; для фильтрации и расширенной сортировки: IBindingListView
; для полной поддержки "новой строки" (DataGridView
): ICancelAddNew
(фу!).
Это много работы. DataTable
(хотя я ненавижу его) - это дешевый способ сделать то же самое. Если вам не нужна привязка данных, просто используйте хеш-таблицу; -p
Здесь простой пример - но вы можете сделать намного больше...
Ответ 3
Используйте ExpandoObject, как ViewBag в MVC 3.
Ответ 4
Создайте Hashtable под названием "Свойства" и добавьте к ней свои свойства.
Ответ 5
Я не уверен, что вы действительно хотите делать то, что, как вы говорите, хотите сделать, но это не для меня причина!
Вы не можете добавлять свойства к классу после того, как он был JITed.
Ближе всего вы можете получить динамическое создание подтипа с Reflection.Emit и скопировать существующие поля, но вам придется обновлять все ссылки на объект самостоятельно.
Вы также не сможете получить доступ к этим свойствам во время компиляции.
Что-то вроде:
public class Dynamic
{
public Dynamic Add<T>(string key, T value)
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Dynamic.dll");
TypeBuilder typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString());
typeBuilder.SetParent(this.GetType());
PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(key, PropertyAttributes.None, typeof(T), Type.EmptyTypes);
MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + key, MethodAttributes.Public, CallingConventions.HasThis, typeof(T), Type.EmptyTypes);
ILGenerator getter = getMethodBuilder.GetILGenerator();
getter.Emit(OpCodes.Ldarg_0);
getter.Emit(OpCodes.Ldstr, key);
getter.Emit(OpCodes.Callvirt, typeof(Dynamic).GetMethod("Get", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(T)));
getter.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getMethodBuilder);
Type type = typeBuilder.CreateType();
Dynamic child = (Dynamic)Activator.CreateInstance(type);
child.dictionary = this.dictionary;
dictionary.Add(key, value);
return child;
}
protected T Get<T>(string key)
{
return (T)dictionary[key];
}
private Dictionary<string, object> dictionary = new Dictionary<string,object>();
}
У меня нет VS, установленного на этой машине, поэтому дайте мне знать, есть ли массовые ошибки (ну... кроме серьезных проблем с производительностью, но я не писал спецификацию!)
Теперь вы можете использовать его:
Dynamic d = new Dynamic();
d = d.Add("MyProperty", 42);
Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));
Вы также можете использовать его как обычное свойство на языке, который поддерживает позднюю привязку (например, VB.NET)
Ответ 6
Я сделал именно это с интерфейсом ICustomTypeDescriptor и Словарем.
Реализация ICustomTypeDescriptor для динамических свойств:
Недавно у меня было требование привязать представление сетки к объекту записи, который мог бы иметь любое количество свойств, которые могут быть добавлены и удалены во время выполнения. Это должно позволить пользователю добавить новый столбец в результирующий набор для ввода дополнительного набора данных.
Это может быть достигнуто за счет наличия каждой строки данных в качестве словаря с ключом, являющимся именем свойства, а значение - строкой или классом, который может хранить значение свойства для указанной строки. Конечно, наличие объектов List of Dictionary не будет привязано к сетке. Здесь находится ICustomTypeDescriptor.
Создавая класс-оболочку для словаря и делая его привязанным к интерфейсу ICustomTypeDescriptor, поведение возвращаемых свойств для объекта может быть переопределено.
Посмотрите на реализацию класса данных "строка" ниже:
/// <summary>
/// Class to manage test result row data functions
/// </summary>
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Gets the Attributes for the object
/// </summary>
AttributeCollection ICustomTypeDescriptor.GetAttributes()
{
return new AttributeCollection(null);
}
/// <summary>
/// Gets the Class name
/// </summary>
string ICustomTypeDescriptor.GetClassName()
{
return null;
}
/// <summary>
/// Gets the component Name
/// </summary>
string ICustomTypeDescriptor.GetComponentName()
{
return null;
}
/// <summary>
/// Gets the Type Converter
/// </summary>
TypeConverter ICustomTypeDescriptor.GetConverter()
{
return null;
}
/// <summary>
/// Gets the Default Event
/// </summary>
/// <returns></returns>
EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
{
return null;
}
/// <summary>
/// Gets the Default Property
/// </summary>
PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
{
return null;
}
/// <summary>
/// Gets the Editor
/// </summary>
object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
{
return null;
}
/// <summary>
/// Gets the Events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the events
/// </summary>
EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
{
return new EventDescriptorCollection(null);
}
/// <summary>
/// Gets the properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
{
List<propertydescriptor> properties = new List<propertydescriptor>();
//Add property descriptors for each entry in the dictionary
foreach (string key in this.Keys)
{
properties.Add(new TestResultPropertyDescriptor(key));
}
//Get properties also belonging to this class also
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor oPropertyDescriptor in pdc)
{
properties.Add(oPropertyDescriptor);
}
return new PropertyDescriptorCollection(properties.ToArray());
}
/// <summary>
/// gets the Properties
/// </summary>
PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
{
return ((ICustomTypeDescriptor)this).GetProperties(null);
}
/// <summary>
/// Gets the property owner
/// </summary>
object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
Примечание. В методе GetProperties я мог бы кэшировать PropertyDescriptors после чтения для производительности, но поскольку я добавляю и удаляю столбцы во время выполнения, я всегда хочу, чтобы они были восстановлены
В методе GetProperties вы также заметите, что дескрипторы свойств, добавленные для записей словаря, имеют тип TestResultPropertyDescriptor. Это настраиваемый класс дескриптора свойств, который управляет тем, как устанавливаются и извлекаются свойства. Взгляните на реализацию ниже:
/// <summary>
/// Property Descriptor for Test Result Row Wrapper
/// </summary>
public class TestResultPropertyDescriptor : PropertyDescriptor
{
//- PROPERTIES --------------------------------------------------------------------------------------------------------------
#region Properties
/// <summary>
/// Component Type
/// </summary>
public override Type ComponentType
{
get { return typeof(Dictionary<string, TestResultValue>); }
}
/// <summary>
/// Gets whether its read only
/// </summary>
public override bool IsReadOnly
{
get { return false; }
}
/// <summary>
/// Gets the Property Type
/// </summary>
public override Type PropertyType
{
get { return typeof(string); }
}
#endregion Properties
//- CONSTRUCTOR -------------------------------------------------------------------------------------------------------------
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public TestResultPropertyDescriptor(string key)
: base(key, null)
{
}
#endregion Constructor
//- METHODS -----------------------------------------------------------------------------------------------------------------
#region Methods
/// <summary>
/// Can Reset Value
/// </summary>
public override bool CanResetValue(object component)
{
return true;
}
/// <summary>
/// Gets the Value
/// </summary>
public override object GetValue(object component)
{
return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
}
/// <summary>
/// Resets the Value
/// </summary>
public override void ResetValue(object component)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
}
/// <summary>
/// Sets the value
/// </summary>
public override void SetValue(object component, object value)
{
((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
}
/// <summary>
/// Gets whether the value should be serialized
/// </summary>
public override bool ShouldSerializeValue(object component)
{
return false;
}
#endregion Methods
//---------------------------------------------------------------------------------------------------------------------------
}
Основные свойства этого класса - GetValue и SetValue. Здесь вы можете увидеть, что компонент заносится как словарь, а значение ключа внутри него - "Установить" или "восстановить". Важно, чтобы словарь в этом классе был одним и тем же типом в классе оболочки Row, иначе кастинг завершится неудачей. Когда дескриптор создается, ключ (имя свойства) передается и используется для запроса словаря для получения правильного значения.
Взято из моего блога по адресу:
Выполнение ICustomTypeDescriptor для динамических свойств
Ответ 7
Я не уверен, что вы за причины, и даже если вы можете каким-то образом снять его с Reflection Emit (я не уверен, что вы можете), это не похоже на хорошую идею. Вероятно, лучшая идея заключается в том, чтобы иметь какой-то словарь, и вы можете переносить доступ к словарю с помощью методов в своем классе. Таким образом, вы можете хранить данные из базы данных в этом словаре, а затем извлекать их с помощью этих методов.
Ответ 8
Вы должны заглянуть в DependencyObjects, как используется WPF, они следуют аналогичной схеме, в соответствии с которой свойства могут быть назначены во время выполнения. Как упоминалось выше, это в конечном счете указывает на использование хеш-таблицы.
Еще одна полезная вещь, на которую нужно обратить внимание: CSLA.Net. Код свободно доступен и использует некоторые из принципов\шаблонов, которые, по-видимому, появляются после.
Также, если вы смотрите на сортировку и фильтрацию, я предполагаю, что вы собираетесь использовать какую-то сетку. Полезным интерфейсом для реализации является ICustomTypeDescriptor, это позволяет эффективно отменить то, что происходит, когда ваш объект получает отражение, поэтому вы можете указать отражатель на собственную внутреннюю хэш-таблицу объекта.
Ответ 9
В качестве замены некоторого кода orsogufo, потому что я недавно отправился со словарем для этой же самой проблемы, вот мой оператор []:
public string this[string key]
{
get { return properties.ContainsKey(key) ? properties[key] : null; }
set
{
if (properties.ContainsKey(key))
{
properties[key] = value;
}
else
{
properties.Add(key, value);
}
}
}
С этой реализацией установщик добавит новые пары ключ-значение, когда вы используете []=
, если они еще не существуют в словаре.
Кроме того, для меня properties
есть IDictionary
, а в конструкторах я инициализирую его new SortedDictionary<string, string>()
.
Ответ 10
Почему бы не использовать индекс с именем свойства в виде строкового значения, переданного в индекс?
Ответ 11
Если это для привязки, вы можете ссылаться на индексаторы из XAML
Text="{Binding [FullName]}"
Здесь он ссылается на индексатор класса с ключом "FullName"
Ответ 12
Не могли бы вы просто открыть класс для объекта Dictionary? Вместо того, чтобы "добавлять больше объектов к объекту", вы можете просто вставить свои данные (с некоторым идентификатором) в словарь во время выполнения.