如果我有一个SomeDisposableObject
实现的类IDisposable
:
class SomeDisposableObject : IDisposable { public void Dispose() { // Do some important disposal work. } }
我有另一个叫做的类AContainer
,它有SomeDisposableObject
一个公共属性的实例:
class AContainer { SomeDisposableObject m_someObject = new SomeDisposableObject(); public SomeDisposableObject SomeObject { get { return m_someObject; } set { m_someObject = value; } } }
然后FxCop将坚持AContainer
也是IDisposable
.
这是很好的,但我看不出我可以安全地调用m_someObject.Dispose()
从AContainer.Dispose()
,作为另一个类可能还是要在一个参考m_someObject
实例.
避免这种情况的最佳方法是什么?
(假设其他代码依赖于AContainer.SomeObject
始终具有非空值,因此只是将实例的创建移到外部AContainer
不是一个选项)
编辑:我将扩展一些例子,因为我认为一些评论者错过了这个问题.如果我只实现一个调用m_someObject.Dispose()的Dispose()
方法,AContainer
那么我将留下这些情况:
// Example One AContainer container1 = new AContainer(); SomeDisposableObject obj1 = container1.SomeObject; container1.Dispose(); obj1.DoSomething(); // BAD because obj1 has been disposed by container1. // Example Two AContainer container2 = new AContainer(); SomeObject obj2 = new SomeObject(); container2.SomeObject = obj2; // BAD because the previous value of SomeObject not disposed. container2.Dispose(); obj2.DoSomething(); // BAD because obj2 has been disposed by container2, which doesn't really "own" it anyway.
这有帮助吗?
没有一个答案,这取决于你的情况,关键点是财产所代表的可支配资源的所有权,正如Jon Skeet指出的那样.
查看.NET Framework中的示例有时很有帮助.以下是三个表现不同的示例:
容器总是处置.System.IO.StreamReader公开一次性属性BaseStream.它被认为拥有底层流,并且处理StreamReader始终处理底层流.
容器永远不会丢弃.System.DirectoryServices.DirectoryEntry公开Parent属性.它不被认为拥有其父级,因此处理DirectoryEntry永远不会释放其父级.
在这种情况下,每次取消引用Parent属性时都会返回一个新的DirectoryEntry实例,并且可能需要调用者处理它.可以说这打破了属性的准则,也许应该有一个GetParent()方法.
容器有时处理.System.Data.SqlClient.SqlDataReader公开一次性Connection属性,但调用者使用SqlCommand.ExecuteReader的CommandBehavior参数决定读者是否拥有(并因此处置)底层连接.
另一个有趣的例子是System.DirectoryServices.DirectorySearcher,它具有读/写一次性属性SearchRoot.如果从外部设置此属性,则假定基础资源不属于该属性,因此容器不会处置该属性.如果它不是从外部设置的,则在内部生成引用,并设置一个标志以确保它将被丢弃.你可以用Lutz Reflector看到这个.
您需要确定容器是否拥有该资源,并确保准确记录其行为.
如果您确定拥有该资源,并且该属性是可读/写的,则需要确保您的setter处理它正在替换的任何引用,例如:
public SomeDisposableObject SomeObject { get { return m_someObject; } set { if ((m_someObject != null) && (!object.ReferenceEquals(m_someObject, value)) { m_someObject.Dispose(); } m_someObject = value; } } private SomeDisposableObject m_someObject;
更新:GrahamS在评论中正确地指出在处置之前最好在setter中测试m_someObject!= value:我已经更新了上面的例子以考虑到这一点(使用ReferenceEquals而不是!=显式).虽然在许多现实场景中,setter的存在可能意味着该对象不归容器所有,因此不会被处置.
这实际上取决于谁在理论上"拥有"一次性物品.在某些情况下,您可能希望能够传入对象,例如在构造函数中,而不会让您的类负责清理它.其他时候你可能想要自己清理它.如果您正在创建对象(如示例代码中所示),那么清理它几乎肯定是您的责任.
至于财产 - 我不认为拥有财产应该真正转移所有权或类似的东西.如果您的类型负责处理对象,则应该保留该责任.
真正的问题可能是您的面向对象设计.如果AContainer是Disposed,则也应该处理其所有成员对象.如果不是这听起来像你可以处置一个身体但想要保持腿部实例生活.听起来不对劲.