Лучший способ распоряжаться списком - PullRequest
29 голосов
/ 06 июля 2011

У меня есть объект List.Как я могу распоряжаться списком?

Например,

List<User> usersCollection =new List<User>();

User user1 = new User();
User user2 = new User()

userCollection.Add(user1);
userCollection.Add(user2);

Если я установлю userCollection = null;, что произойдет?

foreach(User user in userCollection)
{
    user = null;
}

Какой из них лучше?

Ответы [ 17 ]

22 голосов
/ 06 июля 2011

Лучше всего оставить его сборщику мусора.Ваш foreach ничего не будет делать, так как будет установлена ​​только ссылка null, а не элемент в списке.Установка списка в null может фактически привести к тому, что сборка мусора произойдет позже, чем могла бы (см. Этот пост C #: переменные объекта должны быть присвоены нулю? ).

18 голосов
/ 06 июля 2011

Во-первых, вы не можете «распоряжаться» списком, поскольку он не IDisposable, и вы не можете заставить его собирать , поскольку C # работает не так . Обычно вы бы сделали ничего здесь. Поэтому, когда может , нам нужно сделать что-нибудь ?

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

Единственный раз, когда вам нужно что-либо , это если это поле (или захваченная переменная / блочная переменная и т. Д.) и экземпляр (/ делегат / итератор) будет жить долго еще дольше - тогда, возможно, установите поле списка на ноль. Однако обратите внимание, что если какой-либо другой код все еще имеет ссылку на список, все будет по-прежнему доступно.

15 голосов
/ 14 ноября 2013

Я не согласен с тем, что вы не должны ничего делать, если вам больше не нужны объекты в списке. Если объекты реализуют интерфейс System.IDisposable, то разработчик объекта считал, что объект содержит ограниченные ресурсы.

Если вам больше не нужен объект и вы просто присваиваете объекту значение null, то эти ограниченные ресурсы не освобождаются, пока сборщик мусора не завершит работу с объектом. В то же время вы не можете использовать этот ресурс для чего-то другого.

Пример: Предположим, вы создали растровое изображение из файла и решили, что вам больше не нужны ни растровое изображение, ни файл. Код может выглядеть следующим образом:

using System.Drawing;
Bitmap bmp = new Bitmap(fileName);
... // do something with bmp until not needed anymore
bmp = null;
File.Delete(fileName); // EXCEPTION, filename is still accessed by bmp.

Хороший метод будет:

bmp.Dispose();
bmp = null;
File.Delete(fileName);

То же самое относится к объектам в списке или любой коллекции. Все объекты в коллекции, которые IDisposable должны быть удалены. Код должен быть таким:

private void EmptySequence (IEnumerable sequence)
{   // throws away all elements in the sequence, if needed disposes them
    foreach (object o in sequence)
    {
        System.IDisposable disposableObject = o as System.IDisposable;
        o = null;
        if (disposableObject != null)
        {
            disposableObject.Dispose();
        }
    }
}

Или, если вы хотите создать функцию расширения IEnumerable

public static void DisposeSequence<T>(this IEnumerable<T> source)
{
    foreach (IDisposable disposableObject in source.OfType(System.IDisposable))
    {
        disposableObject.Dispose();
    };
}

Все списки / словари / списки / коллекции только для чтения могут использовать эти методы, поскольку все они реализуют интерфейс IEnumerable. Вы даже можете использовать его, если не все элементы в последовательности реализуют System.IDisposable.

12 голосов
/ 25 ноября 2012

Еще одна идея для этого поста ... Если вы хотите убедиться, что все члены коллекции расположены правильно, вы можете использовать следующий метод расширения:

public static void DisposeAll(this IEnumerable set) {
    foreach (Object obj in set) {
        IDisposable disp = obj as IDisposable;
        if (disp != null) { disp.Dispose(); }
    }
}

Это просматривает коллекцию для любого участника, который реализует IDisposable и удаляет его. Из вашего исполняемого кода вы можете очистить список следующим образом:

usersCollection.DisposeAll();
usersCollection.Clear();

Это гарантирует, что все участники получат возможность освободить ресурсы, а итоговый список будет пустым.

2 голосов
/ 23 октября 2015

Еще один пример метода расширения, который вы можете использовать для удаления списка объектов, которые реализуют интерфейс IDisposable. Этот использует синтаксис LINQ.

    public static void Dispose(this IEnumerable collection)
    {
        foreach (var obj in collection.OfType<IDisposable>())
        {
            obj.Dispose();
        }
    }
2 голосов
/ 06 июля 2011

Вы не предоставили достаточно контекста.Здесь важна область действия.

Я думаю, что сборщик мусора должен быть достаточно умным, чтобы иметь дело с памятью, выделенной для пользователей и коллекции, без необходимости устанавливать нулевое значение.

Если коллекция удаляет пользователей, которыене нужны из коллекции, и никакие другие объекты не ссылаются на них, они будут GC, если вы не предоставите никаких подсказок.

GC не будет очищать объект, пока естьживая ссылка на это.Удалите все ссылки, и он сможет выполнить свою работу.

1 голос
/ 22 февраля 2019

Многие из этих ответов имеют что-то вроде ...

public static void DisposeAll(this IEnumerable clx) {
    foreach (Object obj in clx) 
    {
        IDisposable disposeable = obj as IDisposable;
        if (disposeable != null) 
            disposeable.Dispose();
    }
}

usersCollection.DisposeAll();
usersCollection.Clear();

Нет ни одного ответа, в котором упоминается, почему .Clear () полезен. Ответ заключается в том, чтобы отделить элементы коллекции от коллекции и друг от друга.

Чем больше вы разъединяете при разрушении какого-либо объекта, тем выше вероятность того, что сборщик мусора выполнит свою работу своевременно. Часто существенные утечки памяти .NET происходят из-за больших графов объектов, которые на 99% не используются и имеют один элемент, на который все еще ссылаются.

Я думаю, что это хорошая практика ...

  • отписаться от любых событий, на которые подписан объект
  • очистить все хранящиеся коллекции
  • и установите для всех членов значение null.

... в классе, реализующем IDisposable.

Я не предлагаю реализовывать IDisposable на всем и делать это, я говорю, что если так случится, что вам нужно реализовать dispose, вы можете также сделать это. Я реализую IDisposable только тогда, когда объект ...

  • подписаться на события или
  • владеет членами, которые реализуют Dispose (), такими как соединение с БД, растровое изображение или
  • имеет другие неуправляемые ресурсы.

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

1 голос
/ 23 декабря 2016

Существует гораздо лучший способ использования System.Reactive.Disposeables :

Просто инициализируйте новое свойство типа CompositeDisposable и добавьте одноразовые вещи в эту коллекцию. Тогда распоряжайся только этим.

Вот пример кода, как это сделать в типичной модели представления WPF / UWP без утечек памяти:

public sealed MyViewModel : IDisposable
{
    // ie. using Serilog
    private ILogger Log => Log.ForContext<MyViewModel>();

    // ie. using ReactiveProperty 
    public ReactiveProperty<string> MyValue1 { get; } 
        = new ReactiveProperty<string>(string.Empty);

    public ReactiveProperty<string> MyValue1 { get; } 
        = new ReactiveProperty<string>(string.Empty);

    // this is basically an ICollection<IDisposable>
    private CompositeDisposable Subscriptions { get; } 
        = new CompositeDisposable();

    public MyViewModel()
    {
        var subscriptions = SubscribeToValues(); // Query
        Subscriptions.AddRange(subscriptions); // Command
    }

    private IEnumerable<IDisposable> SubscribeToValues()
    {
        yield return MyValue1.Subscribe(
            value => DoSomething1(value), 
            ex => Log.Error(ex, ex.Message), 
            () => OnCompleted()); 

        yield return MyValue2.Subscribe(
            value => DoSomething2(value),
            ex => Log.Error(ex, ex.Message), 
            () => OnCompleted()); 
    }

    private void DoSomething1(string value){ /* ... */ }
    private void DoSomething2(string value){ /* ... */ }
    private void OnCompleted() { /* ... */ }

реализовать IDisposable как это:

    #region IDisposable
    private ~MyViewModel()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    private bool _isDisposed;
    private Dispose(bool disposing)
    {
        if(_isDisposed) return; // prevent double disposing

        // dispose values first, such that they call 
        // the onCompleted() delegate
        MyValue1.Dispose();
        MyValue2.Dispose();

        // dispose all subscriptions at once 
        Subscriptions.Dispose(); 

        // do not suppress finalizer when called from finalizer
        if(disposing) 
        {
            // do not call finalizer when already disposed
            GC.SuppressFinalize(this);
        }
        _isDisposed = true;
    }
    #endregion
}

и вот класс расширения для получения метода .AddRange():

public static class CollectionExtensions
{
    public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values)
    {
        foreach(var value in values)
        {
            collection.Add(value);
        }
    }
}

См. Также

  • BooleanDisposable позволяет запрашивать, был ли уже удален объект
  • CancellationDisposable похоже на BooleanDisposable, но с токеном отмены
  • ContextDisposable позволяет вам размещать в заданном контексте потока
  • MultipleAssignmentDisposable заменяет одноразовое на другое без утилизации старого одноразового использования
  • SerialDisposable заменяет старый утилизатор другим при утилизации старого одноразового
  • SingleAssignmentDisposable хранит одноразовое изделие, которое нельзя заменить другим одноразовым
1 голос
/ 06 марта 2016

И общая реализация, которая будет работать (появится в List<T> списке методов), если элемент реализован IDisposable

public static class LinqExtensions
{
    public static void DisposeItems<T>(this IEnumerable<T> source) where T : IDisposable
    {
        foreach(var item in source)
        {
            item.Dispose();
        }
    }
}

Для использования таким образом

if(list != null)
{
  list.DisposeItems();                
  list.Clear();
}
1 голос
/ 06 июля 2011

Лучший способ это

userCollection= null;

Чем ГК позаботится об отдыхе.

...