2011-05-04 11 views
9

Estoy intentando acceder a varias partes de una estructura de clase anidada utilizando una cadena arbitraria .Propiedad de búsqueda en el gráfico de objetos a través de una cadena

Dadas las clases (artificiales) siguientes:

public class Person 
{ 
    public Address PersonsAddress { get; set; } 
} 

public class Adddress 
{ 
    public PhoneNumber HousePhone { get; set; } 
} 

public class PhoneNumber 
{ 
    public string Number { get; set; } 
} 

Me gustaría ser capaz de conseguir el objeto en "PersonsAddress.HousePhone.Number" desde una instancia del objeto Person.

Actualmente estoy haciendo algunas búsquedas funky recursivas usando la reflexión, pero espero que algunos ninjas tengan mejores ideas.

Para referencia, aquí es el método (cutre) que he desarrollado:

private static object ObjectFromString(object basePoint, IEnumerable<string> pathToSearch) 
{ 
    var numberOfPaths = pathToSearch.Count(); 

    if (numberOfPaths == 0) 
    return null; 

    var type = basePoint.GetType(); 
    var properties = type.GetProperties(); 

    var currentPath = pathToSearch.First(); 

    var propertyInfo = properties.FirstOrDefault(prop => prop.Name == currentPath); 

    if (propertyInfo == null) 
    return null; 

    var property = propertyInfo.GetValue(basePoint, null); 

    if (numberOfPaths == 1) 
    return property; 

    return ObjectFromString(property, pathToSearch.Skip(1)); 
} 
+0

¿Por qué crees que debes hacer esto? –

+0

@Steve - Porque necesito controlar la proyección de tipos arbitrarios, y la configuración es el mejor lugar para eso. – Khanzor

+0

Esto también es útil para implementar un mecanismo de enlace de datos genérico: la propiedad DataMember de BindingSource acepta una cadena de ruta de navegación como esa. – ducu

Respuesta

13

Usted puede simplemente utilizar el estándar .NET DataBinder.Eval Method, así:

object result = DataBinder.Eval(myPerson, "PersonsAddress.HousePhone.Number"); 
+0

¡Esto es probablemente más cercano al enfoque sin código que estaba buscando! – Khanzor

+0

@Khanzor, bueno, es exactamente lo mismo que su método, todavía no puedo entender que ya tiene una respuesta funcional, ¿qué alternativa está buscando? En términos de rendimiento o cualquier otra cosa? La reflexión es la única manera, de lo contrario hay otra alternativa para generar un método dinámico y usarlo, pero es demasiado codificar para pequeños problemas. –

+1

Solo tenga en cuenta que tendrá que hacer referencia a ** System.Web.dll ** – Maxim

3

Aquí hay una versión no recursiva con (casi) la misma semántica:

private static object ObjectFromString(object basePoint, IEnumerable<string> pathToSearch) 
{ 
    var value = basePoint; 
    foreach (var propertyName in pathToSearch) 
    { 
     var property = value.GetType().GetProperty(propertyName); 
     if (property == null) return null; 
     value = property.GetValue(value, null); 
    } 
    return value; 
} 
+0

¿Por qué una implementación no recursiva es necesariamente mejor? El método GetProperty es un buen consejo. – Khanzor

+1

@Khanzor: Recursion nos impide utilizar el iterador foreach natural en el IEnumerable entrante. ¡Para eso es Foreach! –

+0

bueno, supongo que solo estoy usando recursión por causa de la recursión. Sin embargo, no hay tanto problema con la lista de chupetes, no creo. Tomo su punto, sin embargo, el CLR no repite la cola, por lo que tiene más sentido implementar usando foreach :). – Khanzor

4

tengo tenía algo similar en el pasado. Fui con el enfoque lambda porque después de compilarlos puedo guardarlos en caché. He eliminado el almacenamiento en caché en este código.

Incluí algunas pruebas unitarias para mostrar el uso del método. Espero que esto sea útil.

private static object GetValueForPropertyOrField(object objectThatContainsPropertyName, IEnumerable<string> properties) 
    { 
    foreach (var property in properties) 
    { 
     Type typeOfCurrentObject = objectThatContainsPropertyName.GetType(); 

     var parameterExpression = Expression.Parameter(typeOfCurrentObject, "obj"); 
     Expression memberExpression = Expression.PropertyOrField(parameterExpression, property); 

     var expression = Expression.Lambda(Expression.GetDelegateType(typeOfCurrentObject, memberExpression.Type), memberExpression, parameterExpression).Compile(); 

     objectThatContainsPropertyName = expression.DynamicInvoke(objectThatContainsPropertyName); 
    } 

    return objectThatContainsPropertyName; 
    } 

    [TestMethod] 
    public void TestOneProperty() 
    { 
    var dateTime = new DateTime(); 

    var result = GetValueForPropertyOrField(dateTime, new[] { "Day" }); 

    Assert.AreEqual(dateTime.Day, result); 
    } 

    [TestMethod] 
    public void TestNestedProperties() 
    { 
    var dateTime = new DateTime(); 

    var result = GetValueForPropertyOrField(dateTime, new[] { "Date", "Day" }); 

    Assert.AreEqual(dateTime.Date.Day, result); 
    } 

    [TestMethod] 
    public void TestDifferentNestedProperties() 
    { 
    var dateTime = new DateTime(); 

    var result = GetValueForPropertyOrField(dateTime, new[] { "Date", "DayOfWeek" }); 

    Assert.AreEqual(dateTime.Date.DayOfWeek, result); 
    } 
+0

¿Por qué usar una lambda es una buena idea frente a la reflexión? – Khanzor

+0

La razón más importante es que Expressions utiliza AST de las propiedades donde reflection no lo hace. La única manera más rápida de hacer esto sería usar Reflection.Emit y escribirlo usando IL, pero parece que sería más problemático de lo que vale. Tenga en cuenta que si puede guardar el delegado generado por el paso .Compile(), ayudará a mantener el tiempo de ejecución bajo. Tendrá que aprovechar la propiedad y el tipo de caché para recuperar el delegado que desea invocar. –

+0

Creo que esta publicación podría ayudar a explicar más esto, ya que no estoy seguro de haber hecho un buen trabajo. http://stackoverflow.com/questions/2697655/lambda-expression-based-reflection-vs-normal-reflection –

1

Puesto que ya está interesado en la resolución de los caminos de propiedad cadena, se pueden beneficiar de buscar en la biblioteca de consulta publicado como Dynamic LINQ un ejemplo de Scott Guthrie @ Microsoft. Analiza las expresiones de cadena y produce árboles expresos que se pueden compilar y almacenar en caché según lo sugerido por @Brian Dishaw.

Esto le proporcionará una gran cantidad de opciones adicionales al proporcionar una sintaxis de expresión simple y robusta que puede utilizar en su enfoque de configuración. Es compatible con los métodos LINQ comunes en enumerables, además de lógica de operador simple, cálculos matemáticos, evaluación de ruta de propiedad, etc.

Cuestiones relacionadas