Использование отражения в С# для получения свойств вложенного объекта
Учитывая следующие объекты:
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;
}
Любые идеи о том, как улучшить этот метод, или лучший способ решить эту проблему?
ИЗМЕНИТЬ после публикации, я увидел несколько связанных сообщений... Однако, похоже, нет ответа, который специально решает этот вопрос. Кроме того, мне все равно нравятся отзывы о моей реализации.
Ответы
Ответ 1
Я действительно думаю, что ваша логика в порядке. Лично я бы, вероятно, изменил его, чтобы передать объект в качестве первого параметра (который больше связан с PropertyInfo.GetValue, поэтому менее неожиданным).
Я также, вероятно, назвал бы его чем-то более похожим на GetNestedPropertyValue, чтобы было очевидно, что он ищет в стеке свойств.
Ответ 2
Я использую следующий метод, чтобы получить значения из (вложенных классов) свойств, таких как
"Недвижимость"
"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
Ответ 3
Вам нужно получить доступ к объекту 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);
Посмотрите на это сообщение для получения дополнительной информации:
Использование отражения для установки свойства свойства объекта
Ответ 4
Я знаю, что я немного опаздываю на вечеринку, и, как говорили другие, ваша реализация в порядке
... для простых случаев использования.
Тем не менее, я разработал библиотеку, которая решает именно этот прецедент, Pather.CSharp.
Он также доступен как пакет Nuget.
Его основной класс 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]");
Ответ 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();
}
}
Ответ 6
Вы не объясняете источник своего "дискомфорта", но ваш код в основном выглядит для меня.
Единственное, что я задал бы, это обработка ошибок. Вы возвращаете значение null, если код пытается пройти через нулевую ссылку или если имя свойства не существует. Это скрывает ошибки: трудно узнать, вернул ли он null, потому что нет клиента BillTo, или потому, что вы набрали его "BilTo.Address"... или потому, что есть клиент BillTo, и его адрес недействителен! Я бы позволил методу сбой и записи в этих случаях - просто разрешите исключение (или, возможно, оберните его в более дружественный).
Ответ 7
> 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]);
}
Ответ 8
if (info == null) { /* throw exception instead*/ }
Я бы выбрал исключение, если они запрашивают свойство, которого не существует. То, как вы его закодировали, если я вызываю GetPropValue, и он возвращает null, я не знаю, означает ли это, что свойство не существует, или свойство существует, но оно имеет значение null.
Ответ 9
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() + "'");
}
Ответ 10
Вот еще одна реализация, которая пропустит вложенное свойство, если оно является перечислителем и продолжит глубже. Проверка свойств переменной типа не влияет на проверку перечислений.
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;
}
}
на основе этого вопроса и на
Как узнать, является ли PropertyInfo коллекциейБеррил
Я использую это в проекте MVC для динамического упорядочения своих данных, просто передав свойство для сортировки по
Пример:
result = result.OrderBy((s) =>
{
return ReflectionMethods.GetPropValue("BookingItems.EventId", s);
}).ToList();
где BookingItems - это список объектов.
Ответ 11
У меня было отключено подключение к Интернету, когда мне нужно было решить ту же проблему, поэтому мне пришлось "изобретать колесо":
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);
}
}
Довольно точно это решает проблему для любой строки, которую вы используете для имени свойства, независимо от степени вложенности, если все свойство.
Ответ 12
Попробуйте inv.GetType().GetProperty("BillTo+Address");