【问题标题】:Best way to dispose a list处理列表的最佳方式
【发布时间】:2011-07-06 11:27:02
【问题描述】:

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

哪个最好?

【问题讨论】:

  • 您的代码不包含任何 dispose 操作。
  • Dispose 用于释放非托管资源。如果列表没有任何对其的引用,它将由垃圾收集器在适当的时候释放。
  • @Paolo 并提示清理(某些特定于实现的类型)一些托管资源;但是,是的,非托管更常见
  • 注意 - 问题不是专门询问 IDisposable。 OP 只是在常识中使用 dispose 这个词。

标签: c# list garbage-collection dispose


【解决方案1】:

最好的办法是将它留给垃圾收集器。 您的 foreach 不会做任何事情,因为只有引用将设置为 null 而不是列表中的元素。将列表设置为null 实际上可能会导致垃圾收集发生的时间比它可能的时间晚(参见这篇文章C#: should object variables be assigned to null?)。

【讨论】:

  • 你链接了整个线程。您能否更具体地说明为什么在这种情况下稍后会发生垃圾收集?或者甚至可以引用相关段落。
  • @Virtlink - 文章指出“您实际上可能会延长对象在内存中的停留时间,因为 CLR 会认为直到方法结束才能收集对象,因为它看到对那里的对象的代码引用。"
  • 这取决于您存储在集合中的对象类型。如果其中一些实现了 IDisposable,设计者希望鼓励您调用 IDisposable.Dispose() 而不是让垃圾收集器为您执行此操作。通常这是因为资源稀缺。例如,如果一次只有一个对象可以使用相机,那么仅分配 null 不会立即释放相机,而调用 Dispose() 会这样做。
  • 我认为这个答案是赞成的,因为人们喜欢魔法发生的想法。我同意将局部变量设置为 null 是不可取的,并且可能会产生可维护性问题。在 IDisposable 中将成员设置为空时,我不同意。没有什么可以支持这种说法,即将项目设置为 null 实际上会妨碍垃圾收集器。以我对垃圾收集器实现的了解,这样的说法对我来说似乎完全违反直觉。
【解决方案2】:

如果您不再需要列表中的对象,我不同意您不应该做任何事情。如果对象实现了接口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)
    {
        // uses modern pattern-matching
        if (disposableObject is IDisposable disposable)
        {
            disposable.Dispose();
        }
    }
}

或者如果你想创建一个 IEnumerable 扩展函数

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

所有列表/字典/只读列表/集合/等都可以使用这些方法,因为它们都实现了IEnumerable接口。如果不是序列中的所有项目都实现 System.IDisposable,您甚至可以使用它。

【讨论】:

  • 该死的@HaraldCoppoolse 清理你的编辑队列,我无法修复/改进你的扩展方法!!无论如何我会把我的改进贴在这里,即使它看起来很糟糕/// &lt;summary&gt; /// Iterates over the IEnmuerable and disposes each object /// &lt;/summary&gt; public static void DisposeAll&lt;T&gt;(this IEnumerable&lt;T&gt; source) where T : IDisposable { foreach (var disposable in source) { disposable?.Dispose(); }; }
【解决方案3】:

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

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

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

【讨论】:

  • 如果您不再需要列表中的对象,我不同意您不应该做任何事情。如果对象实现 System.IDisposable 接口,则对象的设计者认为该对象拥有稀缺资源。如果您不需要它并且只是将 null 分配给该对象,那么这些稀缺资源不会被释放,直到垃圾收集器最终确定该对象。同时,您不能将此资源用于其他用途。
  • @HaraldDutch “处理列表”和“处理列表中的项目”是有区别的。该列表通常无权假定它是这些对象的唯一所有者。
  • 我同意@MarcGravell 只是因为列表有对象,并不意味着它控制它们。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

    最好的办法是

    userCollection= null;
    

    GC 会负责休息。

    【讨论】:

    • 我没有投票,但肯定可以解释。什么时候需要这个“Bast”?为什么要在丢弃参考之前清除列表?为什么在超出范围时将引用设置为null
    • 当然。不需要自己循环。
    • 如果您不释放一次性对象,则在垃圾收集器最终确定对象之前不会释放其资源。如果从文件创建位图,并将 null 分配给位图,则无法删除该文件,因为在处理之前它仍由位图使用。通过 GC 的最终确定。因此,您应该始终处理所有一次性对象,而不是仅仅分配 null
    【解决方案6】:

    您没有提供足够的上下文。范围在这里很关键。

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

    如果集合从集合中删除了不必要的用户,并且没有其他对象引用他们,那么他们将被 GC 处理而无需您提供任何提示。

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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        使用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在处理旧的一次性用品时用另一个替换旧的disposable
        • SingleAssignmentDisposable 存放一个不能用另一个一次性用品替换的一次性用品

        【讨论】:

          【解决方案9】:

          如果项目实现了IDisposable,则通用实现将起作用(出现在List&lt;T&gt;方法列表中)

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

          【讨论】:

            【解决方案10】:

            其中许多答案都有类似...

            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 并这样做,我是说如果发生这种情况,您需要实施 dispose,您不妨这样做。我只在对象时实现 IDisposable...

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

            我只会实现 Dispose 来解耦对象的唯一情况是当您知道您有内存泄漏并且内存分析器的分析表明它可能会有所帮助时。

            【讨论】:

              【解决方案11】:

              您为什么要处置该列表?如果不再有对它的引用,GC 将为您执行此操作。

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

              【讨论】:

              • 你是对的,但如果你的列表包含 IDisposable 对象,那么这些对象在垃圾收集器完成它们之前不是 Disposed() 。如果对象的设计者声明对象 IDisposable,他表示它拥有一些应该尽快释放的稀缺资源。考虑从文件创建的位图。即使您将 null 分配给位图,您也不能在垃圾收集器完成位图之前销毁文件。所以你永远不知道什么时候可以删除文件。
              • 如果您的列表包含 IDisposable 项目,那么您的类型错误。您的列表类型本身应该是 IDisposable。
              • 如果 OP 的用户列表越来越多,GC 将不执行任何操作。
              【解决方案12】:

              正如每个人都提到的那样,让 GC 是最好的选择,不要强迫 GC。 将变量设置为 null 将标记 GC 的变量。

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

              【讨论】:

              • 根据 .NET 团队的说法,将变量设置为 null 实际上会导致它的生存时间超过必要的时间。运行时非常智能,不需要您将变量设置为 null 即可确定它是否可以清理该内存。 GC 的当前实现实际上是在寻找对变量的引用——一旦没有进一步的代码触及该变量,GC 将在内存压力或空闲周期时回收该内存。在方法结束时将其设置为 null 可以延长其寿命,因为您正在创建变量的额外用法。
              • 如果不释放对象,最终会被垃圾回收器释放。这将释放稀缺资源。问题是您不知道何时会发生这种情况,因此不知道资源何时可用。从文件加载的示例位图。如果您不处理位图,您不知道何时可以删除该文件。仅将 null 分配给它不会释放文件上的锁定。如果你调用dispose,你确定文件可以立即删除
              • 为什么不强制gc?
              【解决方案13】:

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

              例如。

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

              【讨论】:

              • 真的有什么不同吗?难道 GC 还不够聪明,无法知道在 foreach 循环之后不再使用 usersCollection 吗?然而,在调试模式下,它似乎没有被垃圾收集。
              • @ErwinMayer 这是来自 c/c++ 的“技巧”。在正常情况下,它可能无关紧要,代码快速且代码少......
              【解决方案14】:

              我遇到过这样的场景,当处理大量数据时,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 超出范围或被释放后立即进行收集。

              【讨论】:

              • 唯一的问题是您没有覆盖索引器,因此可以存储对象并且在释放列表时不释放它们。
              • 我不得不在 Windows 服务上使用此代码,该服务以块的形式处理大量数据。我分析了该服务,它确实处理了列表中包含的对象。我认为索引器在这里不相关?
              【解决方案15】:

              我看到很多答案在集合的 foreach 循环中调用 Dispose 对象。 由于 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,我收集了一些流,从它们转换数据,然后处理这些流并只保留我转换的对象以供进一步处理。

              【讨论】:

                【解决方案16】:

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

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

                User.BigData = null;
                

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

                【讨论】:

                  【解决方案17】:

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

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

                  如果列表对象是托管对象,那么你不需要做任何事情。 GC会照顾的。

                  【讨论】:

                    【解决方案18】:

                    这可能对某人有帮助:

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

                    【讨论】:

                      【解决方案19】:

                      我认为更明智的做法是创建自定义范围。

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

                      例如用法:

                      创建和处理多个文件(它可以是任何一次性对象)

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

                      OurDisposableScope 可以安全地处理一切。

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2013-11-27
                        • 1970-01-01
                        • 2011-11-11
                        • 2010-09-21
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-09-20
                        相关资源
                        最近更新 更多