在匿名类型和元组类型之间进行选择Choosing between anonymous and tuple types

选择适当的类型需要考虑其可用性和性能,还要与其他类型相比较并进行权衡。Choosing the appropriate type involves considering its usability, performance, and tradeoffs compared to other types. 匿名类型自 C# 3.0 即可使用,而泛型 System.Tuple<T1,T2> 类型是随 .NET Framework 4.0 引入的。Anonymous types have been available since C# 3.0, while generic System.Tuple<T1,T2> types were introduced with .NET Framework 4.0. 自那时起,通过语言级别支持引入了多个新选项,例如 System.ValueTuple<T1,T2>(它名副其实,提供具有匿名类型灵活性的值类型)。Since then new options have been introduced with language level support, such as System.ValueTuple<T1,T2> - which as the name implies, provide a value type with the flexibility of anonymous types. 在本文中,你将了解何时应选择一种类型而不是另一种。In this article, you'll learn when it's appropriate to choose one type over the other.

可用性和功能Usability and functionality

匿名类型是在 C# 3.0 中随语言集成查询 (LINQ) 表达式引入的。Anonymous types were introduced in C# 3.0 with Language-Integrated Query (LINQ) expressions. 使用 LINQ,开发人员通常会将查询结果投影到匿名类型中,这些类型保存着来自其所处理对象的几个选择属性。With LINQ, developers often project results from queries into anonymous types that hold a few select properties from the objects they're working with. 请考虑以下示例,它实例化 DateTime 对象的一个数组,然后循环访问它们,并将其投影到具有两个属性的匿名类型。Consider the following example, that instantiates an array of DateTime objects, and iterates through them projecting into an anonymous type with two properties.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var anonymous in
             dates.Select(
                 date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
    Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}

使用 new 运算符来实例化匿名类型,从声明推断属性名称和类型。Anonymous types are instantiated by using the new operator, and the property names and types are inferred from the declaration. 如果同一程序集中的两个或多个匿名对象初始值设定项指定了属性序列,这些属性采用相同顺序且具有相同的名称和类型,则编译器将对象视为相同类型的实例。If two or more anonymous object initializers in the same assembly specify a sequence of properties that are in the same order and that have the same names and types, the compiler treats the objects as instances of the same type. 它们共享同一编译器生成的类型信息。They share the same compiler-generated type information.

以上 C# 代码片段投影具有两个属性的匿名类型,非常类似于以下编译器生成的 C# 类:The previous C# snippet projects an anonymous type with two properties, much like the following compiler-generated C# class:

internal sealed class f__AnonymousType0
{
    public string Formatted { get; }
    public long Ticks { get; }

    public f__AnonymousType0(string formatted, long ticks)
    {
        Formatted = formatted;
        Ticks = ticks;
    }
}

有关详细信息,请参阅匿名类型For more information, see anonymous types. 投影到 LINQ 查询时,元组具有相同的功能,你可以选择添加属性到元组。The same functionality exists with tuples when projecting into LINQ queries, you can select properties into tuples. 这些元组会贯穿查询,就像匿名类型一样。These tuples flow through the query, just as anonymous types would. 现在请考虑以下使用 System.Tuple<string, long> 的示例。Now consider the following example using the System.Tuple<string, long>.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var tuple in
            dates.Select(
                date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
    Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}

System.Tuple<T1,T2> 中,实例公开编号项属性,如 Item1Item2With the System.Tuple<T1,T2>, the instance exposes numbered item properties, such as Item1, and Item2. 由于属性名称只提供序号,因此这些属性名称让人更难理解属性值的意图。These property names can make it more difficult to understand the intent of the property values, as the property name only provides the ordinal. 此外,System.Tuple 类型是引用 class 类型。Furthermore, the System.Tuple types are reference class types. System.ValueTuple<T1,T2> 是值 struct 类型。The System.ValueTuple<T1,T2> however, is a value struct type. 以下 C# 代码片段使用 ValueTuple<string, long> 投影。The following C# snippet, uses ValueTuple<string, long> to project into. 在此过程中,它使用文字语法进行分配。In doing so, it assigns using a literal syntax.

var dates = new[]
{
    DateTime.UtcNow.AddHours(-1),
    DateTime.UtcNow,
    DateTime.UtcNow.AddHours(1),
};

foreach (var (formatted, ticks) in
            dates.Select(
                date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
    Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}

有关元组的详细信息,请参阅元组类型(C# 参考)元组 (Visual Basic)For more information about tuples, see Tuple types (C# reference) or Tuples (Visual Basic).

以上示例在功能上是等效的;但其可用性和基础实现存在细微差异。The previous examples are all functionally equivalent, however; there are slight differences in their usability and their underlying implementations.

权衡Tradeoffs

你可能希望始终使用 ValueTuple(而不是 Tuple)和匿名类型,但应进行一些权衡考虑。You might want to always use ValueTuple over Tuple, and anonymous types, but there are tradeoffs you should consider. ValueTuple 类型是可变的,而 Tuple 是只读的。The ValueTuple types are mutable, whereas Tuple are read-only. 匿名类型可用于表达式树,而元组不行。Anonymous types can be used in expression trees, while tuples cannot. 下表概述了一些关键区别。The following table is an overview of some of the key differences.

主要区别Key differences

“属性”Name 访问修饰符Access modifier 类型Type 自定义成员名称Custom member name 析构支持Deconstruction support 表达式树支持Expression tree support
匿名类型Anonymous types internal class ✔️✔️ ✔️✔️
Tuple public class ✔️✔️
ValueTuple public struct ✔️✔️ ✔️✔️

序列化Serialization

选择类型时的一个重要考量是,是否需要对其进行序列化。One important consideration when choosing a type, is whether or not it will need to be serialized. 序列化是将对象状态转换为可保持或传输的形式的过程。Serialization is the process of converting the state of an object into a form that can be persisted or transported. 有关详细信息,请参阅序列化For more information, see serialization. 当序列化非常重要时,创建 classstruct 优于使用匿名类型或元组类型。When serialization is important, creating a class or struct is preferred over anonymous types or tuple types.

性能Performance

这些类型各自的性能取决于场景。Performance between these types depends on the scenario. 主要影响涉及分配和复制之间的权衡。The major impact involves the tradeoff between allocations and copying. 在大多数情况下,影响很小。In most scenarios, the impact is small. 如果可能出现重大影响,应采取措施来通知决策者。When major impacts could arise, measurements should be taken to inform the decision.

结束语Conclusion

开发人员在元组和匿名类型之间进行选择时,需要考虑几个因素。As a developer choosing between tuples and anonymous types, there are several factors to consider. 一般来说,如果不使用表达式树,并且你熟悉元组语法,请选择 ValueTuple,因为它们提供可灵活命名属性的值类型。Generally speaking, if you're not working with expression trees, and you're comfortable with tuple syntax then choose ValueTuple as they provide a value type with the flexibility to name properties. 如果使用表达式树并且想要命名属性,请选择匿名类型。If you're working with expression trees, and you'd prefer to name properties, choose anonymous types. 否则,请使用 TupleOtherwise, use Tuple.

请参阅See also