Недействующие элементы в запросе linq - PullRequest
0 голосов
/ 28 ноября 2018

У меня есть linq с Select, где создаются IDisposable объекты.После этого есть фильтр 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

Проблема:

Как видите, есть 1, 2, 3, 7, 8, 9, 0, которые созданы иникогда не выбрасывайте.

Мое решение:

Я могу переместить Where условие внутрь Select, но тогда мне нужно применить уродливый "return 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 голосов
/ 28 ноября 2018

Как уже говорили другие, вы создаете объекты, а затем отбрасываете ссылки на них, что означает, что Dispose() не может быть вызвано.

Эти объекты будут в конечном итоге собраны GC в какой-то момент в будущем, но GC не автоматически вызывает Dispose() для вас без небольшой помощи.

Если это шаблон, который вам действительно нужен, вы можете добавить на свой одноразовый объект финализатор, который будет вызывать Dispose() для вас, когда GC включится.

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();
    }
}
0 голосов
/ 28 ноября 2018

Вы можете сначала отфильтровать string с помощью char, а затем выбрать нужный тип:

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

foreach (var item in results)
{
    item.Dispose();
}
0 голосов
/ 28 ноября 2018

Как вы можете видеть, 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();
0 голосов
/ 28 ноября 2018

Вам нужно запомнить все элементы:

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

, чем утилизировать все элементы:

foreach (var result in allItems)
        result.Dispose();
0 голосов
/ 28 ноября 2018

Получив элементы из библиотеки, вы можете сохранить их в массиве.Вы можете отфильтровать массив, сделать что-нибудь с соответствующими элементами и использовать массив для удаления всех элементов:

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.

var items = GetItemsFromLibrary()
  .Where(o => {
           if (o.Value > '3' && o.Value < '7')
             return true;
           // Object not relevant in this case
           o.Dispose()
           return false;
         })
  .ToArray();
try
{
  // Do something with relevant items
  // ...
}
finally
{
  // Dispose all items
  foreach (var item in items)
    item.Dispose();
}

Даже при удалении ненужных элементов в предложении Where объект уже был создан в библиотеке.Поэтому лучшим вариантом будет изменить библиотеку, чтобы она возвращала только соответствующие элементы.

...