2011-07-06 181 views
26

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

哪一個最好?

+4

您的代碼不包含任何處置操作。 – CodesInChaos

+1

Dispose用於釋放*非託管*資源。 如果列表沒有任何引用,它將在適當的時候由garbabe收集器發佈。 –

+2

@Paolo和一些管理資源的提示清理(某種實現特定種類);但是,非託管更常見 –

回答

18

最好的辦法是把它留給垃圾收集器。 您的foreach將不會執行任何操作,因爲只有參考將設置爲null而不是列表中的元素。將列表設置爲null實際上可能會導致垃圾回收比它可能發生的時間晚(請參閱此文章C#: should object variables be assigned to null?)。

+1

您鏈接了整個線程。你能否更具體地說明爲什麼在這種情況下垃圾收集可能會晚些時候發生?或者甚至可以引用相關的段落。 – Virtlink

+0

@Virtlink - 文章指出「你實際上可能會延長對象留在內存中的時間,因爲CLR會認爲直到方法結束才能收集對象,因爲它看到了對象方法的代碼引用在那裏。「 – RadioSpace

+1

這取決於您在收藏中存儲的對象的類型。如果其中一些實現IDisposable,設計者希望鼓勵您致電IDisposable。Dispose()而不是讓垃圾收集器爲你做這件事。通常這是因爲資源稀缺。例如,如果一次只有一個對象可以使用攝像頭,那麼只需指定null將不會立即釋放攝像頭,而調用Dispose()會執行此操作。 –

1

最好的辦法是

userCollection= null; 

比GC將剩下的事情。

+0

-1 yyyyyyyy ?????????有什麼缺失 –

+1

我沒有投票,但肯定可以解釋。什麼時候這個「麻雀」甚至需要?爲什麼要在丟棄參考文件之前清除一個列表?爲什麼在它超出範圍時將引用設置爲'null'? – Kobi

+0

當然。不需要循環自己。 – Zhen

2

您尚未提供足夠的上下文。範圍在這裏至關重要。

我認爲GC應該足夠聰明來處理分配給用戶和集合的內存,而不必將任何內容設置爲null。

如果集合從集合中刪除了不需要的用戶,並且沒有其他對象引用它們,則它們將被GC'd而不必提供任何提示。

只要有實時引用,GC就不會清理對象。消除所有參考,它可以完成它的工作。

17

首先,你不能「處理」列表,因爲它不是IDisposable,你不能強迫它收集因爲這是不是C#是如何工作的通常你會做沒有在這裏。所以當可能我們需要做什麼東西

  • 如果它是一個方法變量,並且您的方法將立即退出,請不要做任何事情:讓GC在方法存在後的某個時刻擔心它。
  • 如果它是一個字段(實例變量),並且對象即將超出範圍,請不要做任何事情:讓GC在實例無法訪問後的某個時刻擔心它。

需要任何唯一的一次是,如果它是一個場(或被俘變量/迭代器塊可變的/ etc)實例(/代理/迭代器)是要活很長時間 - 然後可能將列表字段設置爲空。但是請注意,如果其他代碼仍然有對列表的引用,那麼所有內容仍然可以訪問。

+3

我不同意如果您不再需要列表中的對象,則不應該執行任何操作。如果這些對象實現了System.IDisposable接口,那麼該對象的設計者認爲該對象擁有稀缺資源。如果你不需要它,只需將null賦值給對象,那麼這些稀有資源不會被釋放,直到垃圾回收器完成對象。同時,您不能將此資源用於其他方面。 –

+4

@HaraldDutch「處置列表」和「處理列表中的項目」有區別。該清單通常無權假定它是這些物品的唯一所有者。 –

0

你爲什麼要處理這個列表?如果沒有對它的引用,GC會爲你做。

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

+0

你說得對,但是如果你的列表包含IDisposable對象,那麼在垃圾回收器完成它們之前,這些對象不是Disposed()。如果一個對象的設計者聲明對象IDisposable,他表示它擁有一些應該儘快釋放的稀缺資源。考慮從文件創建的位圖。即使您將空值分配給位圖,也無法在垃圾回收器完成位圖之前銷燬該文件。所以你永遠不知道你什麼時候可以刪除文件。 –

+0

如果您的列表包含IDisposable項目,那麼您的類型錯誤。您的列表類型本身應該是IDisposable。 –

0

因爲每個人都有提及休假GC,它是最好的選擇,不強制GC。 將變量設置爲空將標記GC的變量。

如果你以後更多的信息:Best Practice for Forcing Garbage Collection in C#

+1

根據.NET團隊的說法,將變量設置爲null實際上可能會導致其壽命超過必要的時間。運行時非常智能,並且不需要您將變量設置爲null,以確定它是否可以清除該內存。 GC的當前實現實際上查找對變量的引用 - 一旦沒有更多的代碼觸及該變量,當存在內存壓縮或備用週期時,GC將回收該內存。在方法的末尾將其設置爲null可以延長其壽命,因爲您正在創建變量的額外用法。 – RyanR

+0

如果你不處理對象,它最終將被垃圾收集器處置。這將釋放稀缺資源。問題是,你不知道什麼時候發生這種情況,因此不知道資源何時可用。從文件加載的示例位圖。如果您不處理位圖,則不知道何時可以刪除該文件。只需將null分配給它,不會釋放文件上的鎖定。如果你打電話處置,你確定該文件可以立即刪除 –

+0

爲什麼不強制gc? – Sharky

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

它真的有什麼區別嗎? GC是否足夠智能知道usersCollection在foreach循環後不再使用? 但是在調試模式下,它似乎不是垃圾收集。 –

+0

@ErwinMayer這是來自c/C++的「技巧」。它在正常情況下可能並不重要,但代碼速度快,代碼少... – Aristos

9

另一個想法爲這個職位...如果你想確保集合中的所有成員都妥善處理,你可以使用下面的擴展方法:

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

這將確保所有成員獲得釋放資源的機會,結果列表是空的。

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超出範圍或處置後立即收集。

+0

唯一的問題是您沒有重寫索引器,因此可以存儲對象,並且在處置List時不會處理它們。 –

+0

我不得不在一個正在處理大量數據的Windows服務上使用這段代碼。我對該服務進行了描述,它確實處理了列表中包含的對象。我不認爲索引器在這裏是相關的? –

12

我不同意你不應該做任何事情,如果你不需要列表中的對象了。如果這些對象實現了System.IDispoable接口,那麼該對象的設計者就會認爲該對象擁有稀缺資源。如果你不需要它,只需將null賦值給對象,那麼這些稀有資源不會被釋放,直到垃圾回收器完成對象。同時,您不能將此資源用於其他方面。

示例: 考慮從文件創建位圖,並決定不再需要位圖和文件。代碼可能看起來像如下:

using System.Drawing; 
Bitmap bmp = new Bitmap(fileName); 
// do something with bmp until not needed anymore 
bmp = null; 
File.Delete(fileName); // ERROR, 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,那麼甚至可以使用它。

2

可以用來處理實現IDisposable接口的對象列表的擴展方法的另一個示例。這個使用LINQ語法。

public static void Dispose(this IEnumerable collection) 
    { 
     foreach (var obj in collection.OfType<IDisposable>()) 
     { 
      obj.Dispose(); 
     } 
    } 
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(); 
} 
1

使用有一個使用時更好的方法System.Reactive.Disposeables

只需初始化一個CompositeDisposable類型的新屬性並將一次性添加到此集合。然後處理這一個。

下面是一個代碼示例如何做到這一點的一個典型的WPF/UWP視圖模型沒有indroducing任何內存泄漏:

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替換舊的處置ALBE與另一個在部署舊一次性
  • SingleAssignmentDisposable商店不能與其他一次性
0

我看到很多答案foreach循環在集合中調用對象的處置replaed一次性。 因爲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一起工作,我收集一些數據流,從它們中轉換數據,然後處理這些數據流並僅保留我的轉換對象以供進一步處理。