如何在C#中长时间调用.Distinct()报告进度

Mic*_*kus 6 .net c# linq progress distinct

我有一个名为的自定义对象数组AnalysisResult.该数组可以包含数十万个对象; 并且,偶尔我只需要Distinct()该数组的元素.所以,我写了一个调用的项比较器类,AnalysisResultDistinctItemComparer并按我这样的方式调用:

public static AnalysisResult[] GetDistinct(AnalysisResult[] results)
{
    return results.Distinct(new AnalysisResultDistinctItemComparer()).ToArray();
}
Run Code Online (Sandbox Code Playgroud)

我的问题是,当数组特别大(大于200,000个对象)时,此调用可能需要很长时间(大约几分钟).

我目前在后台工作程序中调用该方法并显示一个旋转gif,以警告用户该方法正在执行,并且应用程序尚未冻结.这一切都很好,但它没有给用户任何当前进展的指示.

我真的需要能够向用户指出此动作的当前进度; 但是,我一直无法想出一个好方法.我正在玩这样的事情:

public static AnalysisResult[] GetDistinct(AnalysisResult[] results)
{
    var query = results.Distinct(new AnalysisResultDistinctItemComparer());

    List<AnalysisResult> retVal = new List<AnalysisResult>();
    foreach(AnalysisResult ar in query)
    {
        // Show progress here
        retVal.Add(ar);
    }

    return retVal.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

但问题是我无法知道我的实际进展是什么.思考?建议?

sir*_*lot 4

不要ToArray()在方法末尾调用,只需使用yield return. 所以这样做:

public static IEnumerable<AnalysisResult> Distinct(AnalysisResult[] results)
{
    var query = results.Distinct(new AnalysisResultDistinctItemComparer());

    foreach(AnalysisResult ar in query)
    {
        // Use yield return here, so that the iteration remains lazy.
        yield return ar;
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,yield return会执行一些编译器魔法来确保迭代保持惰性,因此您不必等待创建完整的新集合即可返回调用者。相反,在计算每个项目时,您会立即将该项目返回给消费者(然后消费者可以执行更新逻辑 - 如果需要的话,针对每个项目)。GetDistinct您也可以在您的方法中使用相同的技术。

Jon Skeet 有一个如下所示的实现(特定属性上的 LINQ 的 Distinct()):

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里他使用了 a HashSet,它的构建目的是不允许重复。只需检查该项目是否已添加,如果没有,则将其退回。

总而言之,请记住这是一个算法和数据结构类型的问题。这样做会容易得多:

Dictionary<Key, Value> distinctItems = new Dictionary<Key, Value>(); 

foreach (var item in nonDistinctSetOfItems) {
    if (distinctItems.ConatainsKey(item.KeyProperty) == false) {
        distinctItems.Add(item.KeyProperty, item);
    }
}

... = distinctItems.Values // This would contain only the distinct items.
Run Code Online (Sandbox Code Playgroud)

也就是说,符号表就是Dictionary为了解决此类问题而构建的 - 将条目与唯一键相关联。如果您以这种方式存储数据,就会大大简化问题。不要忽视简单的解决方案!