2009-12-23 5 views
50

Учитывая следующие объекты:Использование отражения в C#, чтобы получить свойства вложенного объекта

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; } 
} 

я хотел бы использовать отражение, чтобы пройти через Invoice, чтобы получить Name свойство Customer. Вот что я после, если предположить, этот код будет работать:

Invoice inv = GetDesiredInvoice(); // magic method to get an invoice 
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address"); 
Object val = info.GetValue(inv, null); 

Конечно, это не удается, так как «BillTo.Address» не является допустимым свойством Invoice класса.

Итак, я попробовал написать метод разделения строки на куски на период, и ходить объекты, которые ищут конечное значение, которое меня интересует. Это работает нормально, но мне не все в порядке:

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; 
} 

Любые идеи о том, как улучшить этот метод, или лучший способ решить эту проблему?

EDIT после публикации, я видел несколько связанных должностей ... Однако, похоже, нет ответа, который конкретно решает этот вопрос. Кроме того, мне все равно нравятся отзывы о моей реализации.

+0

просто любопытно, если ваш 'GetDesiredInvoice' возвращает вам объект типа' Invoice', почему бы не использовать 'inv.BillTo.Name' напрямую? – ram

+0

Я использую это немного по-другому, просто упрощен для моего примера. Я беру объект и передаю его в процессор, который объединяет его с шаблоном для печати. – jheddings

+0

Он просто чувствовал себя немного «грубой силой» и казался, что должен быть лучший способ. Однако из ответов, похоже, я не был полностью вне базы. – jheddings

ответ

8

Я действительно думаю, что ваша логика в порядке. Лично я бы, вероятно, изменил его, так что вы передаете объект в качестве первого параметра (который больше связан с PropertyInfo.GetValue, поэтому менее неожиданным).

Я также, вероятно, назвал бы его чем-то более похожим на GetNestedPropertyValue, чтобы было очевидно, что он ищет в стеке свойств.

+0

Хороший вызов для переупорядочения параметров и предлагаемого изменения имени. – itowlson

+0

Спасибо за отзыв ... Я принял оба предложения в мою реализацию. В итоге я превратил его в метод расширения класса «Объект», который усиливает точку при переупорядочении параметров. – jheddings

+0

Почему принимаемый ответ? Это не помогает мне получить свойство вложенного объекта, как запросил ОП. – Levitikon

7

Вы должны получить доступ к объекту ACTUAL, для которого нужно использовать отражение. Вот что я имею в виду:

Вместо этого:

Invoice inv = GetDesiredInvoice(); // magic method to get an invoice 
PropertyInfo info = inv.GetType().GetProperty("BillTo.Address"); 
Object val = info.GetValue(inv, null); 

ли это (отредактированный на основе комментариев):

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); 

Посмотрите на эту должность для получения дополнительной информации: Using reflection to set a property of a property of an object

+0

Спасибо за ответ ... Я знаю, как получить значение свойства первого уровня, но мне было интересно, как получить вложенный. В моем фактическом приложении у меня нет доступа к фактическому объекту. – jheddings

+1

Нужно напрямую получать вложенные свойства. Я также в ситуации, когда «Счет-фактура» есть T, и у меня есть строка пути «Property.Property.Property». Невозможно возиться с каждым имуществом. – Levitikon

6

Вы не объясняете источник своего «дискомфорта», но ваш код в основном выглядит для меня звуком.

Единственное, что я задал бы, это обработка ошибок. Вы возвращаете значение null, если код пытается пройти через нулевую ссылку или если имя свойства не существует. Это скрывает ошибки: трудно узнать, вернули ли они null, потому что нет клиента BillTo, или потому, что вы написали «BilTo.Address» ... или потому, что есть клиент BillTo, а его адрес недействителен! Я бы позволил методу сбой и записать в этих случаях - просто позвольте исключению (или, возможно, оберните его в более дружественный).

1
if (info == null) { /* throw exception instead*/ } 

Я бы выбрал исключение, если они запрашивают свойство, которого не существует. То, как вы его закодировали, если я вызываю GetPropValue, и он возвращает null, я не знаю, означает ли это, что свойство не существует или свойство существует, но оно имеет значение null.

+0

Кроме того, переместите проверку для obj, равную нулю вне цикла. –

+0

Извините, не видел повторного использования obj. Неправильная практика программирования для изменения ваших параметров, это может привести к путанице в будущем. Используйте другую переменную для параметра obj для перемещения внутри цикла. –

+0

Кевин: Чтобы использовать другую переменную, ему придется либо назначить его obj в конце, либо сделать метод рекурсивным. Лично я не думаю, что это проблема (хотя хороший комментарий будет приятным ...) –

-7

Попробуйте inv.GetType().GetProperty("BillTo+Address");

0

с подключением к Интернету было вниз, когда мне нужно решить ту же проблему, так что мне пришлось «изобретать колесо»:

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); 
    } 
} 

Довольно уверен, что это решает проблему любая строка, которую вы используете для имени свойства, независимо от степени вложенности, если все свойство.

5

В надежде не представляясь слишком поздно, чтобы партии, я хотел бы добавить свое решение: Определенно использовать рекурсию в этой ситуации

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(); 
     } 
    } 
1
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() + "'"); 
    } 
+1

Опишите, на чем основана ваша система sulution, как это поможет OP - хорошая оценка. – DontVoteMeDown

2
> Get Nest properties e.g., Developer.Project.Name 
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]); 
      } 
4

Я использую этот метод для получения значений от таких свойств, как

"Недвижимость"

"Address.Street"

"Address.Country.Name"

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; 
     } 
    } 

Здесь скрипку: https://dotnetfiddle.net/PvKRH0

+0

Если свойство равно null, это не сработает, необходимо проверить, что src имеет значение null в начале перед началом работы. – Furtiro

+0

@Furtiro да конечно, не может работать, является ли src (или propName) нулевым. Я добавил исключение throw.Спасибо – DevT

+0

Рад помочь! Но это не работает для меня с тройным вложением, оно останавливается после 2, грустный, но отличный код! – Furtiro

6

Я знаю, Я немного опаздываю на вечеринку, и, как говорили другие, ваша реализация прекрасна
... для простых случаев использования.
Однако, я разработал библиотеку, которая решает именно этот прецедент, Pather.CSharp.
Он также доступен как Nuget Package.

Его основной класс Resolver с его методом Resolve.
Вы передаете ему объект исвойство пути, и он будет возвращать желаемое значение.

Invoice inv = GetDesiredInvoice(); // magic method to get an invoice 
var resolver = new Resolver(); 
object result = resolver.Resolve(inv, "BillTo.Address"); 

Но он также может решить более сложные пути собственности, включая массив и словарь доступа.
Так, например, если Customer имел несколько адресов

public class Customer { 
    public String Name { get; set; } 
    public IEnumerable<String> Addresses { get; set; } 
} 

вы могли бы получить доступ к второй, используя Addresses[1].

Invoice inv = GetDesiredInvoice(); // magic method to get an invoice 
var resolver = new Resolver(); 
object result = resolver.Resolve(inv, "BillTo.Addresses[1]"); 
+0

Как это обрабатывать нулевые объекты для вложенных свойств, т. Е. NullObject.Id, где NullObject имеет значение null в счете-фактуре? –

1

Вот другая реализация, который будет пропустить вложенное свойство, если оно нумератор и продолжает глубже. Проверка подлинности типа не влияет на проверку перечислений.

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; 
    } 
} 

на основе этого вопроса и на

How to know if a PropertyInfo is a collection по Berryl

Я использую это в проекте MVC динамически заказать мои данные, просто передавая недвижимость сортировать по Пример:

result = result.OrderBy((s) => 
       { 
        return ReflectionMethods.GetPropValue("BookingItems.EventId", s); 
       }).ToList(); 

где BookingItems - это список объектов.

Смежные вопросы