【问题标题】:When to use: Tuple vs Class in C# 7.0何时使用:C# 7.0 中的元组与类
【发布时间】:2017-11-22 20:30:51
【问题描述】:

在使用元组之前,我曾经创建一个类及其变量,然后从该类创建对象,并使该对象成为某些函数的返回类型。

现在,使用元组,我可以做同样的事情,在 C# 7.0 中,我们可以为元组属性分配易于理解的名称(在此之前,它是 item1item2 等)

所以现在我想知道,我什么时候使用元组,什么时候在 C# 7.0 中创建一个类?

【问题讨论】:

  • 请注意,元组元素名称主要是设计时属性,在编译的 IL 中不存在。因此,如果您关心这一点,那么您还有另一个适当课程的理由。
  • @OrkhanAlikhanov 虽然这将是一个正确的重复目标,但我同意 OP 的观点,即现在 C# 7 时代已经改变,所以 2012 年和 2010 年的答案现在只部分适用。
  • @poke 不是一个有效的重复目标,那个问题是关于 System.Tuple,这个问题是关于新的 System.ValueTuple,它在 C# 7 中有特殊的语法糖。
  • @Theraot 你确实意识到我在批评结束这个问题的建议,对吧?我知道这个问题是关于什么的,这就是为什么我建议保持开放状态。
  • @Theraot 哦,我在将这个问题解释为引用 System.Tuple 时犯了同样的错误,因为它说...“Tuple”。如果您确信它实际上指的是其他东西,那么您应该对其进行编辑以使其更清晰。假设你是正确的,我恢复了我的关闭。

标签: c# tuples c#-7.0 valuetuple


【解决方案1】:

由于这个答案在这里引起了一些人的困惑,我应该澄清一下 - 根据问题 - 这里对“元组”的所有引用都是指 C# 7 和 in 的 ValueTuple 类型和新的元组语法糖特性没有办法引用旧的 System.Tuple 引用类型。

所以现在我想知道,什么时候应该使用元组,什么时候应该在 c# 7.0 中创建一个类?

只有你才能真正回答这个问题,因为它真的取决于你的代码。

但是,您可以遵循一些指导方针和规则来指导您在它们之间进行选择:

元组是值,因此是按值复制的,而不是按引用复制的。

大多数时候,这应该不是问题。但是,如果您正在传递大型结构的元组,这可能会对性能产生影响。不过,可以使用 Ref locals/returns 来解决这些性能问题。

另外,由于它们是值,远程修改副本不会更改原始副本。这是一件好事,但可能会吸引一些人。

元组元素名称不持久

给元素的名称由编译器使用,并且(在大多数情况下)在运行时不可用。这意味着不能使用反射来发现它们的名称;它们不能动态访问,也不能在 razor 视图中使用。

这也是 API 的一个重要考虑因素。从方法返回的元组是关于编译后名称可发现性规则的例外。编译器将属性添加到保存元组名称信息的方法中。这意味着您可以安全地从一个程序集中的公共方法返回一个元组,并在另一个程序集中访问它的名称。

元组是轻量级的

元组比类型更容易编写,因为它们不那么冗长,并且声明可以“内联”(即在使用点声明)。例如,这在声明返回多个值的方法时非常有效。

但是,由于它们是在使用时声明的,如果您有 MethodA 调用 MethodB 并调用 MethodC 并且每个返回一个元组,则您需要在每个阶段重新定义元组。没有 (yet) 方法可以创建元组的别名并在多个方法中重复使用它。

使用常识

对于您可能考虑使用元组的任何情况:只需问自己一个问题:“元组是否会简化此处的代码”。如果答案是“是”,则使用一个。这最终是使用元组还是自定义类的首要考虑因素。

【讨论】:

  • ValueTuple 是值类型。 Tuple是引用类型。如此简单的Tuple 不会被引用复制。你应该用粗体字提到它,因为它现在很混乱
  • @Szer,问题是关于 C# 7 的元组,所以我不明白为什么会有任何混淆。但我已经更新了问题以澄清这一点。
  • @DavidArno 当我第一次开始使用 C#7 时,我并不清楚新的元组语法是一种新类型还是只是语言支持。
  • 我很惊讶元组元素名称不能在 Razor 视图中使用。 Razor 文件不是转换成 C# 然后编译的吗?如果是这样,他们应该从元数据/IL 中看到元组元素名称,就像任何客户端编译一样。
  • @JulienCouvreur,据我所知,这是剃刀工具的限制。但是,从github.com/aspnet/Razor/issues/1046 看来,这个问题现在要么已修复,要么很快就会发布修复程序。
【解决方案2】:

一般来说,命名类在系统设计中具有一定的意义。它们也更冗长。例如,您可能有一个名为MediaFileOpener 的类。我们知道这个类的作用对设计很重要——我们正在处理媒体文件!

当没有设计意义并且您只需要一个轻量级的数据传输对象 (DTO) 来移动信息时,就会使用匿名类型和元组。

通常,如果您的类需要一些文档来描述它的用途,或者如果它提供了一些行为,请使用完整的类。如果您只需要临时存储或某种分组,请使用元组。考虑一种情况,您希望从异步方法返回多个值。 Tuple 就是为了解决这个问题而设计的。

【讨论】:

    【解决方案3】:

    使用类

    如果您的对象是在整个应用程序中广泛使用的实体,并且还存储在某种持久性存储中,例如关系数据库(SQL Server、MySQL、SQLite)、NoSQL 数据库或缓存(Redis、Azure DocumentDB)或即使是简单的文本文件或 CSV。

    是的,任何持久化的东西都应该有自己的类。

    使用元组

    如果您的对象是短暂的,对您的应用程序没有特殊意义。例如,如果您需要快速返回一对坐标,最好有这样的东西:

    (double Latitude, double Longitude) getCoordinates()
    {
        return (144.93525, -98.356346);
    }
    

    比定义一个单独的类

    class Coordinates
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
    }
    

    元组将节省您使用new在堆上分配内存的时间来进行如此简单的操作。

    另一个我发现元组有用的时候是对某些操作数执行多个数学运算时

    (double Result1, double Result2, double Result3) performCalculations(int operand1, int operand 2)
    

    在这种情况下定义一个类是没有意义的。不管是什么计算的结果都不属于一个类。因此,替代方法是使用 out 变量,但我相信元组更具表现力并提高了可读性。

    【讨论】:

    • 我会将Coordinates 定义为struct 而不是class
    • 坐标是不使用元组的一个很好的例子,因为它很常见,你可能一直需要它。此外,您可以重载 ToString 以提供可读的输出(顺便说一下,元组的默认 ToString 在这里已经很好地工作了),并且重载 Equals/GetHashCode 也可以进行相等比较。
    • 恕我直言,这似乎是一个非常固执的回应,带有模糊的 oop 设计指南。我不认为 oop 与此有任何关系(可以将匿名类放入您的两个类别中)。此外,添加数据库级别的持久性与这些考虑无关。
    【解决方案4】:

    通常,当您的对象要在其他地方使用时,或者如果它代表您领域中的真实对象或概念时,您希望有一个类。您可能会创建一个类来表示汽车或汽车商店,而不是元组。

    另一方面,有时您只想从一个方法中返回几个对象。也许它们并不代表什么特别的东西,只是你需要在那个特定的方法中将它们一起返回。有时,即使它们确实代表了您域中的概念(例如,您返回 (Car, Store),它可以表示为 Sale 对象),您实际上也不会在任何地方使用它们——您只是移动数据。在这些情况下,可以使用元组。

    现在,具体谈谈 C#,您还应该知道一件事。 C# 7 的元组类型实际上是ValueTuple,它是一个结构体。与作为引用类型的类不同,结构是值类型。您可以在msdn 上阅读更多相关信息。最重要的是,要知道它们可能涉及大量复制,所以要小心。

    【讨论】:

      【解决方案5】:

      我认为这将成为一个经常被问到的问题。目前没有关于何时使用新值元组与类的“最佳实践”。

      不过,what came up during previous conversations about the previous versions of tuples vs a class 值得一读。

      在我看来,值元组应该只使用最少的值,最多不超过三个值。我认为这在“在不需要课程的情况下返回一些值”和“可怕的价值观混乱”之间取得了很好的平衡。如果要返回的值超过三个,请创建一个类。

      我也绝不会使用元组从消费者必须使用的面向公众的 API 返回。同样,只需使用一个类。

      这是我使用过的一些真实世界的代码:

      public async Task<(double temperature, double humidity, string description)> DownloadTodaysForecast()
      

      只要我想返回更复杂的数据,我就创建一个类。

      【讨论】:

        【解决方案6】:

        我想首先提到 C# 已经支持 anonymous types。哪些是引用类型。因此,您已经有了一个合适的替代方法来创建命名类。

        命名类的一个优点是更容易重用(例如,如果您需要在多个地方使用相同的类型)和文档。由于匿名类型是匿名的,因此只有可以使用var 才能获取键入给它的变量,这限制了匿名类型有用的上下文(例如,您不能将它们用作字段类型、返回类型或参数类型)。

        当然,您可以使用System.Tuple 克服匿名类型的一些限制。这也是一种引用类型,您可以显式使用它。缺点是它缺少成员的自定义名称。


        C# 7 元组 (ValueTuple) 可以被认为类似于匿名类型。第一个区别是它们是值类型。这意味着只要这些元组保持在本地范围内或在堆栈中移动(这是匿名类型的常见用法,由于其局限性),它们就会具有性能优势。

        第二个区别是新语法允许元组出现在比匿名类型更多的地方,如你所知,你有语法糖来定义返回类型为ValueTuple(使用匿名类型时你必须返回@987654331 @)。

        第三个区别是ValueTuple 支持开箱即用的解构。引用What’s New in C# 7.0

        另一种使用元组的方法是解构它们。解构声明是一种用于将元组(或其他值)拆分为其部分并将这些部分单独分配给新变量的语法:

        (string first, string middle, string last) = LookupName(id1); // deconstructing declaration
        WriteLine($"found {first} {last}.");
        

        您还可以通过添加Deconstruct method 来处理自定义类型。


        摘要:

        • ValueTuple 在 C# 7.0 中有语法糖,应考虑到可读性。
        • ValueTuple 是一个值类型。使用 classstruct 之间的所有优缺点均适用。
        • ValueTuple 可以显式使用(带或不带语法糖),使其具有System.Tuple 的多功能性,同时保留命名成员。
        • ValueTuple 支持解构。

        鉴于它必须是语法糖,我会说选择ValueTuple 的更有力的论据与选择struct 的论据相同。这对于主要存在于堆栈中的小型、不可变类型是理想的(因此您不需要大量装箱和拆箱)。

        ValueTuple 与完整的结构进行比较,考虑到语法糖,我建议默认使用ValueTuple,除非您需要explicit layout 或者您需要为其添加方法。

        我还想说句法糖不一定能提高可读性。主要原因是您没有命名类型,而类型的名称为代码提供了意义。除此之外,您还可以将文档添加到 structclass 声明中,以便于理解。

        总而言之,ValueTuple 真正出色的情况是从一个方法返回多个值。在这种情况下,无需创建新的out 参数。并且使用的ValueTuple 的文档可以存在于方法的文档中。如果您发现需要对 ValueTuple 执行其他操作(例如定义扩展方法),我建议您考虑创建一个命名类型。

        【讨论】:

          【解决方案7】:

          我会避免使用元组作为公共方法的返回类型。在这种情况下,我更愿意定义一个类或结构。

          【讨论】:

          • 您能解释一下原因吗?
          • @Fütemire Google“缺少抽象”或“原始痴迷”设计气味。
          • @MilosMrdovic 谢谢,我会调查的。
          • 强烈同意。命名元组为库的公共接口提供了一个弱域定义。我想如果你将它们隐藏在你的内部以用于一次性场景是很好的,但任何公开暴露的东西,根据定义,都应该被使用。定义一个适合领域的抽象!
          【解决方案8】:

          元组旨在表示多个值,例如当方法打算返回多个值时。 C# 7 中的元组支持使用System.ValueTuple&lt;...&gt; 实例来表示该组值。这些值的名称仅在使用它们的上下文中有效,并且不强制执行。

          类旨在表示具有多个属性的单个值。

          【讨论】:

            【解决方案9】:

            当您想将多个值(可以是不同的类型)组合到一个对象中而不创建自定义类时,元组是一个很好的选择。在这种情况下,Tuple 将是一个快速且完美的选择。

            【讨论】:

              【解决方案10】:

              我最近比较了一个案例的性能差异,该案例涉及一个包含 2 个(或 4 个)基元的元组和一个包含 2 个(或 4 个)相同基元类型的类。我在这里分享以防万一。

              【讨论】:

              • 结果如何?
              • @GrahamLaight 根据屏幕截图,在初始化和字段访问方面,使用 Tuple 比使用具有相同字段类型集的类要快得多。此外,字段数越多,差异越大。
              【解决方案11】:

              对于只使用一次的快速代码,请使用元组。如果必须维护代码,请使用类。查看代码并看到如下表达式令人沮丧:

              if (t.Item4 == x.Item3)
              

              【讨论】:

                猜你喜欢
                • 2018-11-11
                • 2018-05-30
                • 2017-05-01
                • 2018-06-16
                • 1970-01-01
                • 2017-04-11
                • 1970-01-01
                • 1970-01-01
                • 2015-11-28
                相关资源
                最近更新 更多