如何 return 一个依赖于一系列 using 语句的对象?

How to return an object that's dependent on a chain of using statements?

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

C Make()
{
    using (var a = new A())
    using (var b = new B(a))
    {
        return new C(b);
    }
}

这很糟糕,因为当方法 returns、c 保留对已处置对象的引用时。

注意:

这是说明文档很重要的一个很好的例子。它构成了 class.

合同的一部分

你当然已经意识到了这一点,既然你说了,

the author of C stated that C does not take ownership of b.

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

您需要稍微重构此代码。要么更改 Make() 使其接受类型为 B 的参数;调用者将对 BC 的生命周期负责。或者编写一个新的 class 来实现 IDisposable 并包装 ABC 并通过 属性 公开 C

如果您拥有类型 C,您可以考虑修改它以允许它有选择地取得 b 的所有权。这是 .NET 本身中相当常见的模式。参见,例如,XmlReaderSettings.CloseInput.

This is bad since I cant reference a and b from outside and dispose of them.

只有当您保留对 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
    }
}

Unfortunately, C keeps a reference to it's b throughout its lifetime and expects the caller to dispose of it when C is no longer needed

如果您无法控制 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();
    }
}

删除 Busing 语句,并在完成后处理 WrapC

你的情况和我在查询数据库时有时看到的很相似。为了分离逻辑,您有时会看到这样的代码:

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

但是,当然,这行不通,因为到 ExecuteSQL 方法的 SqlDataReader returns 时,连接已经关闭(处置)。

因此,上述方法的替代方法是使用委托来实现关注点分离,但仍然允许代码正常工作:

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