2011-05-04 12 views
9

Próbuję uzyskać dostęp do różnych części zagnieżdżonej struktury klas, używając dowolnego ciągu znaków .Właściwość odnośnika na wykresie obiektu za pomocą ciągu znaków

Przy następujących klas (wymyślonych):

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

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

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

które chciałbym, aby móc uzyskać obiektu w "PersonsAddress.HousePhone.Number" z instancji obiektu Person.

Obecnie robię kilka funky rekursywne wyszukiwanie za pomocą refleksji, ale mam nadzieję, że niektórzy ninja tam mają jakieś lepsze pomysły.

Dla porównania, tutaj jest metoda (bzdura) Mam rozwinięte:

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

Dlaczego uważasz, że musisz to zrobić? –

+0

@Steve - Ponieważ potrzebuję kontrolować rzutowanie dowolnych typów, a konfiguracja jest najlepszym miejscem do tego. – Khanzor

+0

Jest to również przydatne do implementacji ogólnego mechanizmu powiązania danych - właściwość DataMember BindingSource akceptuje taki ciąg ścieżki nawigacji. – ducu

Odpowiedz

13

Mogłeś po prostu użyć standardowego .NET DataBinder.Eval Method coś takiego:

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

Jest to prawdopodobnie bliższe podejściu bez kodu, którego szukałem! – Khanzor

+0

@Khanzor, cóż, to jest dokładnie to samo co twoja metoda, nadal nie mogę zrozumieć, że masz już działającą odpowiedź, jakiej alternatywy szukasz? Pod względem wydajności lub czegokolwiek innego? Refleksja jest jedynym sposobem, w przeciwnym razie istnieje inna alternatywa generowania dynamicznej metody i używania jej, ale zbyt wiele kodu dla małego problemu. –

+1

Po prostu zwróć uwagę, że będziesz musiał odwołać się do - ** System.Web.dll ** – Maxim

3

Oto nierekursywnych wersja z (prawie) same semantyki:

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

Dlaczego implementacja nierekurencyjna jest lepsza? Metoda GetProperty jest jednak dobrą wskazówką. – Khanzor

+1

@Khanzor: Recursion uniemożliwia nam użycie naturalnego iteratora foreach na przychodzącym IEnumerable. Po to jest foreach! –

+0

Cóż, myślę, że używam rekurencji dla samej rekursji. Jednak nie wydaje mi się, że tak źle jest z listami. Rozumiem jednak, że CLR nie wykonuje rekursji ogonowej, więc sensowniej jest implementować za pomocą foreach :). – Khanzor

4

mam w przeszłości coś podobnego. Poszedłem z podejściem lambda, ponieważ po ich skompilowaniu mogę je buforować. Usunąłem buforowanie w tym kodzie.

Dołączyłem kilka testów jednostkowych, aby pokazać użycie tej metody. Mam nadzieję, że to będzie pomocne.

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

Dlaczego używanie lambdy jest dobrym pomysłem a refleksją? – Khanzor

+0

Największym powodem jest to, że wyrażenia używają AST właściwości, których odbicie nie ma. Jedynym szybszym sposobem na to byłoby użycie Reflection.Emit i napisanie go przy pomocy IL, ale wydaje się, że byłoby to więcej kłopotów niż jest warte. Zwróć uwagę, że jeśli możesz skasować delegata wygenerowanego przez krok .Compile(), pomoże to w utrzymaniu niskiego czasu wykonywania. Aby odzyskać delegata, którego chcesz wywołać, musisz skorzystać z właściwości, a także typu pamięci podręcznej. –

+0

Myślę, że ten post może pomóc ci to wyjaśnić, ponieważ nie jestem pewien, czy wykonałem dobrą robotę. http://stackoverflow.com/questions/2697655/lambda-expression-based-reflection-vs-normal-reflection –

1

Skoro już jesteś zainteresowany w rozwiązywaniu ścieżki własności ciąg, można skorzystać z patrząc w bibliotece Dynamic LINQ zapytań pisał jako przykład autorstwa Scotta Guthrie @ Microsoft. Analizuje twoje wyrażenia łańcuchowe i generuje drzewa ekspresowe, które mogą być kompilowane i buforowane zgodnie z sugestią @Brian Dishaw.

Zapewni to bogactwo dodatkowych opcji, zapewniając prostą i niezawodną składnię wyrażeń, której można użyć w podejściu konfiguracyjnym. Obsługuje typowe metody LINQ na wyliczeniach, a także prostą logikę operatora, obliczenia matematyczne, ocenę ścieżki własności, itp.

Powiązane problemy