ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在尝试在我的代码中实现 Data transformation using Reflection1 示例。

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

这可能吗?

1 Web Archive version of original blog post


E
Ed S.
 public static object GetPropValue(object src, string propName)
 {
     return src.GetType().GetProperty(propName).GetValue(src, null);
 }

当然,你会想要添加验证和诸如此类的东西,但这就是它的要点。


又好又简单!不过,我会使其通用:public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }
优化可以消除空异常的风险,如下所示:“src.GetType().GetProperty(propName)?.GetValue(src, null);”;)。
@shA.t:我认为这是个坏主意。您如何区分现有属性的空值或根本没有属性?我宁愿立即知道我发送了一个错误的属性名称。这不是生产代码,但更好的改进是抛出更具体的异常(例如,检查 GetProperty 上的 null 并抛出 PropertyNotFoundException 或如果为 null。)
以防万一您的属性确实是字段而不是属性(如我的;))然后使用 GetField 而不是 GetProperty
j
jheddings

像这样的东西怎么样:

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

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


@FredJand 很高兴你偶然发现了它!当这些旧帖子出现时,总是令人惊讶。这有点模糊,所以我添加了一些文字来解释它。我也切换到使用这些作为扩展方法并添加了一个泛型表单,所以我在这里添加了它。
为什么空守卫在 foreach 而不是上面?
@Santhos 由于 'obj' 在 foreach 循环的主体中重新定义,因此在每次迭代期间都会对其进行检查。
很有用,但在嵌套属性之一可能被隐藏(使用“新”修饰符)的边缘情况下,它将引发查找重复属性的异常。跟踪最后一个属性类型并在嵌套属性上使用 PropertyInfo.PropertyType 而不是 obj.GetType() 会更整洁,就像访问嵌套属性上的属性一样。
从 C#6 开始,您可以像这样使用 nameof 表达式:在调用函数时在 name 参数上使用 nameof(TimeOfDay.Minutes) 以消除魔术字符串并为这些调用添加编译时安全性。
E
Eduardo Cuomo

添加到任何 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"];

@EduardoCuomo:是否可以使用反射,这样您就不需要知道班级有哪些成员?
如果“Bar”是一个对象,是否可以这样做?
这种类型方法的名称是什么..?
非常感谢,我把这个放在我的一些课上
J
J0e3gan

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

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

接着

Versioned.CallByName(this, "method/function/prop name", CallType.Get).ToString();

有趣的建议,进一步检查证明它可以处理字段和属性、COM 对象,甚至可以正确处理动态绑定!
我收到一个错误:找不到类型“MyType”的公共成员“MyPropertyName”。
M
MarredCheese

Great answer by 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 object[];
                    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;
    }

由 MasterList[0][1] 访问的列表列表呢?
作为数组-> as object[] 也会导致 Nullreference 异常。对我有用的方法(不是最有效的方法)是将 unknownCollection 转换为 IEnumerable ,然后在结果上使用 ToArray() 。 fiddle
t
testing

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

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

似乎 GetProperty() 在 Xamarin.Forms 中不可用。 TargetFrameworkProfile 在我的可移植类库(.NET Framework 4.5、Windows 8、ASP.NET Core 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);
}

Source


只是一个微小的可能改进。将 IF 和下一个 return 替换为:return property?.GetValue(source);
R
Rubens Farias

关于嵌套属性的讨论,如果您使用 DataBinder.Eval Method (Object, String),您可以避免所有反射的东西,如下所示:

var value = DataBinder.Eval(DateTime.Now, "TimeOfDay.Hours");

当然,您需要添加对 System.Web 程序集的引用,但这可能没什么大不了的。


M
Matt Frear

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

using System.Reflection; 
public static object GetPropValue(object src, string propName)
{
    return src.GetType().GetRuntimeProperty(propName)?.GetValue(src);
}

准备使用 ? operator
O
OneCricketeer

以下方法非常适合我:

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;

B
Boncho Valkov
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;
    }

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


为什么要这样做:type.GetProperty(pi.Name) 当 == 变量 pi 时?
如果您使用的是 c# 6.0,请去掉 if 并执行 selfValue?.ToString() 否则去掉 if 并使用 selfValue==null?null:selfValue.ToString()
List<KeyValuePair< 的列表也很奇怪,请使用字典 Dictionary<string, string>
t
tom redfern

使用 System.Reflection 命名空间的 PropertyInfo。无论我们尝试访问什么属性,反射都能正常编译。该错误将在运行时出现。

    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。


I
Irshad

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

例如,我使用此方法通过调用如下方法来显示 WebService 响应中所有属性的爆炸或转储:

PropertyValues_byRecursion("响应", 响应, 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);
    }
  }
}

R
Rahma Samaroon
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);
}

K
Komal Narang
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"];

G
Glory Raj
Dim NewHandle As YourType = CType(Microsoft.VisualBasic.CallByName(ObjectThatContainsYourVariable, "YourVariableName", CallType), YourType)

R
Recursor

这是查找不需要字符串告诉您嵌套路径的嵌套属性的另一种方法。感谢 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);
    }

检查 Type.GetProperty 是否返回 null 而不是调用 GetValue 并在循环中抛出 NullReferenceException 可能会更好。
I
Irshad

您从不提及您正在检查的对象,并且由于您拒绝引用给定对象的对象,因此我假设您的意思是静态对象。

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() 是获取“运行”程序集的几种可用方法之一,如果您在加载类型时遇到困难,您可能想尝试一下。


H
Hennadii Lutsyshyn

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

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

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

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

永远不要使用 3rd 方库,如果您可以在合理的时间内在 5-10 行中在自己的代码中实现它。
p
pasx

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

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

简而言之,您传入一个读取属性的 lambda 表达式。 lambda 的主体(粗箭头右侧的部分)是一个成员表达式,您可以从中获取成员名称,并且可以将其强制转换为 PropertyInfo,前提是该成员实际上是一个 Property 而不是,例如,一个方法。

在简短的版本中,空宽容运算符 - !在表达式中 - 告诉编译器 PropertyInfo 不会为空。这是一个很大的谎言,您将在运行时收到 NullReferenceException。如果它设法获得它,较长的版本会为您提供属性的名称。

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


a
andrewsi

更短的方式....

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

C
Community

jheddingsAlexD 都写了关于如何解析属性字符串的出色答案。我想把我的加入其中,因为我专门为此目的编写了一个专用库。

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 page


J
Jeff Codes

这是我根据其他答案得到的。在错误处理方面变得如此具体有点矫枉过正。

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

u
user3175253

这是我的解决方案。它也适用于 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;
}