我使用Visual Studio处理在C#/ .NET中开发的应用程序。在我的方法的原型中,ReSharper经常建议我用更通用的输入参数替换输入参数的类型。例如,List<>使用IEnumerable<>如果我只在我的方法体中使用带有foreach的列表。我可以理解为什么写这个看起来更聪明,但我非常关心性能。我担心,如果我听ReSharper,我的应用程序的性能会降低......
当我写作时,有人能够(或多或少)向我解释幕后发生的事情(即在CLR中):
public void myMethod(IEnumerable<string> list)
{
foreach (string s in list)
{
Console.WriteLine(s);
}
}
static void Main()
{
List<string> list = new List<string>(new string[] {"a", "b", "c"});
myMethod(list);
}
与以下区别:
public void myMethod(List<string> list)
{
foreach (string s in list)
{
Console.WriteLine(s);
}
}
static void Main()
{
List<string> list = new List<string>(new string[] {"a", "b", "c"});
myMethod(list);
}
答案 0 :(得分:11)
你担心表现 - 但你有什么理由担心这个问题吗?我的猜测是你根本没有对代码进行基准测试。在使用性能更高的代码替换可读,干净的代码之前,始终基准。
在这种情况下,对Console.WriteLine
的调用无论如何都会完全控制表现。
虽然我怀疑在此处使用List<T>
和IEnumerable<T>
之间可能存在理论上的性能差异,但我怀疑它在真实世界的应用中具有重要意义的案例数量是很小。
它甚至不是用于许多操作的序列类型 - 只有一次调用GetEnumerator()
,无论如何都被声明返回IEnumerator<T>
。随着列表变大,两者之间性能的任何差异都会变得更小,因为它只会在循环开始时产生任何影响。
忽略分析,要做的就是衡量性能,然后才能对其进行编码决策。
至于幕后发生的事情 - 你必须深入了解每种情况下元数据中究竟是什么。我怀疑在接口的情况下,有一个额外的重定向级别,至少在理论上 - CLR必须找出目标对象类型中IEnumerable<T>
的vtable的位置,然后调用相应的方法的代码。在List<T>
的情况下,JIT将知道开始时的vtable的正确偏移,而不需要额外的查找。这只是基于我对JITting,thunking,vtables以及它们如何应用于接口的朦胧理解。它可能略有错误,但更重要的是它是一个实现细节。
答案 1 :(得分:3)
您必须查看生成的代码才能确定,但在这种情况下,我怀疑存在很大差异。 foreach 语句始终在IEnumerable或IEnumerable<T>
上运行。即使您指定List<T>
,它仍然必须获取IEnumerable<T>
才能进行迭代。
答案 2 :(得分:2)
一般情况下,我会说如果你用通用的味道替换等效的非通用接口(比如IList<>
- &gt; IList<T>
),你一定会得到更好的 或同等的表现。
一个独特的卖点是因为,与java不同,.NET不使用类型擦除和支持真值类型(struct
),其中一个主要区别在于如何它存储例如内部List<int>
。这很快就会变得很大,这取决于List的使用程度。
脑死亡合成基准显示:
for (int j=0; j<1000; j++)
{
List<int> list = new List<int>();
for (int i = 1<<12; i>0; i--)
list.Add(i);
list.Sort();
}
比半等效非泛型更快 3.2x :
for (int j=0; j<1000; j++)
{
ArrayList list = new ArrayList();
for (int i = 1<<12; i>0; i--)
list.Add(i);
list.Sort();
}
免责声明我意识到这个基准是合成的,它实际上并不专注于使用 interfaces (而是直接调度特定类型的虚拟方法调用)然而,它说明了我正在做的一点。 不要害怕泛型(至少不是出于性能原因)。
答案 3 :(得分:1)
一般而言,增加的灵活性将是值得产生的微小性能差异。
答案 4 :(得分:1)
这个建议的基本原因是创建一个适用于IEnumberable vs. List的方法是未来的灵活性。如果将来你需要创建一个MySpecialStringsCollection,你可以让它实现IEnumerable方法并仍然使用相同的方法。
基本上,我认为它会降低,除非你注意到一个重要的,有意义的性能打击(如果你发现任何事情,我会感到震惊);更喜欢一个更宽容的界面,它会接受比你今天所期望的更多的东西。
答案 5 :(得分:1)
在第一个版本(IEnumerable)中,它更通用,实际上你说该方法接受任何实现此接口的参数。
第二个版本哟限制方法接受特定的类类型,根本不建议这样做。而且表现基本相同。
答案 6 :(得分:1)
List<T>
的定义是:
[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>,
IEnumerable<T>, IList, ICollection, IEnumerable
所以List<T>
除了IList
和ICollection
之外,还来自IList<T>,
,ICollection<T>,
,IEnumerable
和IEnumerable<T>.
/ p>
IEnumerable
界面公开GetEnumerator
方法,该方法返回IEnumerator
,MoveNext
方法和Current
属性。这些机制是List<T>
类使用foreach和next迭代列表的机制。
因此,如果不需要IList, ICollection, IList<T>, and ICollection<T>
来完成这项工作,那么使用IEnumerable
或IEnumerable<T>
代替它是合理的,从而消除了额外的管道。
答案 7 :(得分:0)
接口只是定义了类实现的公共方法和属性的存在和签名。由于界面不“独立”,因此方法本身应该存在 no 性能差异,任何“投射”惩罚 - 如果有的话 - 应该几乎太小而无法衡量。
答案 8 :(得分:0)
静态上传没有性能损失。它是程序文本中的逻辑结构。
正如其他人所说,过早优化是万恶之源。编写代码,在担心性能调整之前,通过热点分析运行代码。
答案 9 :(得分:0)
获取IEnumerable&lt;&gt;可能会产生一些麻烦,因为你可能会收到一些执行不同的LINQ表达式,或者收益率返回。在这两种情况下,您都没有集合,但可以迭代 。 因此,当您想设置一些边界时,您可以请求一个数组。在传递参数之前调用 collection.ToArray()没有问题,但是你可以确定那里没有隐藏的不同警告。