处理列表的最佳方式

时间:2011-07-06 11:27:02

标签: c# asp.net list garbage-collection dispose

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

哪一个最好?

18 个答案:

答案 0 :(得分:21)

最好的办法是将它留给垃圾收集器。 您的foreach将不执行任何操作,因为只有参考将设置为null而不是列表中的元素。将列表设置为null实际上可能导致垃圾收集发生得比它可能发生得晚(参见本文C#: should object variables be assigned to null?)。

答案 1 :(得分:18)

首先,你不能“处置”一个列表,因为它不是IDisposable,你不能强制它被收集,因为这不是C#的工作方式 通常 ,您可以在此处 nothing 。那么当可能时,我们需要做 任何事情

  • 如果它是方法变量,并且您的方法稍后要退出,请不要做任何事情:让GC在方法存在后的某个时刻担心它。
  • 如果它是一个字段(实例变量),并且该对象稍后将超出范围,请不要执行任何操作:让GC在实例无法访问后的某个时刻担心它。

你需要任何的唯一时间是它是一个字段(或捕获的变量/迭代器块变量/等) 实例(/ delegate / iterator)将持续很长时间 - 然后可能将列表字段设置为null。但请注意,如果任何其他代码仍然具有对列表的引用,那么仍然可以访问所有内容。

答案 2 :(得分:15)

如果您不再需要列表中的对象,我不同意您不应该做任何事情。如果对象实现了接口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);

列表中的对象或任何集合的相同帐户。应该处理集合中IDisisable的所有对象。代码应该是:

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,您甚至可以使用它。

答案 3 :(得分:12)

这篇文章的另一个想法......如果你想确保集合的所有成员都得到妥善处理,你可以使用以下扩展方法:

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

这将确保所有成员都有机会释放资源,结果列表为空。

答案 4 :(得分:2)

您没有提供足够的背景信息。范围在这里至关重要。

我认为GC应该足够聪明,可以处理为用户和集合分配的内存,而无需将任何内容设置为null。

如果该集合从集合中删除了不必要的用户,并且没有其他对象引用它们,则无需提供任何提示即可进行GC。

只要有对象的实时引用,GC就不会清理对象。消除所有引用,它可以完成它的工作。

答案 5 :(得分:2)

您可以使用扩展方法的另一个示例来处理实现IDisposable接口的对象列表。这个使用LINQ语法。

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

答案 6 :(得分:1)

最好的方法是

userCollection= null;

比GC要好好休息。

答案 7 :(得分:1)

使用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存放一个不能用另一个一次性替换的一次性用品

答案 8 :(得分:1)

如果项目已实施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();
}

答案 9 :(得分:0)

我遇到过这样的情况:当处理大量数据时,GC不会在收集超出范围之后进行清理(从技术上讲,GC会在其认为合适的时候进行收集。可能不是当集合超出范围时。)

在这些(罕见)场景中,我使用了以下类:

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超出范围或处理后立即进行收集。

答案 10 :(得分:0)

另一个想法是使用括号,其中包含您希望保留的变量范围。

例如。

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

答案 11 :(得分:0)

正如大家都提到离开GC一样,它是最好的选择,不要强迫GC。 将变量设置为null将标记GC的变量。

如果您了解更多信息:Best Practice for Forcing Garbage Collection in C#

答案 12 :(得分:0)

为什么要处理这个清单?如果没有对它的引用,GC将为您完成。

垃圾收集:msdn.microsoft.com/en-us/library/0xy59wtx.aspx

答案 13 :(得分:0)

我看到很多答案调用在一个集合中的foreach循环中处理一个对象。 由于Dispose只是在下次运行垃圾收集器时标记要删除的对象,因此它可以正常工作。然而,理论上,处理项目可能会修改集合并破坏foreach,因此首先收集这些一次性对象,清除原始列表,并从最后开始调用for或while循环中的dispose并删除它会更加健壮每次迭代中的对象,例如调用以下方法:

    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,我收集一些流,从中转换数据,然后处理这些流并仅保留我的转换对象以供进一步处理。

答案 14 :(得分:0)

我希望你在问这个问题时遇到内存不足异常,如果不是,你应该创建一个导致内存不足异常的测试。

假设您确实遇到了内存问题,则需要确定User对象中正在消耗所有内存的内容。将User对象中的属性设置为null,消耗最多的内存。

User.BigData = null;

然后你可以保留你的用户列表,让垃圾收集器清理占用你所有内存的属性。

答案 15 :(得分:0)

如果列表中的项目是非托管对象,则可以通过迭代对每个对象调用Dispose()。

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

如果列表对象是托管对象,则您无需执行任何操作。 GC将保重。

答案 16 :(得分:0)

这些答案中的许多答案都类似于...

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并这样做,我是说如果发生这种情况,那么您需要实现处置就可以了。当对象...时,我仅实现IDisposable。

  • 订阅事件或
  • 拥有实现Dispose()的成员,例如数据库连接或位图或
  • 还有其他不受管理的资源。

仅当知道内存泄漏并且对内存探查器的分析表明这可能会有所帮助时,我才唯一实现Dispose来解除对象耦合的操作。

答案 17 :(得分:0)

这可能会帮助某人:

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

或更简单:

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