Получить значение свойства из строки, используя отражение в C#
Я пытаюсь реализовать преобразование данных, используя пример отражения 1 в моем коде.
GetSourceValue
Функция имеет переключатель, сравнивающий различные типы, но я хочу удалить эти типы и свойства и GetSourceValue
получить значение свойства, используя только одну строку в качестве параметра. Я хочу передать класс и свойство в строке и разрешить значение свойства.
Это возможно?
24 ответа
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetProperty(propName).GetValue(src, null);
}
Конечно, вы захотите добавить валидацию и еще много чего, но в этом суть.
Как насчет чего-то вроде этого:
public static Object GetPropValue(this Object obj, String name) {
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;
}
public static T GetPropValue<T>(this Object obj, String name) {
Object retval = GetPropValue(obj, name);
if (retval == null) { return default(T); }
// throws InvalidCastException if types are incompatible
return (T) retval;
}
Это позволит вам перейти в свойства, используя одну строку, например:
DateTime now = DateTime.Now;
int min = GetPropValue<int>(now, "TimeOfDay.Minutes");
int hrs = now.GetPropValue<int>("TimeOfDay.Hours");
Вы можете использовать эти методы в качестве статических методов или расширений.
Добавить к любому Class
:
public class Foo
{
public object this[string propertyName]
{
get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
}
public string Bar { get; set; }
}
Затем вы можете использовать как:
Foo f = new Foo();
// Set
f["Bar"] = "asdf";
// Get
string s = (string)f["Bar"];
Как насчет использования CallByName
из Microsoft.VisualBasic
пространство имен (Microsoft.VisualBasic.dll
)? Он использует отражение, чтобы получить свойства, поля и методы обычных объектов, объектов COM и даже динамических объектов.
using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
а потом
Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();
Отличный ответ от jheddings. Я хотел бы улучшить его, разрешив ссылаться на агрегированные массивы или коллекции объектов, чтобы propertyName могло иметь значение property1.property2[X].property3:
public static object GetPropertyValue(object srcobj, string propertyName)
{
if (srcobj == null)
return null;
object obj = srcobj;
// Split property name to parts (propertyName could be hierarchical, like obj.subobj.subobj.property
string[] propertyNameParts = propertyName.Split('.');
foreach (string propertyNamePart in propertyNameParts)
{
if (obj == null) return null;
// propertyNamePart could contain reference to specific
// element (by index) inside a collection
if (!propertyNamePart.Contains("["))
{
PropertyInfo pi = obj.GetType().GetProperty(propertyNamePart);
if (pi == null) return null;
obj = pi.GetValue(obj, null);
}
else
{ // propertyNamePart is areference to specific element
// (by index) inside a collection
// like AggregatedCollection[123]
// get collection name and element index
int indexStart = propertyNamePart.IndexOf("[")+1;
string collectionPropertyName = propertyNamePart.Substring(0, indexStart-1);
int collectionElementIndex = Int32.Parse(propertyNamePart.Substring(indexStart, propertyNamePart.Length-indexStart-1));
// get collection object
PropertyInfo pi = obj.GetType().GetProperty(collectionPropertyName);
if (pi == null) return null;
object unknownCollection = pi.GetValue(obj, null);
// try to process the collection as array
if (unknownCollection.GetType().IsArray)
{
object[] collectionAsArray = unknownCollection as Array[];
obj = collectionAsArray[collectionElementIndex];
}
else
{
// try to process the collection as IList
System.Collections.IList collectionAsList = unknownCollection as System.Collections.IList;
if (collectionAsList != null)
{
obj = collectionAsList[collectionElementIndex];
}
else
{
// ??? Unsupported collection type
}
}
}
}
return obj;
}
Если я использую код от Ed S., я получаю
ReflectionExtensions.GetProperty(Type, string) недоступен из-за уровня защиты
Кажется, что GetProperty()
недоступно в Xamarin.Forms. TargetFrameworkProfile
является Profile7
в моей портативной библиотеке классов (.NET Framework 4.5, Windows 8, ASP.NET Core 1.0, Xamarin.Android, Xamarin.iOS, Xamarin.iOS Classic).
Теперь я нашел рабочее решение:
using System.Linq;
using System.Reflection;
public static object GetPropValue(object source, string propertyName)
{
var property = source.GetType().GetRuntimeProperties().FirstOrDefault(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase));
return property?.GetValue(source);
}
Приведенный ниже метод идеально подходит для меня:
class MyClass {
public string prop1 { set; get; }
public object this[string propertyName]
{
get { return this.GetType().GetProperty(propertyName).GetValue(this, null); }
set { this.GetType().GetProperty(propertyName).SetValue(this, value, null); }
}
}
Чтобы получить значение свойства:
MyClass t1 = new MyClass();
...
string value = t1["prop1].ToString();
Чтобы установить значение свойства:
t1["prop1] = value;
Метод для вызова изменился в.NET Standard (по состоянию на 1.6). Также мы можем использовать нулевой условный оператор C# 6.
using System.Reflection;
public static object GetPropValue(object src, string propName)
{
return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);
}
Что касается обсуждения вложенных свойств, вы можете избежать всех вещей отражения, если вы используете DataBinder.Eval Method (Object, String)
как показано ниже:
var value = DataBinder.Eval(DateTime.Now, "TimeOfDay.Hours");
Конечно, вам нужно добавить ссылку на System.Web
сборка, но это, вероятно, не имеет большого значения.
public static List<KeyValuePair<string, string>> GetProperties(object item) //where T : class
{
var result = new List<KeyValuePair<string, string>>();
if (item != null)
{
var type = item.GetType();
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var pi in properties)
{
var selfValue = type.GetProperty(pi.Name).GetValue(item, null);
if (selfValue != null)
{
result.Add(new KeyValuePair<string, string>(pi.Name, selfValue.ToString()));
}
else
{
result.Add(new KeyValuePair<string, string>(pi.Name, null));
}
}
}
return result;
}
Это способ получить все свойства с их значениями в списке.
Использование PropertyInfo пространства имен System.Reflection. Reflection компилируется просто независимо от того, к какому свойству мы пытаемся получить доступ. Ошибка появится во время выполнения.
public static object GetObjProperty(object obj, string property)
{
Type t = obj.GetType();
PropertyInfo p = t.GetProperty("Location");
Point location = (Point)p.GetValue(obj, null);
return location;
}
Он отлично работает, чтобы получить свойство Location объекта
Label1.Text = GetObjProperty(button1, "Location").ToString();
Мы получим Location: {X=71,Y=27} Мы также можем вернуть location.X или location.Y таким же образом.
public class YourClass
{
//Add below line in your class
public object this[string propertyName] => GetType().GetProperty(propertyName)?.GetValue(this, null);
public string SampleProperty { get; set; }
}
//And you can get value of any property like this.
var value = YourClass["SampleProperty"];
public static TValue GetFieldValue<TValue>(this object instance, string name)
{
var type = instance.GetType();
var field = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.FieldType) && e.Name == name);
return (TValue)field?.GetValue(instance);
}
public static TValue GetPropertyValue<TValue>(this object instance, string name)
{
var type = instance.GetType();
var field = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).FirstOrDefault(e => typeof(TValue).IsAssignableFrom(e.PropertyType) && e.Name == name);
return (TValue)field?.GetValue(instance);
}
Следующий код представляет собой рекурсивный метод для отображения всей иерархии всех имен и значений свойств, содержащихся в экземпляре объекта. Этот метод использует упрощенную версию AlexD GetPropertyValue()
ответ выше в этой теме. Благодаря этой ветке обсуждения я смог понять, как это сделать!
Например, я использую этот метод, чтобы показать взрыв или сброс всех свойств в WebService
ответ, вызвав метод следующим образом:
PropertyValues_byRecursion("Response", response, false);
public static object GetPropertyValue(object srcObj, string propertyName)
{
if (srcObj == null)
{
return null;
}
PropertyInfo pi = srcObj.GetType().GetProperty(propertyName.Replace("[]", ""));
if (pi == null)
{
return null;
}
return pi.GetValue(srcObj);
}
public static void PropertyValues_byRecursion(string parentPath, object parentObj, bool showNullValues)
{
/// Processes all of the objects contained in the parent object.
/// If an object has a Property Value, then the value is written to the Console
/// Else if the object is a container, then this method is called recursively
/// using the current path and current object as parameters
// Note: If you do not want to see null values, set showNullValues = false
foreach (PropertyInfo pi in parentObj.GetType().GetTypeInfo().GetProperties())
{
// Build the current object property's namespace path.
// Recursion extends this to be the property's full namespace path.
string currentPath = parentPath + "." + pi.Name;
// Get the selected property's value as an object
object myPropertyValue = GetPropertyValue(parentObj, pi.Name);
if (myPropertyValue == null)
{
// Instance of Property does not exist
if (showNullValues)
{
Console.WriteLine(currentPath + " = null");
// Note: If you are replacing these Console.Write... methods callback methods,
// consider passing DBNull.Value instead of null in any method object parameters.
}
}
else if (myPropertyValue.GetType().IsArray)
{
// myPropertyValue is an object instance of an Array of business objects.
// Initialize an array index variable so we can show NamespacePath[idx] in the results.
int idx = 0;
foreach (object business in (Array)myPropertyValue)
{
if (business == null)
{
// Instance of Property does not exist
// Not sure if this is possible in this context.
if (showNullValues)
{
Console.WriteLine(currentPath + "[" + idx.ToString() + "]" + " = null");
}
}
else if (business.GetType().IsArray)
{
// myPropertyValue[idx] is another Array!
// Let recursion process it.
PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
}
else if (business.GetType().IsSealed)
{
// Display the Full Property Path and its Value
Console.WriteLine(currentPath + "[" + idx.ToString() + "] = " + business.ToString());
}
else
{
// Unsealed Type Properties can contain child objects.
// Recurse into my property value object to process its properties and child objects.
PropertyValues_byRecursion(currentPath + "[" + idx.ToString() + "]", business, showNullValues);
}
idx++;
}
}
else if (myPropertyValue.GetType().IsSealed)
{
// myPropertyValue is a simple value
Console.WriteLine(currentPath + " = " + myPropertyValue.ToString());
}
else
{
// Unsealed Type Properties can contain child objects.
// Recurse into my property value object to process its properties and child objects.
PropertyValues_byRecursion(currentPath, myPropertyValue, showNullValues);
}
}
}
Взгляните на библиотеку Heleonix.Reflection. Вы можете получить / установить / вызвать членов по путям или создать метод получения / установки (лямбда-компиляцию в делегат), который быстрее, чем отражение. Например:
var success = Reflector.Get(DateTime.Now, null, "Date.Year", out int value);
Или создайте геттер один раз и кэшируйте для повторного использования (это более производительно, но может вызвать исключение NullReferenceException, если промежуточный член имеет значение null):
var getter = Reflector.CreateGetter<DateTime, int>("Date.Year", typeof(DateTime));
getter(DateTime.Now);
Или если вы хотите создать List<Action<object, object>>
из различных методов получения, просто укажите базовые типы для скомпилированных делегатов (преобразования типов будут добавлены в скомпилированные лямбды):
var getter = Reflector.CreateGetter<object, object>("Date.Year", typeof(DateTime));
getter(DateTime.Now);
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)
Вот еще один способ найти вложенное свойство, для которого не требуется строка, указывающая путь вложения. Благодарим Эд С. за метод единственного имущества.
public static T FindNestedPropertyValue<T, N>(N model, string propName) {
T retVal = default(T);
bool found = false;
PropertyInfo[] properties = typeof(N).GetProperties();
foreach (PropertyInfo property in properties) {
var currentProperty = property.GetValue(model, null);
if (!found) {
try {
retVal = GetPropValue<T>(currentProperty, propName);
found = true;
} catch { }
}
}
if (!found) {
throw new Exception("Unable to find property: " + propName);
}
return retVal;
}
public static T GetPropValue<T>(object srcObject, string propName) {
return (T)srcObject.GetType().GetProperty(propName).GetValue(srcObject, null);
}
Вы никогда не упоминаете, какой объект вы проверяете, и, поскольку вы отклоняете объекты, ссылающиеся на данный объект, я предполагаю, что вы имеете в виду статический объект.
using System.Reflection;
public object GetPropValue(string prop)
{
int splitPoint = prop.LastIndexOf('.');
Type type = Assembly.GetEntryAssembly().GetType(prop.Substring(0, splitPoint));
object obj = null;
return type.GetProperty(prop.Substring(splitPoint + 1)).GetValue(obj, null);
}
Обратите внимание, что я отметил объект, который проверяется локальной переменной obj
, null
означает статический, в противном случае установите его на то, что вы хотите. Также обратите внимание, что GetEntryAssembly()
Это один из немногих доступных методов для получения "работающей" сборки, вы можете поэкспериментировать с ней, если вам трудно загрузить тип.
Хотя первоначальный вопрос был о том, как получить значение свойства, используя только одну строку в качестве параметра , здесь имеет смысл использовать выражение, а не просто строку, чтобы гарантировать, что вызывающий объект никогда не использует жестко запрограммированное свойство. имя. Вот однострочная версия с использованием:
public static class Utils
...
public static TVal GetPropertyValue<T, TVal>(T t, Expression<Func<T, TVal>> x)
=> (TVal)((x.Body as MemberExpression)?.Member as PropertyInfo)!.GetValue(t);
...
var val = Utils.GetPropertyValue(foo, p => p.Bar);
Вот немного лучшая версия с точки зрения удобочитаемости обработки ошибок:
public static TVal GetPropertyValue<T, TVal>(T t, Expression<Func<T, TVal>> x)
{
var m = (x.Body as MemberExpression)?.Member
var p = m as PropertyInfo;
if (null == p)
throw new ArgumentException($"Unknown property: {typeof(T).Name}.{(m?.Name??"???")}");
return (TVal)p.GetValue(t);
}
Короче говоря, вы передаете лямбда-выражение, читающее свойство. Тело лямбды — часть справа от жирной стрелки — это выражение члена, из которого вы можете получить имя члена и которое вы можете привести к PropertyInfo, при условии, что член на самом деле является свойством, а не, например, метод.
В короткой версии оператор, прощающий null, — ! в выражении — сообщает компилятору, что PropertyInfo не будет нулевым. Это большая ложь, и вы получите исключение NullReferenceException во время выполнения. Более длинная версия дает вам имя свойства, если ему удается его получить.
PS: Спасибо Олегу Г. за первоначальную версию этого кода :)
Короче....
var a = new Test { Id = 1 , Name = "A" , date = DateTime.Now};
var b = new Test { Id = 1 , Name = "AXXX", date = DateTime.Now };
var compare = string.Join("",a.GetType().GetProperties().Select(x => x.GetValue(a)).ToArray())==
string.Join("",b.GetType().GetProperties().Select(x => x.GetValue(b)).ToArray());
jheddings и AlexD оба написали отличные ответы о том, как разрешать строки свойств. Я хотел бы добавить свой микс, поскольку я написал специально для этой цели библиотеку.
Основной класс Pather.CSharp Resolver
, По умолчанию он может разрешать свойства, массивы и словарные записи.
Так, например, если у вас есть такой объект
var o = new { Property1 = new { Property2 = "value" } };
и хочу получить Property2
, вы можете сделать это так:
IResolver resolver = new Resolver();
var path = "Property1.Property2";
object result = r.Resolve(o, path);
//=> "value"
Это самый простой пример путей, которые он может разрешить. Если вы хотите узнать, что еще он может или как вы можете его расширить, просто перейдите на его страницу Github.
Вот что я получил, основываясь на других ответах. Немного излишним, когда я стал настолько конкретен с обработкой ошибок.
public static T GetPropertyValue<T>(object sourceInstance, string targetPropertyName, bool throwExceptionIfNotExists = false)
{
string errorMsg = null;
try
{
if (sourceInstance == null || string.IsNullOrWhiteSpace(targetPropertyName))
{
errorMsg = $"Source object is null or property name is null or whitespace. '{targetPropertyName}'";
Log.Warn(errorMsg);
if (throwExceptionIfNotExists)
throw new ArgumentException(errorMsg);
else
return default(T);
}
Type returnType = typeof(T);
Type sourceType = sourceInstance.GetType();
PropertyInfo propertyInfo = sourceType.GetProperty(targetPropertyName, returnType);
if (propertyInfo == null)
{
errorMsg = $"Property name '{targetPropertyName}' of type '{returnType}' not found for source object of type '{sourceType}'";
Log.Warn(errorMsg);
if (throwExceptionIfNotExists)
throw new ArgumentException(errorMsg);
else
return default(T);
}
return (T)propertyInfo.GetValue(sourceInstance, null);
}
catch(Exception ex)
{
errorMsg = $"Problem getting property name '{targetPropertyName}' from source instance.";
Log.Error(errorMsg, ex);
if (throwExceptionIfNotExists)
throw;
}
return default(T);
}
Вот мое решение. Он также работает с COM-объектами и позволяет получить доступ к элементам коллекции / массива из COM-объектов.
public static object GetPropValue(this object obj, string name)
{
foreach (string part in name.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
if (type.Name == "__ComObject")
{
if (part.Contains('['))
{
string partWithoundIndex = part;
int index = ParseIndexFromPropertyName(ref partWithoundIndex);
obj = Versioned.CallByName(obj, partWithoundIndex, CallType.Get, index);
}
else
{
obj = Versioned.CallByName(obj, part, CallType.Get);
}
}
else
{
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
}
return obj;
}
private static int ParseIndexFromPropertyName(ref string name)
{
int index = -1;
int s = name.IndexOf('[') + 1;
int e = name.IndexOf(']');
if (e < s)
{
throw new ArgumentException();
}
string tmp = name.Substring(s, e - s);
index = Convert.ToInt32(tmp);
name = name.Substring(0, s - 1);
return index;
}
Всякий раз, когда вы хотите перебрать все свойства объекта, а затем использовать каждое значение свойства, необходимо использовать этот фрагмент кода:
foreach (var property in request.GetType().GetProperties())
{
var valueOfProperty = property.GetValue(properties, null);
}