C# 反射属性顺序

问题描述 投票:0回答:7

我正在使用https://stackoverflow.com/a/531388/528131中的代码成功地从基中检索对象实例的所有属性,问题是派生类型的属性首先被迭代。由于协议的性质,我首先需要基本属性。

x |
y | B
z |
w | A

B 和 A 是类,B 派生自 A。x、y、z 是 B 的属性,w 是 A 的属性

这是A.GetProperties();的方案。正在返回。我需要这个:

w | A
x |
y | B
z |

有什么方法可以准确地按该顺序获取字段吗?

c# reflection
7个回答
18
投票

类型中的字段不是“有序的”。 这些方法中项目的排序是一个实现细节,强烈建议不要依赖它们。

您应该自己订购这些项目,并期望它们可以从任何位置开始,以确保您的程序健壮而不是脆弱。

由于可以要求每个属性声明它的类型,因此您可以在开始时创建一个查找,通过遍历

object 为层次结构中的每个类提供一个编号,从您开始的类型一直到 
BaseType
 
Type
属性并按每个属性的声明类型的查找值排序:

public static IEnumerable<PropertyInfo> GetOrderedProperties(Type type)
{
    Dictionary<Type, int> lookup = new Dictionary<Type, int>();

    int count = 0;
    lookup[type] = count++;
    Type parent = type.BaseType;
    while (parent != null)
    {
        lookup[parent] = count;
        count++;
        parent = parent.BaseType;
    }

    return type.GetProperties()
        .OrderByDescending(prop => lookup[prop.DeclaringType]);
}

5
投票

您所要做的就是通过声明类型进行分组并反转列表

 var publicProperties = typeof(T).GetProperties()
                .GroupBy(p => p.DeclaringType)
                .Reverse()
                .SelectMany(g => g)
                .ToArray();

3
投票

反射子系统的文档表明您不能依赖返回元素的顺序。

也就是说,根据我的经验,元素按照源文件中声明的顺序返回。这在 Mono 或 .NET 的未来修订版上可能是真的,也可能不是。

如果您希望继续执行上述操作,最好的选择是使用

BindingFlags.DeclaredOnly
选项并手动遍历继承层次结构(在子类型之前扫描基类型以使它们按正确的顺序排列)。您应该以这样的方式编写代码,即单个声明类型的属性顺序并不重要(例如按名称对它们进行排序);如果 .NET 框架的行为发生变化,这将使您的代码更加健壮。

Fasterflect 就是这样做的(主要是为了能够过滤掉已被覆盖的虚拟属性)。它还具有帮助程序来获取属性(无论是否已过滤),使用它自己的更强大的

Flags
选择器参数。

如果单个类型中元素的顺序并不重要,您可以像这样获取列表(使用 Fasterflect):

var properties = type.Properties().Reverse().ToList();

您应该意识到,以这种方式反映时(通过遍历并仅获取声明的属性),重写的属性将被多次包含。 Fasterflect 提供了从结果中过滤这些内容的选项:

var properties = type.Properties( Flags.InstancePublic | Flags.ExcludeBackingMembers ).Reverse().ToList();

如果您不想依赖该库,该代码是开源的,因此您可以选择您需要的部分。遍历算法可以在这里看到(第443行)。


2
投票

作为另一种排序方法:

PropertyInfo[] properties = type.GetProperties(...);
Array.Sort(properties, (pi1, pi2) =>
    {
        if (pi1.DeclaringType.IsSubclassOf(pi2.DeclaringType))
           return 1;
        else if  (pi2.DeclaringType.IsSubclassOf(pi1.DeclaringType))
            return -1;
        else
            return 0;
    });

0
投票

正如一些人指出的那样,依赖反射调用可能是一种脆弱的方法。我会使用其他方法来有意确保排序,例如通过应用属性。如果您可以控制您所处理的类,则可以在您的类型上使用预先存在的

DataMember
属性。它有一个简洁的
Order
属性,用于确保类型字段按照指定的顺序序列化(某些系统可能要求其通信协议中的对象遵守字段排序器,.NET 的数据契约序列化借助该属性来实现这一点属性字段)。

但是,我们也可以使用它的语义来实现您的要求(这可能没问题,因为您的任务看起来像是一种序列化形式)。当然,如果您觉得

DataMemberAttribute
不适合使用,您可以自由定义自定义属性类。

粗鲁地,我修改了@Rawling有趣的解决方案,如下所示:

Array.Sort(
    typeof(T).GetProperties(),
    (a, b) =>
    {
        if (a.DeclaringType.IsSubclassOf(b.DeclaringType))
        {
            return -1; // order any property of base class before derived
        }
        else if (b.DeclaringType.IsSubclassOf(a.DeclaringType))
        {
            return 1; // order any property of base class before derived
        }
        else
        {
            // order two properties of the same class 
            // based on order attribute value

            int orderOfA = a.GetCustomAttributes<DataMemberAttribute>().SingleOrDefault()?.Order ?? 0;
            int orderOfB = b.GetCustomAttributes<DataMemberAttribute>().SingleOrDefault()?.Order ?? 0;
            return orderOfB - orderOfA;
        }
    });

如果您想以自定义顺序混合基类和派生属性,则可以使用以下代码片段,只要相互选择基类和派生类中每个属性的属性顺序值即可:

Array.Sort(
    typeof(T).GetProperties(),
    (a, b) =>
    {
        int orderOfA = a.GetCustomAttributes<DataMemberAttribute>().SingleOrDefault()?.Order ?? 0;
        int orderOfB = b.GetCustomAttributes<DataMemberAttribute>().SingleOrDefault()?.Order ?? 0;
        return orderOfB - orderOfA;
    });

0
投票

看起来自 .NET 7 以来我们已经取得了一些进展,顺序是确定性的:

在 .NET 6 及更早版本中,GetProperties 方法不会按特定顺序(例如字母顺序或声明顺序)返回属性。您的代码不得依赖于返回属性的顺序,因为该顺序会有所不同。 但是,从 .NET 7 开始,排序是根据程序集中的元数据排序确定的。

来源:Microsoft Learn,粗体声明是我添加的。


-1
投票

您可以从

PropertyInfo
的实例获取声明类型,并按与
Object
的距离排序。

这就是我的做法:

void Main()
{
    typeof(B).GetProperties()
    .Select((x,i) => new {
        Prop = x,
        DeclareOrder = i,
        InheritanceOrder = GetDepth(x.DeclaringType),
    })
    .OrderByDescending(x => x.InheritanceOrder)
    .ThenBy(x => x.DeclareOrder)
    .Dump();
}

public class A
{
    public string W {get; set;}
}

public class B : A
{
    public string X {get; set;}
    public string Y {get; set;}
    public string Z {get; set;}
}

static int GetDepth(Type t)
{
    int depth = 0;
    while (t != null)
    {
        depth++;
        t = t.BaseType;
    }
    return depth;
}
© www.soinside.com 2019 - 2024. All rights reserved.