使用反射从字符串获取属性值

我正在尝试在我的代码中实现使用反射进行数据转换1示例。

GetSourceValue函数有一个比较各种类型的开关,但我想删除这些类型和属性,并让GetSourceValue仅使用单个字符串作为参数来获取属性的值。我想在字符串中传递一个类和属性并解析属性的值。

这可能吗?

1原始博客文章的网络存档版本

1005520 次浏览
 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 incompatiblereturn (T) retval;}

这将允许您使用单个字符串降级到属性,如下所示:

DateTime now = DateTime.Now;int min = GetPropValue<int>(now, "TimeOfDay.Minutes");int hrs = now.GetPropValue<int>("TimeOfDay.Hours");

您可以将这些方法用作静态方法或扩展。

你从来没有提到你正在检查什么对象,因为你拒绝引用给定对象的对象,我假设你是指静态对象。

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()是获取“运行”程序集的几种可用方法之一,如果您很难加载该类型,您可能希望使用它。

使用Microsoft.VisualBasic命名空间(Microsoft.VisualBasic.dll)的#0怎么样?它使用反射来获取普通对象、COM对象甚至动态对象的属性、字段和方法。

using Microsoft.VisualBasic;using Microsoft.VisualBasic.CompilerServices;

然后

Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)

杰丁斯的回答很棒。我想通过允许引用聚合数组或对象集合来改进它,这样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.propertystring[] propertyNameParts = propertyName.Split('.');
foreach (string propertyNamePart in propertyNameParts){if (obj == null)    return null;
// propertyNamePart could contain reference to specific// element (by index) inside a collectionif (!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 indexint indexStart = propertyNamePart.IndexOf("[")+1;string collectionPropertyName = propertyNamePart.Substring(0, indexStart-1);int collectionElementIndex = Int32.Parse(propertyNamePart.Substring(indexStart, propertyNamePart.Length-indexStart-1));//   get collection objectPropertyInfo pi = obj.GetType().GetProperty(collectionPropertyName);if (pi == null) return null;object unknownCollection = pi.GetValue(obj, null);//   try to process the collection as arrayif (unknownCollection.GetType().IsArray){object[] collectionAsArray = unknownCollection as object[];obj = collectionAsArray[collectionElementIndex];}else{//   try to process the collection as IListSystem.Collections.IList collectionAsList = unknownCollection as System.Collections.IList;if (collectionAsList != null){obj = collectionAsList[collectionElementIndex];}else{// ??? Unsupported collection type}}}}
return obj;}

使用系统反省命名空间的属性信息。无论我们尝试访问什么属性,反射都能正常编译。错误将在运行时出现。

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

它可以很好地获取对象的位置属性

Label1.Text = GetObjProperty(button1, "Location").ToString();

我们将得到位置:{X=71,Y=27}我们也可以以同样的方式返回位置. X或位置. Y。

短的路…

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

添加到任何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();// Setf["Bar"] = "asdf";// Getstring s = (string)f["Bar"];

这是另一种查找嵌套属性的方法,它不需要字符串告诉您嵌套路径。归功于Ed S.的单一属性方法。

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

以下代码是一个递归方法,用于显示对象实例中包含的所有属性名称和值的整个层次结构。此方法在此线程中使用了上面AlexDGetPropertyValue()答案的简化版本。感谢这个讨论线程,我能够弄清楚如何做到这一点!

例如,我使用此方法通过调用如下方法来显示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 objectobject myPropertyValue = GetPropertyValue(parentObj, pi.Name);if (myPropertyValue == null){// Instance of Property does not existif (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 ValueConsole.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 valueConsole.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);}}}

关于嵌套属性的讨论,如果您使用#0如下所示,您可以避免所有反射内容:

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

这是一种获取List中所有属性及其值的方法。

jheddingAlexD都写了关于如何解析属性字符串的优秀答案。我想把我的混为一谈,因为我正是为此目的编写了一个专用库。

Pather.CS竖琴的主类是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页面

如果我使用Ed S.的代码,我得到

GetProperty(Type, string)由于其保护级别而无法访问

TargetFrameworkProfile在我的可移植类库(. NET Framework 4.5、Windows 8、ASP.NETCore 1.0、Xamarin. Android、Xamarin. iOS、Xamarin. iOS Classic)中是Profile7

现在我找到了一个有效的解决方案:

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

来源

要调用的方法在. NET Standard中已更改(从1.6开始)。我们也可以使用C#6的空条件运算符。

using System.Reflection;public static object GetPropValue(object src, string propName){return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);}
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);}

这是我的解决方案。它也适用于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;}
public class YourClass{//Add below line in your classpublic 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"];

下面的方法非常适合我:

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;

看看Heleonix反射库。您可以通过路径获取/设置/调用成员,或者创建一个比反射更快的getter/setter(lambda编译成委托)。例如:

var success = Reflector.Get(DateTime.Now, null, "Date.Year", out int value);

或者创建一次getter并缓存以供重用(这更高性能,但如果中间成员为null,可能会抛出NullRe的异常):

var getter = Reflector.CreateGetter<DateTime, int>("Date.Year", typeof(DateTime));getter(DateTime.Now);

或者,如果您想创建不同getter的List<Action<object, object>>,只需为已编译委托指定基本类型(类型转换将添加到已编译的lambda中):

var getter = Reflector.CreateGetter<object, object>("Date.Year", typeof(DateTime));getter(DateTime.Now);

这是我根据其他答案得出的结论。把错误处理得这么具体有点矫枉过正。

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

尽管最初的问题是关于如何仅使用单个字符串作为参数获取属性的值,但在这里使用表达式而不是简单的字符串来确保调用者永远不会使用硬编码的属性名称是很有意义的。这是一个单行版本的用法:

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)?.Membervar p = m as PropertyInfo;
if (null == p)throw new ArgumentException($"Unknown property: {typeof(T).Name}.{(m?.Name??"???")}");
return (TVal)p.GetValue(t);}

简而言之,你传入一个读取属性的lambda表达式。lambda的主体——脂肪箭头右侧的部分——是一个成员表达式,你可以从中获取成员名称,你可以将其强制转换为属性信息,前提是该成员实际上是一个属性,而不是,例如,一个方法。

在短版本中,空原谅运算符-表达式中的!-告诉编译器属性信息不会为空。这是一个很大的谎言,你将在运行时得到一个NullReReference ceException。较长的版本会给你属性的名称,如果它设法得到它。

PS:感谢OlegG.提供此代码的初始版本:)