【问题标题】:Does GetCustomAttributes() preserve the attribute order in .NET?GetCustomAttributes() 是否保留 .NET 中的属性顺序?
【发布时间】:2009-01-26 14:47:53
【问题描述】:

标题几乎说明了一切。当我通过我的类进行一些反思时,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 出现的顺序是什么?在?)
  • 这就是为什么我将它们称为“提示”。我意识到使用这个模型我无法避免这样的冲突。事实上,由于应用程序是可插件的,我不可能有一个集中的菜单定义。

标签: .net reflection attributes enumeration


【解决方案1】:

文件中元素的词法顺序绝对不保证在生成的 CIL 程序集中以任何方式保持不变,也不保证在反射返回的结果中得到尊重。在同一个应用程序域内的重复调用中,甚至不能保证此顺序相同!

请注意,MS 过去在反射的其他部分中破坏了这种顺序(注意到他们这样做实际上给他们的一些代码造成了一些问题),因此即使它恰好在此刻起作用,也没有什么在未来或在不同的平台上阻止这种破坏。

考虑更改您的属性模型以允许直接表达语义信息,而不是依赖于排序。

【讨论】:

    【解决方案2】:

    我会在 MenuItemAttribute 构造函数中添加一个额外(可选)值,即“订单”或“优先级”:

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

    我并不是说它会很漂亮,但它可以有效地让您指定排序。

    【讨论】:

    • 这仅指定最后一级层次结构的顺序。这样我就无法指定“客户”和“酒吧”之间的关系。
    • 好的。我怀疑我当时还没有完全理解这个问题。考虑到所涉及的复杂性,也许属性不是最好的方法?难道你不能只在程序集中嵌入一个文件,整个树以适当的顺序排列吗?
    • 嗯,这就是我想要避免的。我想让菜单修改尽可能简单。去中心化的属性看起来不错。事实上,我什至已经为 VS2008 制作了一个表单模板,默认包含该属性。
    • 如果我让菜单集中,那么添加一个新表单(在 99% 的情况下需要一个菜单​​项)将变得更加困难。
    • 嗯,它只是修改一个文件。这也意味着通过查看文件而不是在 每个 形式中查找属性,更容易了解您的整体菜单结构。跨度>
    【解决方案3】:

    很遗憾,您不能保证顺序与您指定的顺序相同。

    编辑:一种解决方法

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

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

    这是一个可能的解决方案:

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

    【讨论】:

    • 是的,你误会了。 :) 当应用了多个 MenuItem 属性时,问题是对字符串进行排序。我必须在所有项目中订购第一个字符串,然后在所有项目中订购第二个字符串,等等。
    【解决方案4】:

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

    例如

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

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

    【讨论】:

    • 这是我想要避免的。现在添加一个新表单就像使用我制作的模板(包括此属性)时单击鼠标三下。
    猜你喜欢
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    相关资源
    最近更新 更多