【问题标题】:Sequence the Properties in a C# Class对 C# 类中的属性进行排序
【发布时间】:2023-03-04 13:21:01
【问题描述】:

我们需要解析的文件格式如下:

v1|000|sammy|endpoint|blah

这是供应商提供给我们的有序固定宽度格式,因此这 5 个字段中的每一个都映射到类中的特定属性(实际格式具有 >30)。

我想通过对属性应用序列来使用反射来解析它。我可以做到这一点的一种方法是自己编造一些东西 - 编写一个接受单个数字的 Attribute 类,并将该属性应用到具有其序列索引的每个属性,并在 OrderBy 子句中的反射期间查找它。

在 C# 中是否有现有的或更好的方法来执行此操作?例如,是否已经有一个属性?有没有办法在 C# 甚至 MSIL 中询问在类中声明了哪些顺序属性?

【问题讨论】:

  • 这取决于。有一些方法,但不能保证它们会起作用,它们依赖于可能会在较新版本的编译器和可能较新版本的 .NET Framework 中发生变化的实现细节。您想要的解决方案有多可靠?
  • 为什么要使用反射?与仅编写一个知道如何将一系列字符映射到给定属性的加载器类相比,它相对较慢且更复杂。
  • @hvd 我会接受可能对未来编译器更改很脆弱的简单性,只要我可以通过单元测试捕捉到编译器驱动的中断,这对于我能想象的任何解决方案都应该是微不足道的。
  • @ChrisMoschini 在那种情况下,用一个大胖子的免责声明来回答。 :)

标签: c# reflection properties sql-order-by


【解决方案1】:

使用PropertyInfo.MetadataToken 可以看到属性在元数据中出现的顺序。碰巧当前的编译器会使这个顺序与源代码中属性出现的顺序相匹配,所以通过MetadataToken排序,你会得到与源代码中相同的顺序。

免责声明:未来的编译器可能会改变这一点。如果没有理由,它可能不会,但是如果编译器,例如,变成多线程的,它可能需要额外的不必要的努力来保持原始顺序。如果您依赖于此,请确保在 .NET Framework 以这种方式更新时出现硬错误而不是静默运行时损坏。

【讨论】:

    【解决方案2】:

    如果您想使用基于属性的方法,我个人会为此创建一个自定义属性。这不是“标准”操作,因此框架中没有(适当的)属性可用于装饰您的类。

    我的方法可能是一个类级别的属性,它接受一个字符串数组作为列表中每个条目的属性名称,或者类似的东西。

    话虽如此,我质疑基于属性的方法是否是正确的方法。您可能需要某种类型的管理器类来调解这一点,因为需要做一些“反思”过程。让该类管理此处的关系可能更有意义,尤其是因为它已经需要了解您的类层次结构(以便首先构造类)。

    在这一点上,拥有一个可以直接构造对象的自定义类或方法将比尝试使用反射并动态执行此操作具有更好的性能、更易于维护和简单得多。

    【讨论】:

    • 这个概念是我会传递类似 DelimitedData.Parse(str) 的东西,DelimitedData 类查看序列的属性,拆分字符串,并将字段分配给模型以正确的顺序。 +1 用于保留带有显式列表的顺序。不过,您的解决方案确实会加倍工作,但会丢失对该列表中拼写错误的编译时检查。
    【解决方案3】:

    您使用的是 .net 4.0 吗?这似乎正是创建 dynamic keyword 的那种情况。也就是说,似乎顺序和一致性比在任何时间点发生的特定类型更重要,因此您可以通过任何让您满意的规则任意将标题、数据等分配给动态对象,然后将它们拉回使用相同的规则。这也将(大概)允许您不使用反射,这始终是一个优点。

    【讨论】:

    • 我希望稍后将静态属性名称用于值绑定 - 例如,我将此模型传递给记录事件和视图。我也不确定使用动态而不是反射的性能优势 - 我希望 CLR 处理这两种情况的成本相似。
    • 我不能说我曾经直接将使用动态与使用反射进行比较,但是处理动态调用的 DLR 在逻辑上覆盖了 CLR,因此使用 dynamic 关键字根本不应该触及 CLR。真的,这主要是出于好奇。如果你要传递一堆东西,我可能也会倾向于静态属性名称。
    【解决方案4】:

    我建议使用 FileHelpers 之类的东西进行解析。

    【讨论】:

      【解决方案5】:

      现在,如果性能不是一个大问题并且您正在使用反射,那么获取没有属性的映射的一种简单方法是使用 RegEx 使用组进行解析。与此实现类似: Read fixed width record from text file

      使用正则表达式,例如:

      "^(?<Field1>.{6})(?<Field2>.{16})(?<Field3>.{12})"
      

      由于您可以自己定义组名,因此您可以明智地选择名称以与您的属性名称完全匹配,这样就可以使用反射自动映射,而无需使用属性。

      编辑: 鉴于您最终会在字符串中使用属性名称,这不会非常“重构友好”,我强烈建议对此进行彻底的单元测试,以确保在产生不匹配时重命名您的属性会破坏测试。

      【讨论】:

      • 另一个好建议。可以通过在构建上述正则表达式时只使用一次反射来解决编译安全问题,一旦正则表达式被编译,转换应该相当快。我的意思是你可以用几个小表达式来构建它,并在此处获取它们的名称:stackoverflow.com/questions/3778598/… 但是,这可能足够复杂,只需使用此处提出的 FileHelper 解决方案即可。
      【解决方案6】:

      您可以考虑实现类似于Google's Protocol Buffers 的东西。

      目前没有 C# 实现(据我所知),但提供的文档非常好,应该可以为您提供一些比反射慢得多且通常复杂得多的想法。

      【讨论】:

        【解决方案7】:

        这里当然有很多可能的答案,所以这是我遇到的一个马马虎虎的答案:

        System.ComponentModel.DataAnnotations 中有一个名为 ColumnAttribute 的现有属性(在 .Net 4.5+ 中,它已移至 System.ComponentModel.DataAnnotations.Schema):

        http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.schema.columnattribute(v=vs.110)

        你可以像这样使用它:

        [Column(Order=1)]
        public string Version { get; set; }
        
        [Column(Order=2)]
        public string Id { get; set; }
        

        但是,如果固定宽度格式发生变化,这显然很烦人 - 如果说在开头添加了一个字段,您必须手动进入并更改您输入的 30 多个序数。由于在这种情况下我们无法控制格式,并且未来的版本可能会频繁出现,因此最好从类中输入的顺序属性中找到具有隐含顺序的内容。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-26
          • 2015-06-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-20
          • 1970-01-01
          相关资源
          最近更新 更多