如何通过反射一次性获取字段和属性?

27

如果这个问题已经有解决方案,我很抱歉。在发布之前我进行了研究!

好的,问题是...我正在使用GetType( ).GetProperties,但它不返回没有get/set的简单实例字段...所以我用了.GetFields,找到了它们,但我想获得一个简单的单个对象来设置/获取值而不用在字段和属性之间切换...这可能吗?

我当前的代码适用于PropertyInfo,工作得很好,但我想知道它是否适用于字段?

[编辑] 这是我想出的解决方案,它运行良好。谢谢大家......

    // some logic borrowed from James Newton-King, http://www.newtonsoft.com
    public static void SetValue(this MemberInfo member, object property, object value)
    {
        if (member.MemberType == MemberTypes.Property)
            ((PropertyInfo)member).SetValue(property, value, null);
        else if (member.MemberType == MemberTypes.Field)
            ((FieldInfo)member).SetValue(property, value);
        else
            throw new Exception("Property must be of type FieldInfo or PropertyInfo");
    }

    public static object GetValue(this MemberInfo member, object property)
    {
        if (member.MemberType == MemberTypes.Property)
            return ((PropertyInfo)member).GetValue(property, null);
        else if (member.MemberType == MemberTypes.Field)
            return ((FieldInfo)member).GetValue(property);
        else
            throw new Exception("Property must be of type FieldInfo or PropertyInfo");
    }

    public static Type GetType(this MemberInfo member)
    {
        switch (member.MemberType)
        {
            case MemberTypes.Field:
                return ((FieldInfo)member).FieldType;
            case MemberTypes.Property:
                return ((PropertyInfo)member).PropertyType;
            case MemberTypes.Event:
                return ((EventInfo)member).EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", "member");
        }
    }

你可以将你的修改作为答案发布。 - Ken Kin
6个回答

39

这样怎么样呢:

const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
MemberInfo[] members = type.GetFields(bindingFlags).Cast<MemberInfo>()
    .Concat(type.GetProperties(bindingFlags)).ToArray();

另外,像FastMember这样的库可以很愉快地与字段或属性一起使用,无论成员类型如何,get/set都是相同的。


从 OP:「获取一个简单的单一对象来获取/设置值」。我不确定 MemberInfo 如何实现这一点。 - CrazyCasta
1
@CrazyCasta确实如此;如果您想要那个,您需要走出核心反射 API,因为没有单个共享接口。然而,存在单独的库,这就是为什么我引用了 FastMember 的原因。 - Marc Gravell
1
我真的很想将@CrazyCasta和marc都标记为答案,因为他们都很有帮助,但最终我还是选择了使用扩展方法的MemberInfo。 - Timmerz

11

你似乎已经注意到GetProperties()和GetFields()的返回类型不同。你需要定义一个接口,其中包含GetValue()和SetValue()方法,并使用扩展参数信息(ParameterInfo)和字段信息(FieldInfo)来实现该接口。这可能会作为一个包装器(wrapper)工作:

interface IGetSettable
{
    public void SetValue(
        Object obj,
        Object value,
        Object[] index);
    public Object GetValue(
        Object obj,
        Object[] index);
}

public class ParameterInfoGS : IGetSettable
{
    protected ParameterInfo pi;

    public ParameterInfoExtra(ParameterInfo _pi)
    {
        pi = _pi;
    }

    public void SetValue(
        Object obj,
        Object value,
        Object[] index) {pi.SetValue(obj, value, index);}
    public Object GetValue(
        Object obj,
        Object[] index) {return pi.GetValue(obj, index);}
}

public class FieldInfoGS : IGetSettable
{
    protected FieldInfo pi;

    public FieldInfoExtra(FieldInfo _pi)
    {
        pi = _pi;
    }

    public void SetValue(
        Object obj,
        Object value,
        Object[] index) {pi.SetValue(obj, value, index);}
    public Object GetValue(
        Object obj,
        Object[] index) {return pi.GetValue(obj, index);}
}

public static class AssemblyExtension
{
    public static IGetSettable[] GetParametersAndFields(this Type t)
    {
        List<IGetSettable> retList = new List<IGetSettable>();

        foreach(ParameterInfo pi in t.GetParameters())
            retList.Add(new ParameterInfoExtra(pi));

        foreach(FieldInfo fi in t.GetFields())
            retList.Add(new FieldInfoExtra(fi));

        return retList.ToArray();
    }
}

这将允许您执行GetType().GetParametersAndFields()(即使用标准反射类型)。

5
那个没有 get 和 set 方法。 - CrazyCasta

7
有点晚了,但我想到了以下的解决方案,只需要一个循环,非常好用 ;-)
        MemberInfo[] memberInfos = dotNetType.GetMembers();
        ModelPropertySpec modelPropertySpec;
        foreach (MemberInfo memberInfo in memberInfos)
        {
            Type itemType = null;
            String memberName = memberInfo.Name;
            switch (memberInfo.MemberType)
            {
                case MemberTypes.Property:
                    itemType = dotNetType.GetProperty(memberName).PropertyType;
                    break;
                case MemberTypes.Field:
                    itemType = dotNetType.GetField(memberName).FieldType;
                    break;
            }

            if (itemType != null)
            {
                modelPropertySpec = ParsePropertyType(memberName, itemType);
                modelSpec.Properties.Add(modelPropertySpec.Name, modelPropertySpec);
            }
        }

5
要获取属性或字段,可以说:
var q=
    from it in type.GetMembers(bindingAttr)
    where it is PropertyInfo||it is FieldInfo
    select it;

其中bindingAttr可以是以下内容:

var bindingAttr=
        BindingFlags.NonPublic|
        BindingFlags.Public|
        BindingFlags.Instance;

如果您不想获取非公共成员,请移除BindingFlags.NonPublic。顺便提一下,该查询不是单个调用而是单个语句
要获取属性或字段的值而无需自己进行转换,请使用InvokeMember技巧:
static object GetValue<T>(
        T x, object target) where T:MemberInfo {
    var invokeAttr=(
            x is FieldInfo
                ?BindingFlags.GetField
                :x is PropertyInfo
                    ?BindingFlags.GetProperty
                    :BindingFlags.Default)|
            BindingFlags.NonPublic|
            BindingFlags.Public|
            BindingFlags.Instance;

    return target.GetType().InvokeMember(
        x.Name, invokeAttr, default(Binder), target, null);
}

同样地,要设置数值:

static void SetValue<T>(
        T x, object target, object value) where T:MemberInfo {
    var args=new object[] { value };
    var invokeAttr=(
            x is FieldInfo
                ?BindingFlags.SetField
                :x is PropertyInfo
                    ?BindingFlags.SetProperty
                    :BindingFlags.Default)|
            BindingFlags.NonPublic|
            BindingFlags.Public|
            BindingFlags.Instance;

    target.GetType().InvokeMember(
        x.Name, invokeAttr, default(Binder), target, args);
}

如果你将除了 PropertyInfo 或者 FieldInfo 以外的 MemberInfo 作为第一个参数传递,它就会抛出异常。因为 BindingFlags.Default 没有指定你要做什么。


3

使用DLR(如果您在编译时知道成员名称,则非常简单):

((dynamic)obj).MyFieldOrPropertyName = myValue;

如果您只在运行时知道成员名称,我建议使用FastMember,正如Marc Gravell所建议的那样。

1
我希望有一个类似于这样的东西可以用在我正在处理的项目中,并且我想要一个更好的通用方法来处理它。
我尝试了很多方法,但最终我选择了创建一个通用函数来获取任何类型的成员,然后只需为属性和字段调用它来获取值成员。

获取成员

public static MemberInfo[] GetValueMembers(this Type t, BindingFlags bindings)
{
    MemberInfo[] result = t.GetMembersSlow(bindings, MemberTypes.Field | MemberTypes.Property);
    return result;
}

public static MemberInfo[] GetMemberSlow(
    this Type t, BindingFlags bindings, MemberTypes types
){
    MemberInfo[] result = t.FindMembers(types, bindings, (o, p) => true, null);
    return result;
}

这种方法的性能还不错,虽然不是最好的,但比我尝试过的其他方法要好。使用 IEnumerable<MemberInfo>.Concat()GetProperties()GetMethods() 的结果强制转换的方法相对较慢。

但是,通过执行 GetProperties()GetFields() 并使用 Array.Copy,我得到了非常好的性能,因此我决定编写一个基于您请求的 Type 上所有 GetX() 类型的内容,并将所有结果一起 Array.Copy() 的程序。

public static MemberInfo[] GetMembers(this Type t, BindingFlags bindings, MemberTypes types)
{
    List<MemberInfo[]> resultArrays = new List<MemberInfo[]>();

    MemberTypes[] memberTypes = types.Split();
    MemberInfo[] buffer = null;

    int totalLength = 0;

    foreach (MemberTypes memberType in memberTypes)
    {
        switch (memberType)
        {
            case MemberTypes.Constructor:
                buffer = t.GetConstructors(bindings);
                break;

            case MemberTypes.Event:
                buffer = t.GetEvents(bindings);
                break;

            case MemberTypes.Field:
                buffer = t.GetFields(bindings);
                break;

            case MemberTypes.Method:
                buffer = t.GetMethods(bindings);
                break;

            case MemberTypes.Property:
                buffer = t.GetProperties(bindings);
                break;

            default:
                // maybe do something here? the other values don't really
                // make sense in a GetMembers call, probably ignore them.
                break;
        }

        if (buffer != null && buffer.Length > 0)
        {
            resultArrays.Add(buffer);
            totalLength += buffer.Length;
            buffer = null;
        }
    }

    MemberInfo[] result = new MemberInfo[totalLength];

    int currentIndex = 0;
    foreach (MemberInfo[] resultArray in resultArrays)
    {
        if (resultArray.Length == 0)
            continue;

        Array.Copy(resultArray, 0, result, currentIndex, resultArray.Length);
        currentIndex += resultArray.Length;
    }

    return result;
}

这里有一个稍微隐藏的东西,在 types.Split() 的调用中,我写了一个函数,它接收一个 flags 枚举并返回包含其中所有单独的标志的数组,以便于循环使用。有关 Split() 代码,请参见https://dev59.com/snNA5IYBdhLWcg3wPbOe#72251903

哦,这很重要,一开始我没有理解: MemberInfo 是一个抽象基本类型,所有的 XInfo 类型都是从它派生出来的,并且很容易被转换为和从中转换。所以,要将 ConstructorInfo[] 更改为 MemberInfo[],只需将其分配给一个 MemberInfo[] 变量,或者使用 (MemberInfo[])X as MemberInfo[] 进行转换等。

根据传入的参数不同,此函数将从 Type 中获取与传入绑定/成员类型相关的所有相关的 MemberInfo 数组,从特定函数中单独获取它们,然后将它们全部复制到一个大的 MemberInfo[] 中。正如我提到的,我在 Properties+Fields 上表现优异,但我担心由于这么多 overhead 会减慢速度。

结果我担心是没有必要的,它比我尝试过的任何其他方法都表现出色,差距还不小。这段代码已经经过了边缘测试,并正常工作。我测试了 Forms.GetType() 上剩下的两个 GetMembers(),并且与 Type.FindMembers() 返回的内容一致。

使用成员

最后,我编写了一些帮助实际利用 Properties+Fields 数据的辅助程序。由于 MemberInfo 是抽象的,因此没有方便的方法从成员实例中获取值。

当您需要从 FieldInfoPropertyInfo 中获取信息或其实例的值时,只需创建一个 MemberFactory,将字段或属性信息作为 MemberInfo 传递即可。

如果您需要成员类型,请调用 GetMemberSystemType(),该函数将返回成员的 System.Type

注意: 这可能远远不能处理所有情况,但对于基本成员它是有效的。

public struct MemberFactory
{
    public readonly FieldInfo fieldInfo;
    public readonly PropertyInfo propertyInfo;

    private MemberFactory(MemberInfo memberInfo)
    {
        fieldInfo = memberInfo as FieldInfo;
        propertyInfo = memberInfo as PropertyInfo;

        if (fieldInfo == null && propertyInfo == null)
            throw new ArgumentException("memberInfo must derive from FieldInfo or PropertyInfo", memberInfo.Name);
    }

    public Type GetMemberSystemType()
    {
        Type result = null;

        if (IsProperty)
            result = propertyInfo.PropertyType;
        else if (IsField)
            result = fieldInfo.FieldType;

        return result;
    }

    public bool IsProperty { get => propertyInfo != null; }
    public bool IsField { get => fieldInfo != null; }

    public Member MakeMember(object instance)
    {
        return new Member(instance, this);
    }

    public static MemberFactory Create(MemberInfo memberInfo)
    {
        return new MemberFactory(memberInfo);
    }
}

然后,当您有一个实际的实例需要处理时,只需调用MakeMember()并使用该实例获取一个Member对象来操作该实例。

由于我们需要检查属性和字段,因此在实现中,我只使用了空值条件/空值合并来同时检查它们。我没有测试在MemberFactory上仅检查IsMethod()/IsProperty()是否会对性能产生很大影响,但我认为这样做没问题。

最后,还有一些扩展函数,以便如果您拥有MemberInfo和一个实例,您不必创建MemberFactory/Member即可使用它们,以防您只需要快速访问一个值而不需要进行大量设置。

public class Member
{
    object instance;
    MemberFactory factory;

    public Member(object instance, MemberFactory factory)
    {
        this.instance = instance;
        this.factory = factory;
    }

    public object GetValue()
    {
        return 
            factory.fieldInfo?.GetValue(instance) 
            ?? factory.propertyInfo?.GetValue(instance);
    }

    public void SetValue(object value)
    {
        factory.fieldInfo?.SetValue(instance, value);
        factory.propertyInfo?.SetValue(instance, value);
    }
}

public static void SetValue(this MemberInfo memberInfo, object instance, object value)
{
    MemberFactory.Create(memberInfo).MakeMember(instance).SetValue(value);
}

public static object GetValue(this MemberInfo memberInfo, object instance)
{
    return MemberFactory.Create(memberInfo).MakeMember(instance).GetValue();
}

关于这个问题就是这样了。我需要它来做其他事情,当我在寻求帮助时,这是我遇到的页面之一,所以我记下来了,并在有一些可靠的半测试想法后回来。

启发我进行此项工作的核心思想基本上就是在Member.SetValue()调用中。


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