如何返回依赖于using语句链的对象?

时间:2015-07-14 14:21:24

标签: c# idisposable

我想写一个类似于这个的方法:

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
  • 的所有权

3 个答案:

答案 0 :(得分:3)

这是文档很重要的地方的一个很好的例子。它构成了课程合同的一部分。

当然,你已经意识到这一点,因为你说,

  

C的作者声称C并未取得b的所有权。

这意味着您无法实现此后的目标。您所拥有的内容可能不正确,因为ab将在新C返回之前立即处理。

您需要稍微重构此代码。更改Make(),使其采用B类型的参数;来电者将对B以及C的有效期负责。或者编写一个实现IDisposable并包装ABC的新类,并通过属性公开C

如果您拥有类型C,则可以考虑对其进行修改,以允许其选择取得b的所有权。这是.NET本身的一种相当常见的模式。例如,请参阅XmlReaderSettings.CloseInput

答案 1 :(得分:2)

  

这很糟糕,因为我无法从外部引用ab并将其丢弃。

仅当您保留对ab的引用时才会出错,这些引用可以从代码外部处理。但是,这些对象是一次性的,并且因为C没有创建它们或者转移所有权,所以它应该在完成构造函数之前从AB获取所需的任何内容,而不是保留对一次性物品的引用:

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

但是,当然,这不起作用,因为当SqlDataReaderExecuteSQL方法返回时,连接已经关闭(处置)。

所以上面的替代方案利用委托来实现关注点的分离,但仍然允许代码正常工作:

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