为啥 C# 编译器在使用 LINQ 方法 Any() 时会创建私有 DisplayClass,我该如何避免它?
Posted
技术标签:
【中文标题】为啥 C# 编译器在使用 LINQ 方法 Any() 时会创建私有 DisplayClass,我该如何避免它?【英文标题】:Why does C# compiler create private DisplayClass when using LINQ method Any() and how can I avoid it?为什么 C# 编译器在使用 LINQ 方法 Any() 时会创建私有 DisplayClass,我该如何避免它? 【发布时间】:2015-12-02 06:02:36 【问题描述】:我有这段代码(整个代码并不重要,但可以在this link上看到):
internal static class PlayCardActionValidator
public static bool CanPlayCard(...)
// ...
var hasBigger =
playerCards.Any(
c => c.Suit == otherPlayerCard.Suit
&& c.GetValue() > otherPlayerCard.GetValue());
// ...
例如在反编译器 (ILSpy) 中打开代码后,我注意到 C# 编译器新创建的类 <>c__DisplayClass0_0
的存在:
如果这段代码对系统性能不重要,这对我来说不是问题。这种方法被调用了数百万次,垃圾收集器正在清理这些<>c__DisplayClass0_0
实例,这会降低性能:
在使用Any
方法时如何避免创建此类(他的实例及其垃圾收集)?
为什么 C# 编译器会创建这个类,我可以使用 Any()
的替代品吗?
【问题讨论】:
这里需要重写你的代码来为捕获的变量otherPlayerCard和trumpCard找到一个安全的家。将它们从局部变量转换为字段,以便它们的值可以保留在方法体之外。 DisplayClass 就是那个安全的家。 不要在热路径上使用 LINQ,这是 Roslyn 代码库的政策。 我通常会避免推荐微优化,但如果此代码运行 数百万 次,则重构此代码以优化速度将是这里的解决方案。 LINQ 很慢。 @DaveShaw 参考? @DanielA.White - github.com/dotnet/roslyn/wiki/Contributing-Code - 请参阅编码约定。 【参考方案1】:要了解“显示类”,您必须了解闭包。您在此处传递的 lambda 是一个 闭包,这是一种特殊类型的方法,它神奇地将状态从其所在方法的范围内拖入并“围绕”它。
...当然,除了没有魔法这样的东西。所有这些状态都必须真正存在于某个真实的地方,某个与闭包方法相关的地方,并且可以从中轻松获得。将状态直接与一种或多种方法相关联的编程模式是什么?
没错:类。编译器将 lambda 转换为闭包类,然后在宿主方法中实例化该类,以便宿主方法可以访问类中的状态。
避免这种情况发生的唯一方法是不使用闭包。如果这确实会影响性能,请使用老式的 FOR
循环而不是 LINQ 表达式。
【讨论】:
“任何足够先进的技术都与魔法无异。” - Arthur C. Clarke【参考方案2】:使用 Any 方法时如何避免创建此类(他的实例及其垃圾收集)?
为什么 C# 编译器会创建这个类,我可以使用 Any() 的替代方法吗?
其他海报已经解释了为什么部分,所以更好的问题是我怎样才能避免创建闭包?。答案很简单:如果 lambda 仅 使用传递的参数和/或常量,编译器将不会创建闭包。例如:
bool AnyClub() return playerCards.Any(c => c.Suit == CardSuit.Club);
bool AnyOf(CardSuit suit) return playerCards.Any(c => c.Suit == suit);
第一个不会创建闭包,而第二个会。
考虑到所有这些,并假设您不想使用 for/foreach 循环,您可以创建自己的扩展方法,类似于 System.Linq.Enumerable
中的扩展方法,但带有附加参数。对于这种特殊情况,这样的事情会起作用:
public static class Extensions
public static bool Any<T, TArg>(this IEnumerable<T> source, TArg arg, Func<T, TArg, bool> predicate)
foreach (var item in source)
if (predicate(item, arg)) return true;
return false;
并将相关代码更改为:
var hasBigger =
playerCards.Any(otherPlayerCard,
(c, opc) => c.Suit == opc.Suit
&& c.GetValue() > opc.GetValue());
【讨论】:
嗯,为什么不为参数或实例成员创建闭包?我知道这不可能被拉出来。 @usr 它将创建一个静态/实例函数并将委托绑定到它。不需要单独的类,因为整个状态由参数和/或this
组成。
好的,不需要上课。确实如此。但是该课程的性能影响为零。创建新的委托和闭包实例是昂贵的。我相信问题是关于性能的。他并不关心隐藏类本身。
@usr 好吧,每次调用创建类实例肯定是额外的 GC 压力。此外,当 lambda 不使用实例成员时(如在 OP 情况下),编译器将生成一个带有已编译委托的静态字段(一次)以上是关于为啥 C# 编译器在使用 LINQ 方法 Any() 时会创建私有 DisplayClass,我该如何避免它?的主要内容,如果未能解决你的问题,请参考以下文章