我正在使用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 |
有什么方法可以准确地按该顺序获取字段吗?
类型中的字段不是“有序的”。 这些方法中项目的排序是一个实现细节,强烈建议不要依赖它们。
您应该自己订购这些项目,并期望它们可以从任何位置开始,以确保您的程序健壮而不是脆弱。
由于可以要求每个属性声明它的类型,因此您可以在开始时创建一个查找,通过遍历
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]);
}
您所要做的就是通过声明类型进行分组并反转列表
var publicProperties = typeof(T).GetProperties()
.GroupBy(p => p.DeclaringType)
.Reverse()
.SelectMany(g => g)
.ToArray();
反射子系统的文档表明您不能依赖返回元素的顺序。
也就是说,根据我的经验,元素按照源文件中声明的顺序返回。这在 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行)。
作为另一种排序方法:
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;
});
正如一些人指出的那样,依赖反射调用可能是一种脆弱的方法。我会使用其他方法来有意确保排序,例如通过应用属性。如果您可以控制您所处理的类,则可以在您的类型上使用预先存在的
DataMember
属性。它有一个简洁的 Order
属性,用于确保类型字段按照指定的顺序序列化(某些系统可能要求其通信协议中的对象遵守字段排序器,.NET 的数据契约序列化借助该属性来实现这一点属性字段)。
但是,我们也可以使用它的语义来实现您的要求(这可能没问题,因为您的任务看起来像是一种序列化形式)。当然,如果您觉得
DataMemberAttribute
不适合使用,您可以自由定义自定义属性类。
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;
});
看起来自 .NET 7 以来我们已经取得了一些进展,顺序是确定性的:
在 .NET 6 及更早版本中,GetProperties 方法不会按特定顺序(例如字母顺序或声明顺序)返回属性。您的代码不得依赖于返回属性的顺序,因为该顺序会有所不同。 但是,从 .NET 7 开始,排序是根据程序集中的元数据排序确定的。
来源:Microsoft Learn,粗体声明是我添加的。
您可以从
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;
}