Estoy buscando una forma de crear una clase con un conjunto de propiedades estáticas. En tiempo de ejecución, quiero poder agregar otras propiedades dinámicas a este objeto desde la base de datos. También me gustaría agregar capacidades de clasificación y filtrado a estos objetos.
¿Cómo hago esto en C #?
Respuestas:
Podría usar un diccionario, digamos
Dictionary<string,object> properties;
Creo que en la mayoría de los casos en los que se hace algo similar, se hace así.
En cualquier caso, no ganaría nada creando una propiedad "real" con los accesos set y get, ya que se crearía solo en tiempo de ejecución y no la usaría en su código ...
A continuación, se muestra un ejemplo que muestra una posible implementación de filtrado y clasificación (sin verificación de errores):
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"]); } } } }
fuente
Si necesita esto para propósitos de enlace de datos, se puede hacer esto con un modelo de descriptor de costumbre ... mediante la implementación
ICustomTypeDescriptor
,TypeDescriptionProvider
y / oTypeCoverter
, puede crear sus propiasPropertyDescriptor
instancias en tiempo de ejecución. Esto es lo que utilizan los controles comoDataGridView
,PropertyGrid
etc. para mostrar propiedades.Para enlazar a listas, necesitaría
ITypedList
yIList
; para la clasificación básica:IBindingList
; para el filtrado y clasificación avanzada:IBindingListView
; para soporte completo de "nueva fila" (DataGridView
):ICancelAddNew
(¡uf!).Sin embargo, es mucho trabajo.
DataTable
(aunque lo odio) es una forma barata de hacer lo mismo. Si no necesita el enlace de datos, simplemente use una tabla hash ;-pAquí hay un ejemplo simple , pero puede hacer mucho más ...
fuente
Use ExpandoObject como ViewBag en MVC 3.
fuente
Crea una tabla hash llamada "Propiedades" y agrégale tus propiedades.
fuente
No estoy seguro de que realmente quieras hacer lo que dices que quieres hacer , ¡pero no me corresponde a mí razonar por qué!
No puede agregar propiedades a una clase después de que se haya modificado.
Lo más cercano que podría conseguir sería crear dinámicamente un subtipo con Reflection.Emit y copie los campos existentes, pero tendría que actualizar todas las referencias al objeto usted mismo.
Tampoco podrá acceder a esas propiedades en tiempo de compilación.
Algo como:
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>(); }
No tengo VS instalado en esta máquina, así que avíseme si hay errores masivos (bueno ... además de los problemas de rendimiento masivos, ¡pero no escribí la especificación!)
Ahora puedes usarlo:
Dynamic d = new Dynamic(); d = d.Add("MyProperty", 42); Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));
También puede usarlo como una propiedad normal en un lenguaje que admita enlaces tardíos (por ejemplo, VB.NET)
fuente
He hecho exactamente esto con una interfaz ICustomTypeDescriptor y un diccionario.
Implementación de ICustomTypeDescriptor para propiedades dinámicas:
Recientemente tuve el requisito de vincular una vista de cuadrícula a un objeto de registro que podría tener cualquier cantidad de propiedades que se pueden agregar y eliminar en tiempo de ejecución. Esto fue para permitir que un usuario agregue una nueva columna a un conjunto de resultados para ingresar un conjunto adicional de datos.
Esto se puede lograr teniendo cada 'fila' de datos como un diccionario con la clave como el nombre de la propiedad y el valor como una cadena o una clase que puede almacenar el valor de la propiedad para la fila especificada. Por supuesto, tener una lista de objetos de diccionario no podrá vincularse a una cuadrícula. Aquí es donde entra ICustomTypeDescriptor.
Al crear una clase contenedora para el diccionario y hacer que se adhiera a la interfaz ICustomTypeDescriptor, se puede anular el comportamiento para devolver propiedades para un objeto.
Eche un vistazo a la implementación de la clase de 'fila' de datos a continuación:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Nota: En el método GetProperties, pude almacenar en caché los PropertyDescriptors una vez leídos para el rendimiento, pero como estoy agregando y eliminando columnas en tiempo de ejecución, siempre quiero que se reconstruyan
También notará en el método GetProperties que los descriptores de propiedad agregados para las entradas del diccionario son del tipo TestResultPropertyDescriptor. Se trata de una clase de descriptor de propiedades personalizada que gestiona cómo se establecen y recuperan las propiedades. Eche un vistazo a la implementación a continuación:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Las principales propiedades a tener en cuenta en esta clase son GetValue y SetValue. Aquí puede ver el componente que se convierte como diccionario y el valor de la clave dentro de él se establece o se recupera. Es importante que el diccionario de esta clase sea del mismo tipo en la clase contenedora Row, de lo contrario, la conversión fallará. Cuando se crea el descriptor, se pasa la clave (nombre de propiedad) y se utiliza para consultar el diccionario para obtener el valor correcto.
Tomado de mi blog en:
Implementación de ICustomTypeDescriptor para propiedades dinámicas
fuente
Debe buscar en DependencyObjects como los usa WPF, estos siguen un patrón similar por el cual las propiedades se pueden asignar en tiempo de ejecución. Como se mencionó anteriormente, esto finalmente apunta hacia el uso de una tabla hash.
Otra cosa útil para echar un vistazo es CSLA.Net . El código está disponible gratuitamente y usa algunos de los principios \ patrones que parece que buscas.
Además, si está buscando ordenar y filtrar, supongo que usará algún tipo de cuadrícula. Una interfaz útil para implementar es ICustomTypeDescriptor, esto le permite anular efectivamente lo que sucede cuando su objeto se refleja para que pueda apuntar el reflector a la propia tabla hash interna de su objeto.
fuente
Como reemplazo de parte del código de orsogufo, debido a que recientemente usé un diccionario para este mismo problema, aquí está mi operador []:
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); } } }
Con esta implementación, el establecedor agregará nuevos pares clave-valor cuando los use
[]=
si aún no existen en el diccionario.Además, para mí
properties
es unIDictionary
y en los constructores lo inicializo ennew SortedDictionary<string, string>()
.fuente
record[name_column] = DBConvert.To<string>(r[name_column]);
dónderecord
está mi DTO. ¿Cómo obtengo este valor del lado del cliente?No estoy seguro de cuáles son sus razones, e incluso si pudiera lograrlo de alguna manera con Reflection Emit (no estoy seguro de que pueda), no parece una buena idea. Lo que probablemente sea una mejor idea es tener algún tipo de diccionario y puede ajustar el acceso al diccionario a través de métodos en su clase. De esa manera, puede almacenar los datos de la base de datos en este diccionario y luego recuperarlos usando esos métodos.
fuente
¿Por qué no utilizar un indexador con el nombre de la propiedad como un valor de cadena que se pasa al indexador?
fuente
¿No podría simplemente hacer que su clase exponga un objeto Diccionario? En lugar de "adjuntar más propiedades al objeto", puede simplemente insertar sus datos (con algún identificador) en el diccionario en tiempo de ejecución.
fuente
Si es para enlace, puede hacer referencia a indexadores desde XAML
Text="{Binding [FullName]}"
Aquí se hace referencia al indexador de clases con la clave "FullName"
fuente