[英]C# .NET preventing an object to dispose what it shouldn't
我正在做一個大項目,並且出現了一個問題:假設我有一個數據庫加載到內存中,該數據庫存儲了廣泛使用的數據。 但是我必須管理是否未將數據加載到內存中,因此我必須下載它,然后在完成后將其處理。
但是我很容易犯一個錯誤:我可以像手動加載數據庫一樣處理該數據庫。 我想防止自己處置數據庫,即使我在數據庫上調用Dispose()
方法也是如此。
我想到了跟蹤誰可以處置數據庫的想法。 當然,唯一允許這樣做的人就是創建數據庫實例的人。
問題示例,記錄在案:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DisposePrevention
{
/// <summary>
/// A bottle containing alcoholics
/// </summary>
class Bottle:IDisposable
{
private static int Id = 0;
private int localId;
public Bottle()
{
this.localId = Bottle.Id++;
}
public void Dispose()
{
//do the trick.
}
public override string ToString()
{
return "Bottle - " + this.localId.ToString();
}
}
/// <summary>
/// A shelf storing bottles
/// </summary>
class Shelf : IDisposable
{
public List<Bottle> Content;
public void Fill()
{
if (this.Content == null)
{
this.Content = new List<Bottle>();
}
for (int i = 0; i < 5; i++)
{
this.Content.Add(new Bottle());
}
}
public void Dispose()
{
if (this.Content == null)
{
return;
}
foreach (Bottle b in this.Content)
{
b.Dispose();
}
}
}
/// <summary>
/// A bartender serving drinks
/// </summary>
class Bartender : IDisposable // very simplified.
{
public List<Shelf> Shelves;
public Bartender()
{
this.Shelves = new List<Shelf>();
for (int i = 0; i < 3; i++)
{
Shelf s = new Shelf();
s.Fill();
this.Shelves.Add(s);
}
}
public void Dispose()
{
if (this.Shelves != null)
{
foreach (Shelf actualShelf in this.Shelves)
{
if ((actualShelf == null) || actualShelf.Content == null)
{
continue;
}
foreach (Bottle bottleItem in actualShelf.Content)
{
bottleItem.Dispose(); // We can call this, because Content is public, but we shouldn't.
}
actualShelf.Dispose();
}
this.Shelves.Clear();
}
}
/// <summary>
/// What can we drink, Sir?
/// </summary>
public void Print()
{
Console.WriteLine("------------------");
if (this.Shelves != null)
{
foreach (Shelf actualShelf in this.Shelves)
{
if ((actualShelf == null) || actualShelf.Content == null)
{
continue;
}
foreach (Bottle bottleItem in actualShelf.Content)
{
Console.WriteLine(bottleItem.ToString());
}
}
}
Console.WriteLine("------------------");
}
/// <summary>
/// Two bartenders can use the same source of drinks.
/// </summary>
/// <param name="list"></param>
internal void AttachShelves(List<Shelf> list)
{
this.Shelves = list;
}
/// <summary>
/// The boss can fire him, so he no longer gets access to the drinks.
/// </summary>
internal void DetachShelves()
{
this.Shelves = null;
}
}
class Program
{
static void Main(string[] args)
{
Bartender john = new Bartender();
Bartender steven = new Bartender();
steven.AttachShelves(john.Shelves);
Console.WriteLine("John:");
john.Print();
Console.WriteLine("Steven");
steven.Print();
Console.WriteLine("");
Console.WriteLine("Calling Dispose.");
Console.WriteLine("");
john.Dispose(); // we kick John. But at this point, we should've called "john.DetachShelves();"
Console.WriteLine("John");
john.Print();
Console.WriteLine("Steven");
steven.Print(); // Steven is sad. We should not allow John to dispose the alcoholics.
Console.ReadLine();
}
}
}
結果:
John:
------------------
Bottle - 0
Bottle - 1
Bottle - 2
Bottle - 3
Bottle - 4
Bottle - 5
Bottle - 6
Bottle - 7
Bottle - 8
Bottle - 9
Bottle - 10
Bottle - 11
Bottle - 12
Bottle - 13
Bottle - 14
------------------
Steven
------------------
Bottle - 0
Bottle - 1
Bottle - 2
Bottle - 3
Bottle - 4
Bottle - 5
Bottle - 6
Bottle - 7
Bottle - 8
Bottle - 9
Bottle - 10
Bottle - 11
Bottle - 12
Bottle - 13
Bottle - 14
------------------
Calling Dispose.
John
------------------
------------------
Steven
------------------
------------------
GCHandle
-s來防止泄漏(保留對防止GC
收集的對象的引用) unsafe
代碼...(這是WPF和Silverlight項目) 想法:我可能寫了一個包裝器,但是仍然存在引用問題。
題:
我想防止John能夠在貨架上調用Dispose()。 是否有“最佳實踐”來做到這一點?
提前致謝!
編輯:包裝器
/// <summary>
/// A shelf storing bottles
/// </summary>
class ShelfWrapped : IDisposable
{
public List<Bottle> Content;
public void Fill()
{
if (this.Content == null)
{
this.Content = new List<Bottle>();
}
for (int i = 0; i < 5; i++)
{
this.Content.Add(new Bottle());
}
}
public void Dispose()
{
if (this.Content == null)
{
return;
}
foreach (Bottle b in this.Content)
{
b.Dispose();
}
}
}
/// <summary>
/// Wrapper for a shelf storing bottles
/// </summary>
class Shelf:IDisposable
{
private ShelfWrapped InnerShelf = null;
public Shelf()
{
this.InnerShelf = new ShelfWrapped();
}
public void Fill()
{
if (this.InnerShelf == null)
{
this.InnerShelf = new ShelfWrapped();
}
this.InnerShelf.Fill();
}
public ShelfWrapped GetShelf()
{
return this.InnerShelf;
}
private List<Bartender> AllowedToDispose = new List<Bartender>();
public void Dispose(object disposer)
{
if (this.AllowedToDispose.Contains(disposer))
{
if (InnerShelf != null)
{
this.InnerShelf.Dispose();
}
}
}
public void Dispose()
{
// And again, John can dispose the shelf...
}
}
通常,一次性物品應擁有明確的所有者,而物品只能處置其擁有的物品。 有時,可能有必要讓某個字段屬於某個類型的實例,但不屬於其他實例; 在這種情況下,應將字段與另一個字段組合起來,以表明該字段是否擁有所討論的實例。 例如,如果FunStream
類從Stream
繼承並包裝Stream
,則實例創建后, 如果創建該實例的代碼不再使用基礎流,則實例應調用Dispose
基礎流,但如果實例不再使用基礎流,則實例應調用Dispose
處置FunStream
后,創建它的代碼將希望繼續使用Stream
。 由於創建FunStream
的代碼將知道其期望的模式,因此FunStream
構造函數或工廠方法應提供一個參數,以指示FunStream
是否應承擔該流的所有權。
當一個對象實際上是不可變的,但仍然封裝資源時,唯一會造成重大困難的情況是發生。 不變的對象通常是自由共享的,因此經常被共享。 盡管應該在沒有人需要時釋放資源,但是很難預測哪個不可變對象將是使用該資源的最后一個對象。 處理這種情況的最佳方法可能是擁有一個LifetimeManager
類,該類將使用一些ConditionalWeakTable
對象與每個類關聯一些仍在使用它的對象的弱引用列表,並提供DisposeIfNotNeeded(IDisposable resource, Object owner)
; 這樣會將owner
從仍然需要資源的對象列表中刪除,並在沒有更多所有者時將其處置。 我不知道任何現有的實現,但是這種事情的設計可能有點棘手。 不過,使用此類可能是確保封裝資源的共享對象得到適當定時處置的最干凈的方法。
我認為GC應該由框架來處理。 我曾經嘗試對內存進行微管理,因為在.NET出現之前這是必需的,但是除了“怪異”問題外,它什么也沒有引起。 我的建議是不要理會GC甚至處置。 僅當在項目(非托管對象)中實現COM時,才真正需要進行處理。 看到此以獲得更多信息-http: //msdn.microsoft.com/zh-CN/library/fs2xkftw%28v=vs.110%29.aspx
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.