锁定枚举可能会导致多次枚举吗?

时间:2014-09-19 16:23:25

标签: c# resharper ienumerable

我觉得ReSharper骗我。

我有这个扩展方法(希望)返回两个枚举的xor:

public static IEnumerable<T> Xor<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
    lock (first)
    {
        lock (second)
        {
            var firstAsList = first.ToList();
            var secondAsList = second.ToList();

            return firstAsList.Except(secondAsList).Union(secondAsList.Except(firstAsList));
        }
    }
}

ReSharper认为我正在对两个参数执行IEnumerable的多重枚举。如果我取下锁,那么我很满意,我不会。

Multiple enumeration

ReSharper是对还是错?我认为这是错的。


编辑:我确实意识到我多次枚举这些列表,但ReSharper说我多次枚举原始参数 ,我不认为这是真的。我将两个参数枚举到一个列表中,这样我就可以执行实际的集合操作,但正如我所看到的,我实际上并没有迭代多次传递的参数。

例如,如果传递的参数实际上是查询结果,我相信这种方法不会导致设置操作执行查询风暴。我通过警告多个枚举来理解ReSharper的含义:如果传递的枚举数很多,那么如果它们被多次枚举,那么对它们执行多次枚举将会慢得多。

此外,删除锁定肯定使ReSharper更快乐:

No multiple enumeration

2 个答案:

答案 0 :(得分:2)

您确实多次枚举这两个列表。您没有枚举多次作为参数传递的枚举。对于Except的每次调用,都会枚举两次列表。对Union的调用不会在额外的时间内枚举任何一个序列,而是枚举两次调用Except的结果。

当然,在这样的上下文中多次迭代List并不是真正的问题;多次迭代不变的列表不会产生负面影响。

lock语句与序列的枚举没有任何关系。锁定IEnumerable不会迭代它。当然,锁定这样的两个对象,特别是两个不限于此部分代码范围的对象,非常是危险的。如果其他地方的代码(例如此方法的另一个调用)最终以相反的顺序对相同的对象进行锁定,则很可能最终使用此庄园中使用的锁来解锁程序。

答案 1 :(得分:1)

这有点滑稽。

首先要做的事情是:正如您已经正确识别的那样,R#正在针对 List 的多次使用提高此检查 - 有当然,无需担心多次枚举List - 而是反对(R'认为多次使用) IEnumerable参数。我假设你已经知道为什么这可能会很糟糕,所以我会跳过它。


现在谈谈R#是否适合在这里抱怨。引用C#规范,

  

表单的锁定声明

lock (x) ...
     

其中x是引用类型的表达式,是精确的   等效

System.Threading.Monitor.Enter(x);
try {
  ...
}
finally {
  System.Threading.Monitor.Exit(x);
}
     

除了x仅评估一次。

(我已经强调了这一点,因为我喜欢这个措辞;它避免了关于这是否是“合成糖”的辩论(我绝对没有资格进入)。)

以一个产生这种R#检验的最小例子:

private static void Method(IEnumerable<int> enumerable)
{
    lock (enumerable)
    {
        var list = enumerable.ToList();
    }
}

并将其替换为我认为的规格所强制的精确等效版本:

private static void Method(IEnumerable<int> enumerable)
{
    var x = enumerable;
    System.Threading.Monitor.Enter(x);
    try
    {
        var list = enumerable.ToList();
    }
    finally
    {
        System.Threading.Monitor.Exit(x);
    }
}

生成检查。

那么问题是:R#正确来产生这种检查吗?这就是我认为我们进入灰色地带的地方。当我将以下可枚举传递给这些方法中的任何一个时:

static IEnumerable<int> MyEnumerable()
{
    Console.WriteLine("Enumerable enumerated");
    yield return 1;
    yield return 2;
}

乘以枚举,这表明R#在此处发出警告是错误的;但是,我实际上无法在文档中找到保证 lockMonitor.Enter的此行为。所以对我而言,它并不像this R# bug I reported, where use of GetType flagged this inspection那么明确;但是我猜你是安全的。

如果你提出这个on the R# bug tracker,你可以让JetBrains最好看看a)这种行为是否确实得到保证。 b)R#是否可以调整为不警告,或提供警告的理由。


那说,当然,在这里使用锁定可能实际上并没有实现你想要实现的目标,正如其他答案和评论所述......