2010-07-30 40 views
14

Esta es una pregunta teórica, ya tengo una solución a mi problema que me llevó por un camino diferente, pero creo que la pregunta aún es potencialmente interesante.¿Cómo puedo pasar una propiedad como delegado?

¿Puedo pasar las propiedades de los objetos como delegados de la misma manera que con los métodos? Por ejemplo:

Digamos que tengo un lector de datos cargado con datos, y el valor de cada campo debe pasar a las propiedades de diferentes tipos que se han comprobado para DBNull. Si el intento de conseguir un solo campo, podría escribir algo como:

if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"]; 

Pero si tengo que decir 100 campos, que se convierte en difícil de manejar muy rápidamente. Hay un par de maneras en que una llamada para hacer esto puede tener un aspecto agradable:

myClass.Property = GetDefaultOrValue<string>(rdr["field1"]); //Which incidentally is the route I took 

Lo que también puede tener un aspecto agradable como un método de extensión:

myClass.Property = rdr["field1"].GetDefaultOrValue<string>(); 

O:

SetPropertyFromDbValue<string>(myClass.Property1, rdr["field1"]); //Which is the one that I'm interested in on this theoretical level 

En el En segunda instancia, la propiedad tendría que pasar como un delegado para establecerlo.

Así que la pregunta es en dos partes:

  1. Es esto posible?
  2. ¿Qué aspecto tendría?

[Como esto es sólo teórico, respuestas en VB o C# son igualmente aceptables para mí]

Editar: Hay algunas respuestas mancha aquí. Gracias a todos.

Respuesta

11

(Adición de una segunda respuesta porque está en un enfoque completamente diferente)

Para hacer frente a su problema original, que es más sobre el deseo de una buena API para valores con nombre de mapeo en un datareader a las propiedades de su objeto, considere System.ComponentModel.TypeDescriptor - una alternativa que a menudo se pasa por alto al hacer una tarea reflexiva usted mismo.

He aquí un fragmento útil:

var properties = TypeDescriptor.GetProperties(myObject) 
    .Cast<PropertyDescriptor>() 
    .ToDictionary(pr => pr.Name); 

que crea un diccionario de los propertydescriptors de su objeto.

ahora que puedo hacer esto:

properties["Property1"].SetValue(myObject, rdr["item1"]); 

PropertyDescriptor 's método FijarValor (a diferencia de System.Reflection.PropertyInfo' s equivalente) lo hará la conversión de tipos para usted - analizar cadenas como enteros, y así sucesivamente.

Lo que es útil acerca de esto es que uno puede imaginar un enfoque atributo impulsado a iteración a través de esa colección de propiedades (PropertyDescriptor tiene un Attributes propiedad para permitirle obtener los atributos personalizados que se agregaron a la propiedad) averiguar qué valor en el lector de datos para usar; o tener un método que recibe un diccionario de nombre de propiedad - mapeo de nombre de columna que recorre y realiza todos esos conjuntos por usted.

Sospecho que un enfoque como este puede darle el atajo API que necesita de una manera que el engaño reflexivo de la expresión lambda, en este caso, no lo haga.

+0

Me encanta la implicación de esto también. Gran respuesta. – BenAlabaster

6

No, no hay nada parecido a las conversiones de grupos de métodos para las propiedades. Lo mejor que puede hacer es usar una expresión lambda para formar un Func<string> (por un captador) o un Action<string> (para un setter):

SetPropertyFromDbValue<string>(value => myClass.Property1 = value, 
           rdr["field1"]); 
+1

interesante, el lambda no había pasado por la cabeza, por supuesto que sería más difícil de manejar que mi primera declaración en este escenario específico. Sin embargo, eso es interesante, me pregunto si esto podría tomar un paso más allá de alguna manera para envolverlo ... o tal vez mi cerebro simplemente se está alejando de mí: P – BenAlabaster

+0

¿Se pueden pasar los tipos genéricos a los delegados? – BenAlabaster

+0

@BenAlabaster: No estoy seguro de lo que quieres decir con la última pregunta ... –

2

Vale la pena mencionar que puede hacer esto con algún truco reflexión .. algo como ...

public static void LoadFromReader<T>(this object source, SqlDataReader reader, string propertyName, string fieldName) 
    { 
     //Should check for nulls.. 
     Type t = source.GetType(); 
     PropertyInfo pi = t.GetProperty(propertyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); 
     object val = reader[fieldName]; 
     if (val == DBNull.Value) 
     { 
      val = default(T); 
     } 
     //Try to change to same type as property... 
     val = Convert.ChangeType(val, pi.PropertyType); 
     pi.SetValue(source, val, null); 
    } 

continuación

myClass.LoadFromReader<string>(reader,"Property1","field1"); 
+0

Pensé en la reflexión, pero es una de esas áreas que siempre me preocupa arrastrarán bajo rendimiento En consecuencia, es un nicho en el que intervengo solo en las áreas de mi aplicación que es menos probable que impacte. En áreas donde estoy cargando cientos de objetos o analizando datos en cientos de campos, me preocuparía que impida el rendimiento demasiado. – BenAlabaster

+0

Aunque le dí +1 porque responde la pregunta, simplemente no estaba del todo en línea con lo que estaba buscando (que no especifiqué en la pregunta). – BenAlabaster

14

me gusta usar árboles de expresión para resolver este problema. Siempre que tenga un método en el que desee tomar un "delegado de propiedad", use el parámetro tipo Expression<Func<T, TPropertyType>>. Por ejemplo:

public void SetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression, 
    TProperty value 
) 
{ 
    MemberExpression member = (MemberExpression)expression.Body; 
    PropertyInfo property = (PropertyInfo)member.Member; 
    property.SetValue(obj, value, null); 
} 

Lo bueno de esto es que la sintaxis se ve igual también.

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression 
) 
{ 
    MemberExpression member = (MemberExpression)expression.Body; 
    PropertyInfo property = (PropertyInfo)member.Member; 
    return (TProperty)property.GetValue(obj, null); 
} 

O, si estás perezoso:

public TProperty GetPropertyFromDbValue<T, TProperty>(
    T obj, 
    Expression<Func<T, TProperty>> expression 
) 
{ 
    return expression.Compile()(obj); 
} 

Invocación se vería así:

SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]); 
GetPropertyFromDbValue(myClass, o => o.Property1); 
+0

Esto es increíble, solo estaba en el medio de tener una idea similar inspirada por la respuesta de @JoSkeet. Estoy pensando que puedo simplificarlo un poco, pero estoy tratando de pensar sobre esto en medio de otros 1,000 cada día I.T. problemas ... como lo haces: P – BenAlabaster

+0

+1 de mi parte. Esto no compilaría, así que edité las correcciones. Espero tenerlo bien, por favor perdónenme si no. Además, esto se traduce en un gran conjunto de métodos de extensión, entre los que se incluyen 'public static PropertyInfo GetPropertyInfo (esta expresión Expression >) que pude utilizar como una de las bases para otra función de biblioteca para obtener, con genéricos, la longitud máxima de un campo de cadena de Linq a Sql. ¡Gracias! –

+1

@StephenKennedy, gracias, buena captura! SO necesita un botón de compilación. ;) –

6

Haciendo caso omiso de si esto es útil en sus circunstancias específicas (donde creo que el enfoque has tomado las obras bien), tu pregunta es '¿hay alguna manera de convertir una propiedad en un delegado'?

Bueno, podría haber algo así.

Cada propiedad en realidad (detrás de escena) consiste en uno o dos métodos: un método de configuración y/o un método get. Y puede, si puede obtener esos métodos, delegar en delegados.

Por ejemplo, una vez que tienes el asimiento de un objeto System.Reflection.PropertyInfo que representa una propiedad de tipo TProp en un objeto de tipo TObj, podemos crear un Action<TObj,TProp> (es decir, un delegado que toma un objeto sobre el que establezca la propiedad y un valor para ajustarlo a) que envuelve ese método de selección de la siguiente manera:

Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod()) 

o podemos crear una Action<TProp> que envuelve el organismo en una instancia específica de TObj así:

Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod()) 

Podemos concluir que el pequeño lote utilizando un método static reflection extensión:

public static Action<T> GetPropertySetter<TObject, T>(this TObject instance, Expression<Func<TObject, T>> propAccessExpression) 
{ 
    var memberExpression = propAccessExpression.Body as MemberExpression; 
    if (memberExpression == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression"); 

    var accessedMember = memberExpression.Member as PropertyInfo; 
    if (accessedMember == null) throw new ArgumentException("Lambda must be a simple property access", "propAccessExpression"); 

    var setter = accessedMember.GetSetMethod(); 

    return (Action<T>) Delegate.CreateDelegate(typeof(Action<T>), instance, setter); 
} 

y ahora podemos obtener una bodega de un delegado 'incubadora' de una propiedad de un objeto como éste:

MyClass myObject = new MyClass(); 
Action<string> setter = myObject.GetPropertySetter(o => o.Property1); 

Está fuertemente tipado, según el tipo de propiedad en sí, por lo que es robusto frente a la refactorización y el tipo de tiempo de compilación seleccionado.

Por supuesto, en su caso, desea poder establecer su propiedad utilizando un objeto posiblemente nulo, por lo que una envoltura fuertemente tipada alrededor del colocador no es la solución completa, pero sí le da algo que pasar a su método SetPropertyFromDbValue.

+0

Me gusta mucho. Gracias. – BenAlabaster

Cuestiones relacionadas