在.NET中,GetCustomAttributes()方法是否保留属性顺序?

17

标题已经说得很清楚了。当我在通过我的类进行一些反射时,MemberInfo.GetCustomAttributes() 方法是否会保留成员上属性的顺序?官方文档并没有明确说明。


如果你想知道为什么我需要这个,这里是完整的解释。它很冗长,对于现在提出的问题来说并不需要,但也许有人能想出一个不需要依靠属性枚举顺序的更好的解决方案来解决这个更大的问题。

我正在尝试为一个(ASP.NET)应用程序制作一个灵活的框架,该应用程序预计将具有相当长的生命周期。在此过程中,它将获得许多表单,这些表单必须从菜单中访问。为了让开发人员更轻松地使用,我创建了一个 MenuItemAttribute,你可以将其应用于表单的类。这个属性在它的构造函数中有无限数量的字符串参数,允许开发人员指定表单在菜单中的位置。一个典型的用法示例可能是类似于 [MenuItem("Company", "Clients", "Orders")] 这样的代码,这意味着菜单上应该有一个名为“公司”的项,在其下面有一个名为“客户”的项,在其下面有一个名为“订单”的项,单击它就可以打开表单。如果需要,一个单独的表单可以有几个这样的属性,那么它将可以从菜单中的多个位置访问。

显然,整个菜单是通过枚举我程序集中的所有类并搜索此属性来在运行时构建的。然而,最近我收到了一个请求,要求菜单项按预定义的方式排序。具有相关功能的表单应该在菜单中相邻。请注意,这不是按字母顺序排序,而是由开发人员指定的预定义顺序。

这就带来了问题 - 如何在这些属性中指定顺序?由于一个 MenuItemAttribute 描述了整个层次结构,因此顺序规范也应包括整个(或至少一部分)层次结构的顺序号码。仅针对层次结构较低的阶段的排序编号是不够的。

我可以创建另一个属性-MenuItemOrderHintAttribute,但是当存在多个MenuItemAttribute的情况下,这将带来问题。因此,出现了最初的问题。

我也可以扩展MenuItemAttribute以使用两个数组或一组对,但那会使语法变得复杂。最后的想法是使字符串具有特殊格式,但在我看来那会很混乱。


好的,我有另一个想法。让我们使用Jon Skeet建议的排序方式。这将允许为层次结构的最后一级指定顺序,而不是更高的级别。但我可以修改属性,以便它不仅适用于类,还适用于程序集本身。在这种情况下,菜单项将不具有关联的表单。在程序集级别上,这些属性可以用于指定在层次结构的较高级别之间的排序。

这是集中式和分散式菜单系统之间的权衡。有什么想法认为这是个坏主意吗?


这种权衡避免了在更高的层次上处理冲突值的麻烦(例如,MenuA说Bar出现在2,MenuB说Bar出现在4,MenuC说Baz出现在3。那么Bar和Baz以什么顺序出现?) - Damien_The_Unbeliever
这就是为什么我会称它们为“提示”的原因。我意识到,使用这种模型我无法避免这样的冲突。实际上,由于应用程序支持插件,我不可能拥有集中式的菜单定义。 - Vilx-
4个回答

18
文件中元素的词汇顺序在生成的CIL程序集中绝对不能保证以任何方式持久存在,也不能保证在Reflection返回的结果中被遵守。即使在同一应用程序域内重复调用时,此排序甚至不保证相同!
请注意,微软过去曾在反射的其他部分中破坏了这种排序(他们指出这实际上会导致一些代码出现问题),因此即使目前可行,也没有阻止未来或在不同平台上发生故障的事情。
考虑更改您的属性模型,以允许直接表达语义信息,而不是依赖顺序。

8

我会在MenuItemAttribute构造函数中添加一个额外的(可选)值,该值为“order”或“priority”:

[MenuItem(0, "Company", "Clients", "Orders")]
[MenuItem(1, "Foo", "Bar", "Baz")]

我并不是说这会很好看,但它可以有效地让你指定排序方式。

这只指定了层次结构的最后一级顺序。这样我就无法指定“客户”和“酒吧”之间的关系。 - Vilx-
好的。我怀疑我还没有完全理解问题。考虑到涉及到的复杂性,也许属性不是最好的方法?你不能只在程序集中嵌入一个文件,其中整个树以适当的顺序排列吗? - Jon Skeet
那就是我想要避免的。我希望尽可能地简化菜单修改过程。分散的属性看起来不错。事实上,我甚至为VS2008制作了一个默认包含此属性的表单模板。 - Vilx-
如果我将菜单居中,那么添加新表单(99%的情况下需要一个菜单项)将变得更加困难。 - Vilx-
2
嗯,它只是修改单个文件。这也意味着仅通过查看该文件,而不是在每个表格中查找属性,就可以更容易地了解您的整体菜单结构的想法。 - Jon Skeet
那是真的。不过,你也可以直接运行应用程序看看。 :) - Vilx-

1

很遗憾,您无法保证顺序与指定顺序相同。

编辑:解决方法

免责声明:如果我误解了您的最终问题,我提前道歉。

听起来好像您需要能够将n个字符串传递给MenuItemAttribute,并且让MenuItemAttribute按属性构造函数中开发人员输入的顺序保持这些字符串的顺序。

以下是一个可能的解决方案:

让MenuItemAttribute使用 LinkedList & lt; String> 来维护其状态。 然后,您可以在构造函数中迭代 params String [] 并将它们添加到 LinkedList & lt; String> 中,以保持顺序。


是的,你误解了。 :) 问题在于当应用多个MenuItem属性时对字符串进行排序。我必须按所有项目中的第一个字符串排序,然后按所有项目中的第二个字符串排序,依此类推。 - Vilx-

0

我觉得你的问题实际上源于表单指定它们在菜单中出现的位置,这意味着你正在尝试通过组合程序集中的所有表单来构建菜单。如果你将菜单的结构与任何表单分开指定,并从对应菜单项的类定义的各种属性/特性解析表单,可能会更容易。

例如:

public class MenuItem
{
    string Text { get; }
    Type FormType { get; }
    ICollection<MenuItem> SubItems { get; }
}

然后当选择菜单项时,以某种方式解析表单并显示它。这种方法的缺点是任何新表单都需要在指定菜单结构的代码以及表单本身上进行更改,但这将是相当小的...


这就是我想要避免的。现在,使用我制作的模板(其中包括此属性),添加新表单只需要三次鼠标点击。 - Vilx-

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