我想写一个类似于这个的方法:
C Make()
{
using (var a = new A())
using (var b = new B(a))
{
return new C(b);
}
}
这很糟糕,因为当方法返回时,c
会保留对已处置对象的引用。
请注意:
A
实施IDisposable
。 B
实施IDisposable
。C
由于IDisposable
的作者声明C
,因此请勿实施C
不接受b
。答案 0 :(得分:3)
这是文档很重要的地方的一个很好的例子。它构成了课程合同的一部分。
当然,你已经意识到这一点,因为你说,
C
的作者声称C
并未取得b
的所有权。
这意味着您无法实现此后的目标。您所拥有的内容可能不正确,因为a
和b
将在新C
返回之前立即处理。
您需要稍微重构此代码。更改Make()
,使其采用B
类型的参数;来电者将对B
以及C
的有效期负责。或者编写一个实现IDisposable
并包装A
,B
和C
的新类,并通过属性公开C
。
如果您拥有类型C
,则可以考虑对其进行修改,以允许其选择取得b
的所有权。这是.NET本身的一种相当常见的模式。例如,请参阅XmlReaderSettings.CloseInput。
答案 1 :(得分:2)
这很糟糕,因为我无法从外部引用
a
和b
并将其丢弃。
仅当您保留对a
或b
的引用时才会出错,这些引用可以从代码外部处理。但是,这些对象是一次性的,并且因为C
没有创建它们或者转移所有权,所以它应该在完成构造函数之前从A
或B
获取所需的任何内容,而不是保留对一次性物品的引用:
class C {
private readonly string x;
private readonly int y;
public C(B b) {
// Using b here is OK
x = b.X;
y = b.Y;
// We are done with b, so when b is disposed, C will not break
}
}
不幸的是,
C
在其生命周期内始终引用它b
,并期望调用者在不再需要C
时处理它
如果您无法控制C
,请为其设置IDisposable
包装,取得B
的所有权,并在不再需要C
时将其处置:
class WrapC : IDisposable {
private readonly B b;
public C C { get; private set; }
public WrapC (B b) {
this.b = b;
C = new C(b);
}
public void Dispose() {
b.Dispose();
}
}
删除using
的{{1}}语句,并在完成后处置B
。
答案 2 :(得分:0)
您的情况与我在查询数据库时遇到的情况非常相似。在尝试分离逻辑时,您有时会看到如下代码:
var reader = ExecuteSQL("SELECT ...");
while (reader.Read()) // <-- this fails, because the connection is closed.
{
// process row...
}
public SqlDataReader ExecuteSQL(string sql)
{
using (SqlConnection conn = new SqlConnection("..."))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
return cmd.ExecuteReader();
}
}
}
但是,当然,这不起作用,因为当SqlDataReader
从ExecuteSQL
方法返回时,连接已经关闭(处置)。
所以上面的替代方案利用委托来实现关注点的分离,但仍然允许代码正常工作:
ExecuteSQL(reader => {
// write code that reads from the SqlDataReader here.
});
public void ExecuteSQL(string sql, Action<SqlDataReader> processRow)
{
using (SqlConnection conn = new SqlConnection("..."))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
processRow(reader);
}
}
}
}
}
那么,也许你可以在你的情况下尝试类似的东西?像这样:
MakeAndProcess(c => {
// write code that uses C here.
// This will work, because A and B are not disposed yet.
});
public void MakeAndProcess(Action<C> processC)
{
using (var a = new A())
using (var b = new B(a))
{
processC(new C(b));
}
}