Best way to dispose a list

2019-01-22 18:08发布

问题:

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 set userCollection = null; what will happen?

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

Which one is best?

回答1:

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?).



回答2:

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.



回答3:

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)
    {
        System.IDisposable disposableObject = o as System.IDisposable;
        o = null;
        if (disposableObject != null)
        {
            disposableObject.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.



回答4:

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.



回答5:

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.



回答6:

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


回答7:

Best way is

userCollection= null;

Than GC will take care of rest.



回答8:

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


回答9:

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


回答10:

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



回答11:

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#



回答12:

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


回答13:

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.



回答14:

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.



回答15:

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.



回答16:

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.