`Type.GetProperties`属性顺序
精简版
Type.GetProperties
的MSDN文档声明它返回的集合不保证按字母顺序或声明顺序,但运行简单测试表明通常它以声明顺序返回。 是否有特定情况,您知道不是这种情况? 除此之外,建议的替代方案是什么?
详细版本
我意识到Type.GetProperties
的MSDN文档说明:
GetProperties方法不以特定顺序返回属性,例如按字母顺序或声明顺序。 您的代码不得依赖于返回属性的顺序,因为该顺序会有所不同。
因此无法保证方法返回的集合将以任何特定方式进行排序。 根据一些测试,我发现相反的返回属性按照它们在类型中定义的顺序出现。
例:
class Simple { public int FieldB { get; set; } public string FieldA { get; set; } public byte FieldC { get; set; } } class Program { static void Main(string[] args) { Console.WriteLine("Simple Properties:"); foreach (var propInfo in typeof(Simple).GetProperties()) Console.WriteLine("\t{0}", propInfo.Name); } }
输出:
Simple Properties: FieldB FieldA FieldC
一个这样的情况,这只是略有不同的是当有问题的类型的父母也有属性时:
class Parent { public int ParentFieldB { get; set; } public string ParentFieldA { get; set; } public byte ParentFieldC { get; set; } } class Child : Parent { public int ChildFieldB { get; set; } public string ChildFieldA { get; set; } public byte ChildFieldC { get; set; } } class Program { static void Main(string[] args) { Console.WriteLine("Parent Properties:"); foreach (var propInfo in typeof(Parent).GetProperties()) Console.WriteLine("\t{0}", propInfo.Name); Console.WriteLine("Child Properties:"); foreach (var propInfo in typeof(Child).GetProperties()) Console.WriteLine("\t{0}", propInfo.Name); } }
输出:
Parent Properties: ParentFieldB ParentFieldA ParentFieldC Child Properties: ChildFieldB ChildFieldA ChildFieldC ParentFieldB ParentFieldA ParentFieldC
这意味着GetProperties
方法在发现属性时从下往上GetProperties
inheritance链。 那很好,可以这样处理。
问题:
- 是否存在我所遗漏的描述行为不同的特定情况?
- 如果不建议视订单而定,那么推荐的方法是什么?
一个看似显而易见的解决方案是定义一个自定义属性,该属性指示属性应该出现的Order
(类似于DataMember
属性上的Order
属性)。 就像是:
public class PropOrderAttribute : Attribute { public int SeqNbr { get; set; } }
然后执行如下:
class Simple { [PropOrder(SeqNbr = 0)] public int FieldB { get; set; } [PropOrder(SeqNbr = 1)] public string FieldA { get; set; } [PropOrder(SeqNbr = 2)] public byte FieldC { get; set; } }
但正如许多人所发现的,如果你的类型有100个属性而你需要在前2个之间添加一个属性,这就成了一个严重的维护问题。
UPDATE
此处显示的示例仅用于演示目的。 在我的特定场景中,我使用类定义消息格式,然后遍历类的属性并获取其属性以查看消息中的特定字段应如何解组。 消息中字段的顺序很重要,因此我的类中的属性顺序需要很大。
它目前通过迭代GetProperties
的返回集合来工作,但由于文档声明不推荐我,我想了解为什么以及我有什么其他选项?
订单根本无法保证; 该发生的总会发生。
可能发生变化的明显案例:
- 任何实现ICustomTypeDescriptor的东西
- 任何有TypeDescriptionProvider的东西
但是一个更微妙的案例:部分类。 如果一个类被拆分为多个文件,则根本不会定义它们的使用顺序。 请参阅正式定义的部分类中的“文本顺序”吗?
当然,即使对于单个(非部分)定义也没有定义; p
但想象一下
档案1
partial class Foo { public int A {get;set;} }
档案2
partial class Foo { public int B {get;set:} }
A和B之间没有正式的声明命令。请参阅链接的post,看看它是如何发生的。
重新编辑; 最好的办法是分别指定编组信息; 一种常见的方法是使用一个采用数字顺序的自定义属性,然后用它来装饰成员。 然后,您可以根据此号码进行订购。 protobuf-net做了非常相似的事情,坦率地说,我建议在这里使用现有的序列化库:
[ProtoMember(n)] public int Foo {get;set;}
其中“n”是整数。 特别是在protobuf-net的情况下,还有一个API可以单独指定这些数字,这在类型不在您的直接控制之下时非常有用。
依赖于明确记录为无法保证的实施细节是灾难的一种方法。
“推荐方法”取决于您拥有这些属性后要对这些属性执行的操作。 只是在屏幕上显示它们? MSDN文档按成员类型(属性,字段,函数)分组,然后在组内按字母顺序排列。
如果您的消息格式依赖于字段的顺序,那么您需要:
-
在某种消息定义中指定预期的顺序。 如果我记得,Google 协议缓冲区就是这样工作的 – 在这种情况下,将.proto文件中的消息定义编译成代码文件,以便用于您正在使用的任何语言。
-
依赖于可以独立生成的订单,例如字母顺序。
我使用自定义属性自己添加必要的元数据(它与REST服务一起使用,它使用并返回CRLF分隔的Key = Value对。
首先,自定义属性:
class ParameterOrderAttribute : Attribute { public int Order { get; private set; } public ParameterOrderAttribute(int order) { Order = order; } }
然后,装饰你的类:
class Response : Message { [ParameterOrder(0)] public int Code { get; set; } } class RegionsResponse : Response { [ParameterOrder(1)] public string Regions { get; set; } } class HousesResponse : Response { public string Houses { get; set; } }
将PropertyInfo转换为可排序的int的便捷方法:
private int PropertyOrder(PropertyInfo propInfo) { int output; var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault(); output = orderAttr != null ? orderAttr.Order : Int32.MaxValue; return output; }
更好的是,写作是一个扩展:
static class PropertyInfoExtensions { private static int PropertyOrder(this PropertyInfo propInfo) { int output; var orderAttr = (ParameterOrderAttribute)propInfo.GetCustomAttributes(typeof(ParameterOrderAttribute), true).SingleOrDefault(); output = orderAttr != null ? orderAttr.Order : Int32.MaxValue; return output; } }
最后,您现在可以使用以下命令查询Type对象:
var props = from p in type.GetProperties() where p.CanWrite orderby p.PropertyOrder() ascending select p;
对于它的价值,按MetadataToken排序似乎对我有用。
GetType().GetProperties().OrderBy(x => x.MetadataToken)
原始文章: http : //www.sebastienmahe.com/v3/seb.blog/2010/03/08/c-reflection-getproperties-kept-in-declaration-order/
1:
我花了最后一天来解决MVC 3项目中的问题,这一切都归结为这个特定的问题。 它基本上依赖于整个会话中的属性顺序相同,但在某些情况下,一些属性切换位置,弄乱了网站。
首先,代码调用Type.GetProperties()
来定义动态jqGrid表中的列名,在这种情况下,每个page_load
会出现一次。 随后的时间调用Type.GetProperties()
方法是为了填充表的实际数据,在一些罕见的情况下,属性切换位置并完全搞砸了表示。 在某些情况下,站点依赖于分层子网格的其他属性被切换,即您无法再看到子数据,因为ID列包含错误数据。 换句话说: 是的,这肯定会发生 。 谨防。
2:
如果您需要在整个系统会话中保持一致的顺序,但是并非所有会话的顺序完全相同,则变通方法很简单 :将您从Type.GetProperties()
获得的PropertyInfo[]
数组存储为webcache中的值或带有字典的type(或typename)作为缓存/字典键。 随后,每当您要执行Type.GetProperties()
,请将其替换为HttpRuntime.Cache.Get(Type/Typename)
或Dictionary.TryGetValue(Type/Typename, out PropertyInfo[])
。 通过这种方式,您可以保证始终获得您第一次遇到的订单。
如果你总是需要相同的订单(即所有系统会话)我建议你将上面的方法与某种类型的配置机制结合起来,即在web.config / app.config中指定顺序,对你得到的PropertyInfo[]
数组进行排序根据指定的顺序从Type.GetProperties()
,然后将其存储在缓存/静态字典中。