c# - tipo - ¿Cómo puedo pasar una propiedad como delegado?
propiedad tag c# (6)
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 todavía es potencialmente interesante.
¿Puedo pasar las propiedades de los objetos como delegados de la misma manera que puedo con los métodos? Por ejemplo:
Digamos que tengo un lector de datos cargado con datos, y el valor de cada campo debe transferirse a propiedades de diferentes tipos que se han verificado para DBNull. Si intento obtener un solo campo, podría escribir algo como:
if(!rdr["field1"].Equals(DBNull.Value)) myClass.Property1 = rdr["field1"];
Pero si tengo 100 campos, eso se vuelve difícil de manejar muy rápidamente. Hay un par de maneras en que una llamada para hacer esto puede verse bien:
myClass.Property = GetDefaultOrValue<string>(rdr["field1"]); //Which incidentally is the route I took
Lo que también podría verse bien 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 la segunda instancia, la propiedad tendría que ser aprobada como un delegado para poder establecerla.
Así que la pregunta es en dos partes:
- es posible?
- Como se veria eso?
[Como esto es solo teórico, las respuestas en VB o C # son igualmente aceptables para mí]
Edición: hay algunas respuestas ingeniosas aquí. Gracias a todos.
(Añadiendo una segunda respuesta porque está en un enfoque completamente diferente)
Para abordar su problema original, que es más acerca de querer una buena API para asignar valores con nombre en un datareader a las propiedades en su objeto, considere System.ComponentModel.TypeDescriptor
- una alternativa a menudo ignorada para hacer el trabajo sucio reflexivo.
Aquí hay un fragmento útil:
var properties = TypeDescriptor.GetProperties(myObject)
.Cast<PropertyDescriptor>()
.ToDictionary(pr => pr.Name);
Eso crea un diccionario de los descriptores de propiedad de su objeto.
Ahora puedo hacer esto:
properties["Property1"].SetValue(myObject, rdr["item1"]);
El método SetValue de PropertyDescriptor (a diferencia del equivalente de System.Reflection.PropertyInfo
) realizará la conversión de tipos por usted: analizará las cadenas como ints, y así sucesivamente.
Lo que es útil acerca de esto es que uno puede imaginar un enfoque basado en atributos para iterar a través de esa colección de propiedades ( PropertyDescriptor
tiene una propiedad de Attributes
para permitirle obtener cualquier atributo personalizado que se haya agregado a la propiedad) para determinar qué valor en el datareader usar; o tener un método que recibe un diccionario de propiedades de nombre - asignaciones de nombre de columna que recorre y realiza todos esos conjuntos para usted.
Sospecho que un enfoque como este puede proporcionarle el acceso directo a la API que necesita de una manera que el engaño reflexivo de lambda-expresión, en este caso, no lo hará.
Como han señalado otros, la reflexión estática es el camino a seguir.
Esas clases funcionan fuera de la caja:
http://www.codeproject.com/Articles/36262/Getting-Fun-with-Net-Static-Reflection.aspx
Ignorando si esto es útil en sus circunstancias específicas (donde creo que el enfoque que ha tomado funciona bien), su pregunta es "¿existe una manera de convertir una propiedad en un delegado?".
Bueno, podría haber algo así.
Cada propiedad en realidad (detrás de la escena) consta de uno o dos métodos: un método de configuración y / o un método de obtención. Y puede, si puede obtener estos métodos, hacer delegados que los envuelvan.
Por ejemplo, una vez que haya obtenido un objeto System.Reflection.PropertyInfo
que representa una propiedad de tipo TProp
en un objeto de tipo TObj
, podemos crear una Action<TObj,TProp>
(es decir, un delegado que toma un objeto en el que se establece la propiedad y un valor para configurarlo en) que ajusta ese método de establecimiento de la siguiente manera:
Delegate.CreateDelegate(typeof (Action<TObj, TProp>), propertyInfo.GetSetMethod())
O podemos crear una Action<TProp>
que envuelva el configurador en una instancia específica de TObj
como esta:
Delegate.CreateDelegate(typeof (Action<TProp>), instance, propertyInfo.GetSetMethod())
Podemos envolver ese pequeño lote utilizando un método de extensión de reflexión estática :
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 puedo conseguir un delegado ''setter'' para una propiedad en un objeto como este:
MyClass myObject = new MyClass();
Action<string> setter = myObject.GetPropertySetter(o => o.Property1);
Eso está fuertemente tipado, basado en el tipo de la propiedad en sí, por lo que es robusto frente a la refactorización y la verificación de tiempo de compilación.
Por supuesto, en su caso, desea poder configurar su propiedad utilizando un objeto posiblemente nulo, por lo que una envoltura fuertemente tipada alrededor del establecedor no es la solución completa, pero le brinda algo para pasar a su método SetPropertyFromDbValue
.
Me gusta usar árboles de expresiones para resolver este problema. Siempre que tenga un método en el que desee tomar un "delegado de propiedad", use el tipo de parámetro 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 parece ser la misma que la de getta.
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 te sientes perezoso:
public TProperty GetPropertyFromDbValue<T, TProperty>(
T obj,
Expression<Func<T, TProperty>> expression
)
{
return expression.Compile()(obj);
}
La invocación se vería así:
SetPropertyFromDbValue(myClass, o => o.Property1, reader["field1"]);
GetPropertyFromDbValue(myClass, o => o.Property1);
No, no hay nada parecido a las conversiones de grupos de métodos para las propiedades. Lo mejor que puedes hacer es usar una expresión lambda para formar una Func<string>
(para un captador) o una Action<string>
(para un definidor):
SetPropertyFromDbValue<string>(value => myClass.Property1 = value,
rdr["field1"]);
Vale la pena mencionar que puedes hacer esto con algunos trucos de reflexión ... algo así 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);
}
entonces
myClass.LoadFromReader<string>(reader,"Property1","field1");