it-swarm.com.ru

Лучший способ распоряжаться списком

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

Например,

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;
}

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

27
Vivekh

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

21
Cornelius

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

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

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

18
Marc Gravell

Я не согласен с тем, что вы не должны ничего делать, если вам больше не нужны объекты в списке. Если объекты реализуют интерфейс 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.

15
Harald Coppoolse

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

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();

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

12
jheddings

Вы не предоставили достаточно контекста. Сфера имеет решающее значение здесь.

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

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

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

2
duffymo

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

    public static void Dispose(this IEnumerable collection)
    {
        foreach (var obj in collection.OfType<IDisposable>())
        {
            obj.Dispose();
        }
    }
2
mslissap

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

userCollection= null;

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

1
Pranay Rana

И общая реализация, которая будет работать (появится в списке методов 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
Mohsen Afshin

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

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

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

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
MovGP0

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

    public static void DisposeItemsInList<T>(this IList<T> list) where T : IDisposable
    {
        DeleteItemsInList(list, item => item.Dispose());
    }

    public static void DeleteItemsInList<T>(this ICollection<T> list, Action<T> delete)
    {
        if (list is IList && !((IList)list).IsFixedSize)
        {
            while (list.Count > 0)
            {
                T last = list.Last();
                list.Remove(last);
                delete?.Invoke(last);
            }
        }
        else
        {
            for (int i = 0; i < list.Count; i++)
            {
                delete?.Invoke(list.ElementAt(i));
            }
        }
    }

Я на самом деле использую DeleteItemsInList для других целей, например удалить файлы: DeleteItemsInList (File.Delete))

Как они заявили, в общем случае нет необходимости располагать таким списком .. В случае, когда я располагаю элемент в списке, работаю с Stream, я собираю несколько потоков, преобразовываю из них данные, а затем утилизировать эти потоки и оставить только мои преобразованные объекты для дальнейшей обработки.

0
EricBDev

Как уже упоминалось, отпуск в GC, это лучший вариант и не заставлять GC. Установка переменной на ноль пометит переменную для GC. 

если вам нужно больше информации: Лучшая практика принудительного сбора мусора в C #

0
user831055

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

В этих (редких) сценариях я использовал следующий класс:

public class DisposableList<T> : List<T>, IDisposable
{
    public void Dispose()
    {
    }
}

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

var myList = new DisposableList<MyObject>();

Затем вызовите метод Dispose, когда закончите:

myList.Dispose();

Или, альтернативно, объявите это в операторе using:

using (var myList = new DisposableList<MyObject>())
{
    ...
}

Это тогда заставляет GC делать свою коллекцию немедленно, как только DisposableList выходит из области действия или удаляется.

0
user2746082

Почему вы хотите распоряжаться списком? GC сделает это за вас, если на него больше нет ссылок. 

Сборка мусора: msdn.Microsoft.com/en-us/library/0xy59wtx.aspx

0
Patrick

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

Предполагая, что у вас действительно есть проблемы с памятью, вам нужно определить, что в объекте User потребляет всю вашу память. Установите свойства объекта User в null, которые занимают больше всего памяти.

User.BigData = null;

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

0
Timothy Gonzalez

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

foreach(User user in userCollection)
{
user.Dispose();
}

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

0
RJN

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

например.

void Function()
{
    ... some code here ....

    {   // inside this bracket the usersCollection is alive
        // at the end of the bracet the garbage collector can take care of it

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

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

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

        foreach(User user in userCollection)
        {

        }
    }

    ... other code here ....
}
0
Aristos

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

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% не используются и имеют один элемент, на который все еще ссылаются. 

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

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

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

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

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

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

0
Mick