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

1177

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

GetSourceValue函数有一个比较不同类型的switch语句,但我想删除这些类型和属性,并使GetSourceValue仅使用单个字符串作为参数获取属性的值。我想要在字符串中传递类和属性,并解析属性的值。

这种做法可能吗?

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

24个回答

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

当然,您肯定希望添加验证等功能,但这就是要点。


41
简单易懂!但我会让它更通用一些: public static T GetPropertyValue<T>(object obj, string propName) { return (T)obj.GetType().GetProperty(propName).GetValue(obj, null); }(该函数可以获取对象中指定属性的值,返回类型为 T) - Ohad Schneider
10
优化可以消除空引用异常的风险,像这样:"src.GetType().GetProperty(propName)?.GetValue(src, null);" ;)。 - shA.t
27
@shA.t: 我认为那是个不好的主意。你如何区分一个已存在的属性的空值或者根本没有该属性?我更愿意立即知道我是否在发送错误的属性名称。虽然这并不是生产代码,但更好的改进方式是抛出一个更具体的异常(例如,在 GetProperty 中检查 null 并抛出 PropertyNotFoundException 或类似的异常)。 - Ed S.
9
如果您的属性确实是字段而不是属性(就像我的属性一样;)则请使用GetField而不是GetProperty - Christopher K.

254
像这样的东西怎么样:
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");

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


4
@FredJand,很高兴你看到了它!当这些旧帖子重新出现时,总是令人惊讶的。它有点含糊不清,所以我添加了一些文本来解释它。我还改用了这些作为扩展方法并添加了一个泛型形式,所以我在这里加入了它。 - jheddings
为什么在foreach中使用null guard而不是在其上方? - Santhos
5
由于在 foreach 循环体中重新定义了 'obj',因此它会在每次迭代时进行检查。 - jheddings
虽然有用,但在嵌套属性中可能会隐藏一个(使用“new”修饰符),这将导致查找重复属性时抛出异常。更好的方法是跟踪最后一个属性类型,并像访问嵌套属性上的属性一样使用PropertyInfo.PropertyType而不是obj.GetType() - Nullius
2
你可以像C#6一样使用nameof表达式,例如在调用函数时的名称参数上使用nameof(TimeOfDay.Minutes),以消除魔术字符串并为这些调用添加编译时安全性。 - Reap

116

添加到任何中:

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:能否使用反射来实现,从而不需要知道类有哪些成员? - Our Man in Bananas
如果“Bar”是一个对象,那么这个操作是否可行? - big_water
1
这种类型的方法叫什么名字? - Sahan Chinthaka
1
@SahanChinthaka 索引属性。 https://learn.microsoft.com/tr-tr/dotnet/csharp/programming-guide/indexers/ - Mustafa Salih ASLIM
1
非常感谢,我将其应用于我的一些课程中。 - ilyas varol
显示剩余2条评论

48

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

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

然后

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

6
有趣的建议,进一步检查证明它可以处理字段和属性、COM 对象,甚至可以正确处理动态绑定! - IS4
我遇到了一个错误:在类型'MyType'上未找到公共成员'MyPropertyName'。 - vldmrrdjcc

45

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]访问的列表列表怎么样? - Jesse Adam
将“as Array”转换为“as object []”也会导致Nullreference异常。 对我有用的方法(属性不是最有效的方法)是将unknownCollection强制转换为IEnumerable,然后对结果使用ToArray()。 fiddle - M1sterPl0w

19

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

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

看来Xamarin.Forms中不提供GetProperty()方法。Profile7是我的便携式类库(.NET Framework 4.5、Windows 8、ASP.NET Core 1.0、Xamarin.Android、Xamarin.iOS和Xamarin.iOS Classic)中的TargetFrameworkProfile

现在我找到了一个可行的解决方案:

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

来源


4
可以微调一下,用以下代码替换IF和下一个return:return property?.GetValue(source); - Tomino

13
以下方法对我非常有效:
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;

这是一个非常棒的答案,非常简单易实施和使用。使用这个方法会有什么潜在的意想不到的副作用吗? - d0rf47

13

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

2
使用 ? 运算符,准备好了吗? - blfuentes

12
关于嵌套属性的讨论,如果您使用以下方式的DataBinder.Eval Method (Object, String),则可以避免所有反射内容:
var value = DataBinder.Eval(DateTime.Now, "TimeOfDay.Hours");

当然,你需要添加对System.Web程序集的引用,但这可能并不是一个大问题。


6
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 - weston
如果您正在使用C# 6.0,请摆脱if并执行selfValue?.ToString()。否则,摆脱if并使用selfValue==null?null:selfValue.ToString() - weston
如果一个 List<KeyValuePair< 的列表很奇怪,那么请使用一个字典 Dictionary<string, string> - weston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,