Dados los siguientes objetos:
public class Customer {
public String Name { get; set; }
public String Address { get; set; }
}
public class Invoice {
public String ID { get; set; }
public DateTime Date { get; set; }
public Customer BillTo { get; set; }
}
Me gustaría usar la reflexión para pasar por el Invoice
y obtener la Name
propiedad de a Customer
. Esto es lo que busco, asumiendo que este código funcionaría:
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address");
Object val = info.GetValue(inv, null);
Por supuesto, esto falla ya que "BillTo.Address" no es una propiedad válida de la Invoice
clase.
Entonces, intenté escribir un método para dividir la cadena en pedazos en el punto y caminar sobre los objetos buscando el valor final que me interesaba. Funciona bien, pero no me siento del todo cómodo con él:
public Object GetPropValue(String name, Object obj) {
foreach (String part in name.Split('.')) {
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
¿Alguna idea sobre cómo mejorar este método o una mejor manera de resolver este problema?
EDITAR después de publicar, vi algunas publicaciones relacionadas ... Sin embargo, no parece haber una respuesta que aborde específicamente esta pregunta. Además, aún me gustaría recibir comentarios sobre mi implementación.
fuente
GetDesiredInvoice
te devuelve un objeto de tipo,Invoice
¿por qué no usarloinv.BillTo.Name
directamente?Respuestas:
De hecho, creo que tu lógica está bien. Personalmente, probablemente lo cambiaría para que pase el objeto como primer parámetro (que está más en línea con PropertyInfo.GetValue, por lo que es menos sorprendente).
También probablemente lo llamaría algo más como GetNestedPropertyValue, para que sea obvio que busca en la pila de propiedades.
fuente
Object
clase, lo que refuerza el punto de reordenar los parámetros.Utilizo el siguiente método para obtener los valores de las propiedades (clases anidadas) como
"Propiedad"
"Nombre de la calle"
"Dirección.País.Nombre"
public static object GetPropertyValue(object src, string propName) { if (src == null) throw new ArgumentException("Value cannot be null.", "src"); if (propName == null) throw new ArgumentException("Value cannot be null.", "propName"); if(propName.Contains("."))//complex type nested { var temp = propName.Split(new char[] { '.' }, 2); return GetPropertyValue(GetPropertyValue(src, temp[0]), temp[1]); } else { var prop = src.GetType().GetProperty(propName); return prop != null ? prop.GetValue(src, null) : null; } }
Aquí está el violín: https://dotnetfiddle.net/PvKRH0
fuente
Sé que llego un poco tarde a la fiesta y, como dijeron otros, su implementación está bien
... para casos de uso simples .
Sin embargo, he desarrollado una biblioteca que resuelve exactamente ese caso de uso, Pather.CSharp .
También está disponible como paquete Nuget .
Su clase principal es
Resolver
con suResolve
método.Le pasa un objeto y la ruta de la propiedad , y devolverá el valor deseado .
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice var resolver = new Resolver(); object result = resolver.Resolve(inv, "BillTo.Address");
Pero también puede resolver rutas de propiedades más complejas , incluido el acceso a matrices y diccionarios.
Así, por ejemplo, si su
Customer
tenían varias direccionespublic class Customer { public String Name { get; set; } public IEnumerable<String> Addresses { get; set; } }
puede acceder al segundo usando
Addresses[1]
.Invoice inv = GetDesiredInvoice(); // magic method to get an invoice var resolver = new Resolver(); object result = resolver.Resolve(inv, "BillTo.Addresses[1]");
fuente
Tienes que acceder al objeto REAL en el que necesitas usar la reflexión. Esto es lo que quiero decir:
En lugar de esto:
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice PropertyInfo info = inv.GetType().GetProperty("BillTo.Address"); Object val = info.GetValue(inv, null);
Haga esto (editado según el comentario):
Invoice inv = GetDesiredInvoice(); // magic method to get an invoice PropertyInfo info = inv.GetType().GetProperty("BillTo"); Customer cust = (Customer)info.GetValue(inv, null); PropertyInfo info2 = cust.GetType().GetProperty("Address"); Object val = info2.GetValue(cust, null);
Mire esta publicación para obtener más información: Uso de la reflexión para establecer una propiedad de una propiedad de un objeto
fuente
Con la esperanza de no sonar demasiado tarde para la fiesta, me gustaría agregar mi solución: definitivamente use la recursividad en esta situación
public static Object GetPropValue(String name, object obj, Type type) { var parts = name.Split('.').ToList(); var currentPart = parts[0]; PropertyInfo info = type.GetProperty(currentPart); if (info == null) { return null; } if (name.IndexOf(".") > -1) { parts.Remove(currentPart); return GetPropValue(String.Join(".", parts), info.GetValue(obj, null), info.PropertyType); } else { return info.GetValue(obj, null).ToString(); } }
fuente
No explica la fuente de su "incomodidad", pero su código básicamente me parece correcto.
Lo único que cuestionaría es el manejo de errores. Devuelve nulo si el código intenta atravesar una referencia nula o si el nombre de la propiedad no existe. Esto oculta errores: es difícil saber si devolvió nulo porque no hay un cliente BillTo, o porque lo escribió mal "BilTo.Address" ... o porque hay un cliente BillTo y su dirección es nula. Dejaría que el método se bloquee y se queme en estos casos, solo dejaría escapar la excepción (o tal vez envolverla en una más amigable).
fuente
Aquí hay otra implementación que omitirá una propiedad anidada si es un enumerador y continuará más profundamente. Las propiedades del tipo cadena no se ven afectadas por la comprobación de enumeración.
public static class ReflectionMethods { public static bool IsNonStringEnumerable(this PropertyInfo pi) { return pi != null && pi.PropertyType.IsNonStringEnumerable(); } public static bool IsNonStringEnumerable(this object instance) { return instance != null && instance.GetType().IsNonStringEnumerable(); } public static bool IsNonStringEnumerable(this Type type) { if (type == null || type == typeof(string)) return false; return typeof(IEnumerable).IsAssignableFrom(type); } public static Object GetPropValue(String name, Object obj) { foreach (String part in name.Split('.')) { if (obj == null) { return null; } if (obj.IsNonStringEnumerable()) { var toEnumerable = (IEnumerable)obj; var iterator = toEnumerable.GetEnumerator(); if (!iterator.MoveNext()) { return null; } obj = iterator.Current; } Type type = obj.GetType(); PropertyInfo info = type.GetProperty(part); if (info == null) { return null; } obj = info.GetValue(obj, null); } return obj; } }
basado en esta pregunta y en
Utilizo esto en un proyecto MVC para ordenar dinámicamente mis datos simplemente pasando la propiedad para ordenar por ejemplo:
result = result.OrderBy((s) => { return ReflectionMethods.GetPropValue("BookingItems.EventId", s); }).ToList();
donde BookingItems es una lista de objetos.
fuente
private static System.Reflection.PropertyInfo GetProperty(object t, string PropertName) { if (t.GetType().GetProperties().Count(p => p.Name == PropertName.Split('.')[0]) == 0) throw new ArgumentNullException(string.Format("Property {0}, is not exists in object {1}", PropertName, t.ToString())); if (PropertName.Split('.').Length == 1) return t.GetType().GetProperty(PropertName); else return GetProperty(t.GetType().GetProperty(PropertName.Split('.')[0]).GetValue(t, null), PropertName.Split('.')[1]); }
fuente
if (info == null) { /* throw exception instead*/ }
De hecho, lanzaría una excepción si solicitan una propiedad que no existe. De la forma en que lo tiene codificado, si llamo a GetPropValue y devuelve nulo, no sé si eso significa que la propiedad no existía, o que la propiedad existía pero su valor era nulo.
fuente
public static string GetObjectPropertyValue(object obj, string propertyName) { bool propertyHasDot = propertyName.IndexOf(".") > -1; string firstPartBeforeDot; string nextParts = ""; if (!propertyHasDot) firstPartBeforeDot = propertyName.ToLower(); else { firstPartBeforeDot = propertyName.Substring(0, propertyName.IndexOf(".")).ToLower(); nextParts = propertyName.Substring(propertyName.IndexOf(".") + 1); } foreach (var property in obj.GetType().GetProperties()) if (property.Name.ToLower() == firstPartBeforeDot) if (!propertyHasDot) if (property.GetValue(obj, null) != null) return property.GetValue(obj, null).ToString(); else return DefaultValue(property.GetValue(obj, null), propertyName).ToString(); else return GetObjectPropertyValue(property.GetValue(obj, null), nextParts); throw new Exception("Property '" + propertyName.ToString() + "' not found in object '" + obj.ToString() + "'"); }
fuente
Quería compartir mi solución, aunque puede que sea demasiado tarde. Esta solución es principalmente para verificar si existe la propiedad anidada. Pero se puede modificar fácilmente para devolver el valor de la propiedad si es necesario.
private static PropertyInfo _GetPropertyInfo(Type type, string propertyName) { //*** //*** Check if the property name is a complex nested type //*** if (propertyName.Contains(".")) { //*** //*** Get the first property name of the complex type //*** var tempPropertyName = propertyName.Split(".", 2); //*** //*** Check if the property exists in the type //*** var prop = _GetPropertyInfo(type, tempPropertyName[0]); if (prop != null) { //*** //*** Drill down to check if the nested property exists in the complex type //*** return _GetPropertyInfo(prop.PropertyType, tempPropertyName[1]); } else { return null; } } else { return type.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); } }
Tuve que referirme a algunas publicaciones para encontrar esta solución. Creo que esto funcionará para múltiples tipos de propiedades anidadas.
fuente
Mi conexión a Internet se cayó cuando necesito resolver el mismo problema, así que tuve que 'reinventar la rueda':
static object GetPropertyValue(Object fromObject, string propertyName) { Type objectType = fromObject.GetType(); PropertyInfo propInfo = objectType.GetProperty(propertyName); if (propInfo == null && propertyName.Contains('.')) { string firstProp = propertyName.Substring(0, propertyName.IndexOf('.')); propInfo = objectType.GetProperty(firstProp); if (propInfo == null)//property name is invalid { throw new ArgumentException(String.Format("Property {0} is not a valid property of {1}.", firstProp, fromObject.GetType().ToString())); } return GetPropertyValue(propInfo.GetValue(fromObject, null), propertyName.Substring(propertyName.IndexOf('.') + 1)); } else { return propInfo.GetValue(fromObject, null); } }
Estoy bastante seguro de que esto resuelve el problema para cualquier cadena que use para el nombre de la propiedad, independientemente del grado de anidación, siempre que todo sea una propiedad.
fuente
Tratar
inv.GetType().GetProperty("BillTo+Address");
fuente