2011-05-04 13 views
9

Sto cercando di accedere a varie parti di una struttura di classe nidificata utilizzando un arbitrario stringa .proprietà Ricerca in oggetto grafico tramite una stringa

Dati i seguenti (artificiosa) classi:

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

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

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

mi piacerebbe essere in grado di ottenere l'oggetto "PersonsAddress.HousePhone.Number" da un'istanza dell'oggetto Person.

Attualmente sto facendo un po 'di ricerca ricorsiva funky utilizzando la riflessione, ma spero che alcuni ninja là fuori hanno alcune idee migliori.

Per avere un riferimento, qui è il metodo (scadente) ho sviluppato:

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

Perché pensi di aver bisogno di fare questo? –

+0

@Steve - Perché ho bisogno di controllare la proiezione di tipi arbitrari e la configurazione è il posto migliore per questo. – Khanzor

+0

Ciò è utile anche per l'implementazione di un meccanismo di associazione dati generico: la proprietà DataMember di BindingSource accetta una stringa del percorso di navigazione del genere. – ducu

risposta

13

Si potrebbe semplicemente utilizzare lo standard .NET DataBinder.Eval Method, in questo modo:

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

Questo è probabilmente più vicino all'approccio no-code che stavo cercando! – Khanzor

+0

@Khanzor, beh, è ​​esattamente lo stesso del tuo metodo, non riesco ancora a capire che hai già una risposta funzionante, quale alternativa stai cercando? In termini di prestazioni o qualcos'altro? La riflessione è l'unico modo, altrimenti c'è un'altra alternativa di generare un metodo dinamico e usarlo, ma è troppo di codifica per piccoli problemi. –

+1

Ricorda che dovrai fare riferimento a - ** System.Web.dll ** – Maxim

3

Ecco una versione non ricorsiva con (quasi) la stessa semantica:

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

Perché un'implementazione non ricorsiva è necessariamente migliore? Il metodo GetProperty è comunque un buon consiglio. – Khanzor

+1

@Khanzor: ricorsione ci impedisce usando l'iteratore foreach naturale sul IEnumerable entrante. Questo è ciò per cui è foreach! –

+0

beh, immagino di usare solo la ricorsione per motivi di ricorsione. Non c'è davvero molto male con la lista chomping però, non credo. Prendo il tuo punto di vista, il CLR non esegue la ricorsione in coda, quindi ha più senso implementare l'uso di foreach :). – Khanzor

4

ho dovuto qualcosa di simile in passato. Sono andato con l'approccio lambda perché dopo averli compilati posso memorizzarli nella cache. Ho rimosso la cache in questo codice.

ho inserito un paio di unit test per mostrare l'utilizzo del metodo. Spero che questo sia utile.

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

Perché usare un lambda è una buona idea rispetto alla riflessione? – Khanzor

+0

Il motivo principale è che le espressioni utilizzano AST delle proprietà in cui la riflessione non lo fa. L'unico modo più veloce per farlo sarebbe quello di usare Reflection.Emit e scriverlo usando IL, ma sembra che sarebbe più un problema che non ne valga la pena. Si noti che se si riesce a incassare il delegato generato dal passaggio .Compile(), ciò contribuirà a mantenere basso il tempo di esecuzione. Sarà necessario sfruttare la proprietà e il tipo per la cache per recuperare il delegato che si desidera richiamare. –

+0

Penso che questo post potrebbe aiutare a spiegare ulteriormente questo perché non sono sicuro di aver fatto un buon lavoro. http://stackoverflow.com/questions/2697655/lambda-expression-based-reflection-vs-normal-reflection –

1

Dal momento che sono già interessati a risolvere i percorsi di proprietà della stringa, si può beneficiare di guardare in biblioteca Dynamic LINQ interrogazione inviato come un esempio di Scott Guthrie @ Microsoft. Analizza le espressioni di stringa e produce alberi espressi che possono essere compilati e memorizzati nella cache come suggerito da @Brian Dishaw.

Ciò fornirà una vasta gamma di opzioni aggiuntive, fornendo una sintassi semplice e robusto espressione è possibile utilizzare nel vostro approccio di configurazione. Esso supporta i comuni metodi di LINQ su enumerables, più logica operatore semplice, calcoli matematici, proprietà valutazione percorso, ecc

Problemi correlati