开发者

Best way to dispose a list

I am having List object. How can I dispose of the list?

For example,

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

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

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

If I开发者_Go百科 set userCollection = null; what will happen?

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

Which one is best?


Best idea is to leave it to the garbage collector. Your foreach will do nothing since only the reference will be set to null not the element in the list. Setting the list to null could in fact cause garbage collection to occur later than it could have (see this post C#: should object variables be assigned to null?).


I don't agree that you shouldn't do anything if you don't need the objects in the list anymore. If the objects implement the interface System.IDisposable then the designer of the object thought that the object holds scarce resources.

If you don't need the object anymore and just assign null to the object, then these scarce resources are not freed until the garbage collector finalizes the object. In the mean time you can't use this resource for something else.

Example: Consider you create a bitmap from a file, and decide you don't need neither the bitmap, nor the file anymore. Code could look like follows:

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.

The good method would be:

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

The same accounts for objects in a list, or any collection. All objects in the collection that are IDisposable should be disposed. Code should be like:

private void EmptySequence (IEnumerable sequence)
{   // throws away all elements in the sequence, if needed disposes them
    foreach (object o in sequence)
    {
        // uses modern pattern-matching
        if (disposableObject is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

Or if you want to create an IEnumerable extension function

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

All lists / dictionaries / read only lists / collections / etc can use these methods, because they all implement IEnumerable interface. You can even use it if not all items in the sequence implement System.IDisposable.


Firstly, you cannot "dispose" a list since it isn't IDisposable, and you can't force it to be collected since that isn't how C# works. Typically you would do nothing here. So when might we need to do anything?

  • If it is a method variable, and your method is going to exit in a moment, don't do anything: let the GC worry about it at some point after the method has existed.
  • If it is a field (instance variable), and the object is going to go out of scope in a moment, don't do anything: let the GC worry about it at some point after the instance is unreachable.

The only time you need to anything is if it is a field (or captured variable / iterator block variable / etc) and the instance (/delegate/iterator) is going to live a long while longer - then perhaps set the list field to null. Note, however, that if any other code still has a reference to the list then everything will still be reachable.


Another idea for this post... If you were wanting to ensure that all members of a collection are properly disposed, you could use the following extension method:

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

This looks through the collection for any member that implements IDisposableand disposing of it. From your executing code, you could clean up the list like this:

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

This will ensure that all members get the chance to release resources and the resulting list is empty.


You haven't provided enough context. Scope is critical here.

I think the GC should be smart enough to deal with the memory allocated for users and the collection without having to set anything to null.

If the collection removes users that aren't necessary from the collection, and no other objects refer to them, they'll be GC'd without you having to provide any hints.

The GC will not clean up an object as long as there's a live reference to it. Eliminate all the references and it can do its job.


Yet another example of an extension method you can use to dispose a list of objects which implement the IDisposable interface. This one uses LINQ syntax.

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


And a generic implementation which will be worked (appear in List<T> method list) if the item implemented IDisposable

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

To be used in this way

if(list != null)
{
  list.DisposeItems();                
  list.Clear();
}


There is a much better way when using System.Reactive.Disposeables:

Just initialize a new property of type CompositeDisposable and add the disposables to this collection. Then dispose just this one.

Here is a code example how to do it in an typical WPF/UWP ViewModel without indroducing any memory leaks:

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() { /* ... */ }

implement IDisposable like this:

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

and here is the extension class to get the .AddRange() method:

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

See also

  • BooleanDisposable lets you query if the object already got disposed
  • CancellationDisposable is like BooleanDisposable but with a cancellation token
  • ContextDisposable lets you dispose in a given thread context
  • MultipleAssignmentDisposable replaces one disposable with another without disposing the old disposable
  • SerialDisposable replaces the old disposalbe with another while disposing the old disposable
  • SingleAssignmentDisposable stores a disposable that cannot replaed with another disposable


Best way is

userCollection= null;

Than GC will take care of rest.


Many of these answers have something like...

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

There's not a single answer which mention of why .Clear() is helpful. The answer is decoupling the items in the collection from the both the collection and each other.

The more you decouple on tear down of any object the greater the chance the garbage collector will do it's job in a timely manner. Often substantial .NET memory leaks result from large graphs of objects which are 99% unused which have one item that is still being referenced.

I think its good practice to...

  • unsubscribe from any events that the object is subscribed to
  • clear any collections held
  • and set all the members to null.

...in a class implementing IDisposable.

I'm not suggesting implementing IDisposable on everything and doing this, I'm saying if it so happens that you need to implement dispose you might as well do it. I only implement IDisposable when the object...

  • subscribe to events or
  • owns members which implement Dispose() such as a DB connection or a Bitmap or
  • has other unmanaged resources.

The only time I would only implement Dispose just to decouple an object would be when you know you have a memory leak and the analysis of a memory profiler suggested it might be helpful.


Why do you want to dispose the list? The GC will do it for you if there is no references to it anymore.

Garbage Collection: msdn.microsoft.com/en-us/library/0xy59wtx.aspx


As everyone has mentioned leave to GC, its the best option and don't force the GC. Setting the variable to null will mark the variable for the GC.

if your after more info: Best Practice for Forcing Garbage Collection in C#


One other idea is to use brackets that include the scope of your variable that you wish to keep.

for example.

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


I've come across scenarios where, when large amounts of data are being processed, the GC doesn't clean-up until after the collection has gone out of scope (technically the GC does its collection when it sees fit and this may not be when then collection goes out of scope).

In these (rare) scenarios, I've used the following class:

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

You can then use it just like a normal List, e.g.

var myList = new DisposableList<MyObject>();

Then call the Dispose method when you're finished:

myList.Dispose();

Or, alternatively, declare it in a using statement:

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

This then causes the GC to do its collection immediately once the DisposableList is out of scope or disposed.


I see many answers calling Dispose of an object within a foreach loop over a collection. Since Dispose just marks the object to be removed the next time the garbage collector runs, it will work ok. In theory however, disposing an item could modify the collection and breaking the foreach, so it would be more robust to first collect those disposable objects, clear the original list, and calling the dispose within a for or while loop starting from the end and removing the object in each iteration, e.g. calling the following method:

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

I am actually using the DeleteItemsInList for other purposes, e.g. to delete files: DeleteItemsInList(File.Delete) )

As those have stated, in general case, it should not be necessary to dispose such a list. A case where I do dispose item in a list is working with Stream, I collect a few steams, transform the data from them, and then dispose these stream and keep only my transformed objects for further processing.


I hope you've encountered an out of memory exception if you are asking this question, if not you should create a test to cause an out of memory exception.

Assuming you actually have memory issues, you need to identify what in the User object is consuming all of your memory. Set the properties in the User object to null which are consuming the most memory.

User.BigData = null;

Then you can keep your list of users and let the garbage collector cleanup the properties that are consuming all of your memory.


If your item in list is un-managed object then you can call Dispose() on every object by iterating it.

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

If the list object is managed object then you need not do anything. GC will take care.


This might help someone :

public class DisposableList<T> : List<T>, IDisposable where T : IDisposable
{
    public void Dispose()
    {
        foreach (var item in this)
            item?.Dispose();
    }
}

or simpler :

public class DisposableList : List<IDisposable>, IDisposable
{
    public void Dispose()
    {
        foreach (var item in this)
            item?.Dispose();
    }
}


I think a smarter way of doing this is to create a custom scope.

   public sealed class DisposableScope : IDisposable
   {
      // you can use ConcurrentQueue if you need thread-safe solution
      private readonly Queue<IDisposable> _disposables = new();

      public T Using<T>(T disposable) where T : IDisposable
      {
         _disposables.Enqueue(disposable);
         return disposable;
      }

      public void Dispose()
      {
         foreach (var item in _disposables)
            item.Dispose();
      }
   }

e.g usage :

create and dispose of multiple files (it can be any disposable object though)

using var scope = new DisposableScope();

foreach (var fileName in files)
{
   var file = scope.Using(File.Create(fileName));
   
   // some other action
}

OurDisposableScope can safely handles everything.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜