Ищу способ создать класс с набором статических свойств. Во время выполнения я хочу иметь возможность добавлять к этому объекту другие динамические свойства из базы данных. Я также хотел бы добавить к этим объектам возможности сортировки и фильтрации.
Как мне это сделать на C #?
Ответы:
Вы можете использовать словарь, скажем
Dictionary<string,object> properties;
Я думаю, что в большинстве случаев, когда делается что-то подобное, это делается так.
В любом случае, вы ничего не получите от создания «реального» свойства с помощью аксессуаров set и get, поскольку оно будет создано только во время выполнения, и вы не будете использовать его в своем коде ...
Вот пример, показывающий возможную реализацию фильтрации и сортировки (без проверки ошибок):
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"]); } } } }
источник
Если вам это нужно для целей привязки данных, вы можете сделать это с помощью пользовательской модели дескриптора ... путем реализации
ICustomTypeDescriptor
,TypeDescriptionProvider
и / илиTypeCoverter
, вы можете создавать свои собственныеPropertyDescriptor
экземпляры во время выполнения. Это то , что контроль нравитсяDataGridView
, иPropertyGrid
т.д. использовать для отображения свойств.Для привязки к спискам вам потребуются
ITypedList
иIList
; для базовой сортировки:IBindingList
; для фильтрации и передовой сортировки:IBindingListView
; для полной поддержки "новой строки" (DataGridView
):ICancelAddNew
(уф!).Хотя это большая работа.
DataTable
(хотя я ненавижу это) - дешевый способ сделать то же самое. Если вам не нужна привязка данных, просто используйте хеш-таблицу ;-pВот простой пример - но вы можете сделать гораздо больше ...
источник
Используйте ExpandoObject как ViewBag в MVC 3.
источник
Создайте хеш-таблицу под названием «Свойства» и добавьте к ней свои свойства.
источник
Я не уверен, что вы действительно хотите делать то, что говорите , но не мне объяснять, почему!
Вы не можете добавлять свойства к классу после того, как он был 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).
источник
Я сделал именно это с помощью интерфейса 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 Реализация для динамических свойств
источник
Вам следует изучить DependencyObjects, используемые WPF, они следуют аналогичному шаблону, в соответствии с которым свойства могут быть назначены во время выполнения. Как упоминалось выше, это в конечном итоге указывает на использование хеш-таблицы.
Еще одна полезная вещь, на которую стоит обратить внимание, - это CSLA.Net . Код находится в свободном доступе и использует некоторые принципы \ шаблоны, которые, похоже, вам нужны.
Также, если вы смотрите на сортировку и фильтрацию, я предполагаю, что вы собираетесь использовать какую-то сетку. Полезным интерфейсом для реализации является ICustomTypeDescriptor, который позволяет вам эффективно переопределить то, что происходит, когда ваш объект отражается, чтобы вы могли указать отражателем на собственную внутреннюю хеш-таблицу вашего объекта.
источник
В качестве замены некоторого кода 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>()
.источник
record[name_column] = DBConvert.To<string>(r[name_column]);
гдеrecord
находится мой DTO. Как мне получить это значение на стороне клиента?Я не уверен, каковы ваши причины, и даже если бы вы могли как-то справиться с этим с помощью Reflection Emit (я не уверен, что вы сможете), это не похоже на хорошую идею. Вероятно, лучшая идея - это иметь какой-то словарь, и вы можете обернуть доступ к словарю через методы своего класса. Таким образом, вы можете хранить данные из базы данных в этом словаре, а затем извлекать их с помощью этих методов.
источник
Почему бы не использовать индексатор с именем свойства в качестве строкового значения, передаваемого в индексатор?
источник
Не могли бы вы просто предоставить вашему классу объект Dictionary? Вместо «добавления дополнительных свойств к объекту» вы можете просто вставить свои данные (с некоторым идентификатором) в словарь во время выполнения.
источник
Если это для привязки, вы можете ссылаться на индексаторы из XAML
Text="{Binding [FullName]}"
Здесь он ссылается на индексатор класса с ключом "FullName"
источник