Использование отражения в 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.Адреса" не является допустимым свойством!--3--> класса.
Итак, я попытался написать метод, чтобы разделить строку на части на период, и ходить по объектам, ищущим конечное значение, которое меня интересовало в. Он работает нормально, но я не совсем комфортно с ним:
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 после публикации я увидел несколько связанных сообщений... Однако, похоже, нет ответа, который конкретно затрагивает этот вопрос. Кроме того, я все равно хотел бы получить обратную связь о моей реализации.
12 ответов:
Я на самом деле думаю, что ваша логика в порядке. Лично я, вероятно, изменил бы его, поэтому вы передаете объект в качестве первого параметра (который более встроен в PropertyInfo.Думою, так что менее удивительно).
Я также, вероятно, назову его чем-то более похожим на GetNestedPropertyValue, чтобы было очевидно, что он ищет в стеке свойств.
вы должны получить доступ к фактическому объекту, на котором вам нужно использовать отражение. Вот что я имею в виду:
вместо этого:
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);посмотрите на этот пост для получения дополнительной информации: использование отражения для установки свойства свойства объекта
вы не объясняете источник вашего "дискомфорта", но ваш код в основном выглядит здравым для меня.
единственное, что я бы спросил, это обработка ошибок. Вам возвращают null, если код пытается пройти через нулевую ссылку или если имя свойства не существует. Это скрывает ошибки: трудно узнать, вернул ли он null, потому что нет клиента BillTo, или потому что вы неправильно написали его "BilTo.Адрес.".. или потому что есть клиент BillTo, и его адрес равен нулю! Я бы пусть метод аварийно завершится и сгорит в этих случаях - просто позвольте исключению избежать (или, возможно, оберните его в более дружественный).
в надежде не звучать слишком поздно для партии, я хотел бы добавить свое решение: Определенно используйте рекурсию в этой ситуации
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(); } }
Я знаю, что немного опоздал на вечеринку, и, как говорили другие,ваша реализация в порядке
...для простых случаев использования.
Тем не менее, я разработал библиотеку, которая решает именно этот вариант использования,Песнь.CSharp.
Он также доступен как Пакет Nuget.его основной класс
ResolverСResolveметод.
Вы передаете его объект и the путь к свойству, и он вернет нужное значение.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]");
Я использую этот метод, чтобы получить значения из свойств, таких как
"свойства"
"адрес.Улица"
"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
> Get Nest properties e.g., Developer.Project.Nameprivate 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]); }
if (info == null) { /* throw exception instead*/ }Я бы на самом деле бросил исключение, если они запрашивают свойство, которое не существует. Таким, каким вы его закодировали, если я назову новые, и он возвращает значение null, я не знаю, если это означает, что свойство не существует, или свойство существует, но его значение было null.
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() + "'"); }
вот еще одна реализация, которая будет пропустить вложенное свойство, если это перечислитель и продолжить глубже. Проверка перечисления не влияет на свойства типа string.
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; } }основываясь на этом вопросе и на
Я использую это в проекте MVC для динамического упорядочения моих данных, просто передавая свойство для сортировки пример:
result = result.OrderBy((s) => { return ReflectionMethods.GetPropValue("BookingItems.EventId", s); }).ToList();где BookingItems-это список объектов.
мое подключение к интернету было отключено, когда мне нужно было решить ту же проблему, поэтому мне пришлось "заново изобрести колесо":
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); } }уверен, что это решает проблему для любой строки, которую вы используете для имени свойства, независимо от степени вложенности, пока все это имущество.
Comments