new [] 或者 new List<T>?

25

我正在考虑样式和性能问题。以前我会写像这样的东西:

var strings = new List<string> { "a", "b", "c" };
var ints = new List<int> { 1, 2, 3};

但现在我更喜欢这种风格。

var strings = new [] { "a", "b", "c" }.ToList();
var ints = new [] { 1, 2, 3}.ToList();

我更喜欢第二种风格,但现在考虑 - 是否真的值得这样写,或者它不太高效,需要更多的操作?


8
为什么你更喜欢第二种风格?我认为它不太易读,特别是在使用“var”时。 - Simon Woker
你有尝试过对它进行一些性能测试吗?只需计时并重复100次,然后查看该测试的平均值。 - Frederiek
13
我更喜欢第一个版本,因为我觉得它更容易为人类阅读。 - elsni
我认为这只是一个观点问题,有些人喜欢洋葱,有些人讨厌洋葱。最好的做法是选择一种并坚持下去(如果团队中有的话,要在整个团队中执行)。 - mbx-mbx
@alexanderb 我在我的回答中包含了开销,这些开销显示了基于我的分析测试哪个更快。 - Darren
5个回答

42

我不同意Darin的观点:就性能而言,它们并不等价。后一种版本必须创建一个新数组,并且ToList会将其复制到新列表中。集合初始化程序版本相当于:

var tmp = new List<int>();
tmp.Add(1);
tmp.Add(2);
tmp.Add(3);
var ints = tmp;

假设列表开始时具有足够大的缓冲区,那么就不需要进一步分配 - 虽然它会涉及一些方法调用。如果您对非常大量的项目执行此操作,则将需要比ToList版本更多的分配,因为它会在进行操作时复制项目。
性能差异可能是微不足道的,但它是非零的(并且在任何方向上都不明显更好 - 数组版本中调用较少,但分配更多)。
我会更注重风格而不是性能,除非您有理由怀疑差异很大,在这种情况下,您应该测量而不是猜测。
个人而言,我更喜欢第一种形式 - 我认为从一开始就清晰地使用列表。另一个选择是编写自己的静态类:
public static class Lists
{
    public static List<T> Of<T>(T item0)
    {
        return new List<T> { item0 };
    }

    public static List<T> Of<T>(T item0, T item1)
    {
        return new List<T> { item0, item1 };
    }

    public static List<T> Of<T>(T item0, T item1, T item2)
    {
        return new List<T> { item0, item1, item2 };
    }

    ... as many times as you really care about, then ...

    public static List<T> Of<T>(params T[] items)
    {
        return items.ToList();
    }
}

然后你可以写下以下内容:
var ints = Lists.Of(1);
var ints = Lists.Of(1, 2, 3);
var ints = Lists.Of(1, 2, 3, 5, 6, 7, 8); // Use the params version

这样做可以清晰地表明您正在使用列表,但利用了类型推断。

您可能认为这是过度设计了 :)


1+。这就是我想说的!有很多数组复制发生,而且它不是免费的 - Aliostad
绝对不会创建两倍所需的项目,也不会执行额外的操作。事实上,这种差异是如此微小,以至于当您仅初始化列表一次时,您不必担心它,但如果您多次实例化它们,它们可能会随时间而有所不同。但无论如何,我们谈论的都是很小的差异。 - Jack
如果我理解正确的话,他的意思是在源数据不是很大的情况下,第一个版本的性能更好,因为需要重新分配内存(新分配的内存大小是原来的两倍,还要复制数据)。 - sasjaq
@alexanderb:不一定-对于元素数量较少的情况,集合初始化程序版本涉及的分配和复制较少,但需要更多调用Add,每个调用都必须检查是否需要扩展等。 - Jon Skeet

13

从性能的角度来看,不考虑两者之间的差异,前者更好地表达了您想要实现的目标。

考虑用英语表达代码:

声明一个带有这些内容的字符串列表

声明一个带有这些内容的字符串数组,然后将其转换为字符串列表

对我来说,第一个似乎更自然。尽管我认识到第二个对你可能更好。


这正是我在阅读你的答案前一秒钟所想的 :) - Simon Woker

7
示例1(var ints = new List { 1, 2, 3};):使用Eumerable.ToList会导致31.5%的额外开销,而List.Add()会导致8.7%的开销。
而示例2:在List.ctor上引起11.8%的开销,在Ensure Capacity上引起5%的开销。
(来自Red Gate ANTS性能分析工具的结果)
您可以看到,使用var ints = new List { 1, 2, 3};需要通过反汇编执行更多操作。
 var intsx = new[] {1, 2, 3}.ToList();
0000003f  mov         edx,3 
00000044  mov         ecx,60854186h 
00000049  call        FFF5FD70 
0000004e  mov         dword ptr [ebp-4Ch],eax 
00000051  lea         ecx,[ebp-50h] 
00000054  mov         edx,872618h 
00000059  call        61490806 
0000005e  lea         eax,[ebp-50h] 
00000061  push        dword ptr [eax] 
00000063  mov         ecx,dword ptr [ebp-4Ch] 
00000066  call        614908E3 
0000006b  mov         ecx,dword ptr [ebp-4Ch] 
0000006e  call        dword ptr ds:[008726D8h] 
00000074  mov         dword ptr [ebp-54h],eax 
00000077  mov         eax,dword ptr [ebp-54h] 
0000007a  mov         dword ptr [ebp-40h],eax 

 var ints = new List<int> { 1, 2, 3 };
0000007d  mov         ecx,60B59894h 
00000082  call        FFF5FBE0 
00000087  mov         dword ptr [ebp-58h],eax 
0000008a  mov         ecx,dword ptr [ebp-58h] 
0000008d  call        60805DB0 
00000092  mov         eax,dword ptr [ebp-58h] 
00000095  mov         dword ptr [ebp-48h],eax 
00000098  mov         ecx,dword ptr [ebp-48h] 
0000009b  mov         edx,1 
000000a0  cmp         dword ptr [ecx],ecx 
000000a2  call        608070C0 
000000a7  nop 
000000a8  mov         ecx,dword ptr [ebp-48h] 
000000ab  mov         edx,2 
000000b0  cmp         dword ptr [ecx],ecx 
000000b2  call        608070C0 
000000b7  nop 
000000b8  mov         ecx,dword ptr [ebp-48h] 
000000bb  mov         edx,3 
000000c0  cmp         dword ptr [ecx],ecx 
000000c2  call        608070C0 
000000c7  nop 
000000c8  mov         eax,dword ptr [ebp-48h] 
000000cb  mov         dword ptr [ebp-44h],eax 
        }

3
这些“call”做什么?它们的成本是多少? - L.B
我没有安装任何分析工具来提供准确的成本,这是反汇编的原始输出,显示了问题中两个示例执行的操作数量。将安装并进行编辑。 - Darren
@L.B 更新了我的答案,提供了您所需要的费用信息。 - Darren

6
我猜在第一种情况下,元素会自动添加到列表中,而在第二种情况下,首先创建一个数组,然后迭代该数组,并将每个元素添加到列表中。
虽然第二种方法可能会被优化以避免真正的数组创建,但值得注意的是,如果此操作在循环内执行,则可能会使对象分配量增加两倍(除非它被直接优化),而实际上并没有这样的必要。
您应该检查字节码以确保它。
我不太了解C#内部情况,所以请谨慎对待!

1

我喜欢最初的版本。但是就性能而言,我认为最好的方法是使用数组,并明确定义元素的数量,如果可能的话:

var x = new int[3] { 1, 3, 3 }.ToList();

我认为明确指定数组大小没有任何意义。编译器可以通过静态分析确定它;而无论哪种情况下,List<int>都将在构造函数中选择所需的大小创建。 - Dan Is Fiddling By Firelight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接