在匿名类型和元组类型之间进行选择

选择适当的类型涉及考虑其可用性、性能和权衡与其他类型相比。 自 C# 3.0 以来,匿名类型已可用,而泛型 System.Tuple<T1,T2> 类型通过 .NET Framework 4.0 引入。 自那时以来,语言级别支持引入了新选项,例如 System.ValueTuple<T1,T2> ,顾名思义,提供具有匿名类型灵活性的值类型。 在本文中,你将了解何时适合选择一种类型而非另一种类型。

可用性和功能

C# 3.0 中引入了 Language-Integrated 查询(LINQ)表达式的匿名类型。 借助 LINQ,开发人员通常会将查询的结果投影到匿名类型,这些查询包含他们正在使用的对象中的一些选择属性。 请考虑以下示例,它实例化了一个DateTime对象的数组,并遍历这些对象,将它们投影到具有两个属性的匿名类型中。

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 运算符进行实例化,并且从声明推断属性名称和类型。 如果同一程序集中的两个或多个匿名对象初始值设定项指定相同顺序且具有相同名称和类型的属性序列,编译器会将对象视为同一类型的实例。 它们共享同一编译器生成的类型信息。

前面的 C# 代码片段投影一个具有两个属性的匿名类型,这与以下编译器生成的 C# 类类似:

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

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

有关详细信息,请参阅 匿名类型。 在投影到 LINQ 查询时,元组存在相同的功能,你可以选择元组中的属性。 这些元组会贯穿查询,就像匿名类型一样。 现在,请考虑使用 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>实例,该实例公开编号项属性,例如 Item1,和 Item2。 这些属性名称可能使得更难理解属性值的意图,因为属性名称仅提供序号。 此外,这些 System.Tuple 类型是引用 class 类型。 而 System.ValueTuple<T1,T2> 是值 struct 类型。 以下 C# 代码片段使用 ValueTuple<string, long> 投影。 这样做时,它使用文本语法进行分配。

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)。

前面的示例在功能上都是等效的,但是,它们的可用性和基础实现稍有不同。

权衡

你可能希望始终选择使用 ValueTuple 而不是 Tuple 和匿名类型,但应考虑其中的权衡。 类型 ValueTuple 是可变的,而 Tuple 类型是只读的。 匿名类型可用于表达式树,而元组不行。 下表概述了一些主要差异。

主要差异

名称 访问修饰符 类型 自定义成员名称 析构支持 表达式树支持
匿名类型 internal class ✔️ ✔️
Tuple public class ✔️
ValueTuple public struct ✔️ ✔️

序列化

选择类型时,一个重要考虑因素是是否需要序列化。 序列化是将对象的状态转换为可持久保存或传输的形式的过程。 有关详细信息,请参阅 序列化。 当序列化很重要时,优先创建classstruct,而不是匿名类型或元组类型。

性能

这些类型之间的性能取决于方案。 主要影响涉及分配与复制之间的权衡。 在大多数情况下,影响很小。 当可能发生重大影响时,应采取度量来告知决策。

结论

作为开发人员,在选择使用元组还是匿名类型时,需要考虑几个因素。 一般来说,如果你不使用 表达式树,并且你对元组语法很熟悉,那么请选择 ValueTuple,因为它们提供了具备命名属性灵活性的值类型。 如果使用的是表达式树,并且希望命名属性,请选择匿名类型。 否则使用 Tuple

另请参阅