LINQ查询中未处置的项目

时间:2018-11-28 10:00:55

标签: c# linq idisposable

我在Select对象中创建了IDisposable的linq。之后有一个过滤器Where,它导致某些对象从未处置。

这是一个复制人:

class Program
{
    static void Main(string[] args)
    {
        var results = "1234567890"
            .Select(o => new Test(o))
            .Where(o => o.Value > '3' && o.Value < '7')
            .ToList();

        // do something with results
        // ...

        // dispose
        foreach (var result in results)
            result.Dispose();
    }
}

class Test : IDisposable
{
    public char Value { get; }
    public Test(char value)
    {
        Value = value;
        Console.WriteLine($"{Value}");
    }
    public void Dispose() => Console.WriteLine($"{Value} disposed");
}

输出:

1
2
3
4
5
6
7
8
9
0
4 disposed
5 disposed
6 disposed

问题:

您会看到1237890它们是创建的,永远不会被丢弃。

我的解决方案:

我可以在Where内移动Select条件,但是然后我需要应用难看的“返回null + Where而不是null”解决方法:

var results = "1234567890".Select(o =>
{
    if (o > '3' && o < '7')
        return new Test(o);
    return null;
}).Where(o => o != null).ToList();

输出:

4
5
6
4 disposed
5 disposed
6 disposed

有没有一种更好的方式(更优雅)?

如果Select处于某个返回IEnumerable<T>的库方法中,而我无法更改,那么我的解决方法很难做到。如何应用Where而不会泄漏?

5 个答案:

答案 0 :(得分:1)

  

您会看到创建了1,2,3,7,8,9,0   从来没有处置过

这是因为Where需要为每个Test调用创建Select实例以根据条件测试结果。

过滤后,results包含已创建对象的子集=>您的代码仅处理此子集项。

  

有没有更好(更优雅)的方式?

唯一的 LINQ 方法是在过滤之前,将初始可枚举的实例化成List<Test>(或数组),并处理列表项:

        var results = "1234567890"
            .Select(o => new Test(o))
            .ToList();

        var filteredResults = results
            .Where(o => o.Value > '3' && o.Value < '7')
            .ToList();

        // do something with FILTERED results
        // ...

        // dispose
        foreach (var result in results)
            result.Dispose();

但是,如果出于某些原因需要尽快Dispose产生不必要的结果,并且您无法修改代码,则会产生可枚举的结果,只是不要使用LINQ 。编写常规的foreach

        var enumerable = "1234567890"
            .Select(o => new Test(o));

        var results = new List<Test>();

        foreach (var item in enumerable)
        {
            if (!(item.Value > '3' && item.Value < '7'))
            {
                item.Dispose();
            }
            else
            {
                results.Add(item);
            }
        }

        // do something with results
        // ...

        // dispose
        foreach (var result in results)
            result.Dispose();

答案 1 :(得分:0)

从库中获取项目后,可以将它们存储在数组中。您可以过滤数组,对相关项进行处理,然后使用数组处理所有项:

DETAILS:    Can't find method com.mirth.connect.server.userutil.ChannelMap.size(string).
at 7b0f55d4-9758-4764-8486-6b0363f598c5:75 (doTransform)
at 7b0f55d4-9758-4764-8486-6b0363f598c5:101 (doScript)
at 7b0f55d4-9758-4764-8486-6b0363f598c5:103
at com.mirth.connect.server.transformers.JavaScriptFilterTransformer$FilterTransformerTask.doCall(JavaScriptFilterTransformer.java:154)
at com.mirth.connect.server.transformers.JavaScriptFilterTransformer$FilterTransformerTask.doCall(JavaScriptFilterTransformer.java:119)
at com.mirth.connect.server.util.javascript.JavaScriptTask.call(JavaScriptTask.java:113)
at java.util.concurrent.FutureTask.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)

import java.util.Random; { public static void main(String[] args) { int randomNr; boolean[] lottoRow = new boolean[35]; Random randNr = new Random(); for(int i=0; i<7; i++) { randomNr = randNr.nextInt(35)+1; } } 将项目存储在一个数组中,这样您就可以在处理它们时再次处理相同的项目,而不必再次查询库,这可能会导致返回新的项目。

如果您担心内存不足,可以使用以下(丑陋的)方法尽早丢弃不必要的项目。这可能是一个优势,但是与使用var items = GetItemsFromLibrary().ToArray(); try { var relevantItems = items.Where(o => o.Value > '3' && o.Value < '7'); // Do something with relevant items // ... } finally { // Dispose all items foreach (var item in items) item.Dispose(); } 存储所有项目相比,我怀疑这不是一个很大的优势。

ToArray

即使在ToArray子句中放置不必要的项目时,该对象也已在库中实例化。因此,最好的选择是将库更改为仅返回相关项目。

答案 2 :(得分:0)

您需要记住所有项目:

var allItems= "1234567890"
        .Select(o => new Test(o)).ToArray();
var result = allItems.Where(o => o.Value > '3' && o.Value < '7')
        .ToList();

而不是处理allItems

foreach (var result in allItems)
        result.Dispose();

答案 3 :(得分:0)

您可以先用string过滤char,然后选择所需的类型:

var results = "1234567890".Where(ch => ch > '3' && ch < '7').Select(s => new Test(s)).ToList();

foreach (var item in results)
{
    item.Dispose();
}

答案 4 :(得分:0)

正如其他人所述,您正在创建对象,然后丢弃对它们的引用,这意味着无法调用Dispose()

这些对象最终将在将来的某个时候由GC收集,但是GC不会不会在没有一点帮助的情况下自动为您调用Dispose()

如果这是您真正需要的模式,则可以在一次性对象上实现终结器,以在GC启动时为您调用Dispose()

class Program
{
    static void Main(string[] args)
    {
        var results = "1234567890"
                      .Select(o => new Test(o))
                      .Where(o => o.Value > '3' && o.Value < '7')
                      .ToList();

        // do something with results
        // ...

        // dispose
        foreach (var result in results)
            result.Dispose();

        // Force GC to prove dispose called...
        GC.Collect();

        Console.ReadLine();
    }
}
class Test : IDisposable
{
    public char Value { get; }
    public Test(char value)
    {
        Value = value;
        Console.WriteLine($"{Value}");
    }
    public void Dispose() => Console.WriteLine($"{Value} disposed");

    ~Test()
    {
        Dispose();
    }
}