正确的处置方式:对象未沿所有异常路径释放

本文关键字:异常 释放 路径 对象 方式 | 更新日期: 2023-09-27 18:30:49

我收到第 84 行和第 85 行的消息(这两个,使用行堆叠):

CA2000:Microsoft.可靠性:在方法'RavenDataAccess.GetRavenDatabase()'中,对象'<>g_initLocal9'不会沿所有异常路径释放。调用 System.IDisposable.在对象 '<>g_initLocal9' 上释放,然后所有对它的引用都超出范围。

DocumentStore 实现了 IDisposable。

为什么?我还能如何释放文档存储对象?它们是在使用块中创建的,我将它们丢弃在我的捕获块中。应该如何解决这个问题?

private static IDocumentStore GetRavenDatabase()
{
    Shards shards = new Shards();
    try
    {
        using (DocumentStore docStore1 = new DocumentStore { Url = ConfigurationManager.AppSettings["RavenShard1"] })  // Line 84
        using (DocumentStore docStore2 = new DocumentStore { Url = ConfigurationManager.AppSettings["RavenShard2"] })  // Line 85
        {
            shards.Add(docStore1);
            shards.Add(docStore2);
        }
        using (ShardedDocumentStore documentStore = new ShardedDocumentStore(new ShardStrategy(), shards))
        {
            documentStore.Initialize();
            IndexCreation.CreateIndexes(typeof(RavenDataAccess).Assembly, documentStore);
            return documentStore;
        }
    }
    catch
    {
        shards.ForEach(docStore => docStore.Dispose());
        throw;
    }
}

正确的处置方式:对象未沿所有异常路径释放

您必须确保按照任何可能的异常路径处置所有新创建的一次性对象。见下文:

private static IDocumentStore GetRavenDatabase()
{
    Shards shards = new Shards();
    DocumentStore docStore1 = null;
    DocumentStore docStore2 = null;
    ShardedDocumentStore shardedDocumentStore = null;
    ShardedDocumentStore tempShardedDocumentStore = null;
    try
    {
        docStore1 = new DocumentStore();
        docStore1.Url = ConfigurationManager.AppSettings["RavenShard1"];
        docStore2 = new DocumentStore();
        docStore2.Url = ConfigurationManager.AppSettings["RavenShard2"];
        shards.Add(docStore1);
        shards.Add(docStore2);
        tempShardedDocumentStore = new ShardedDocumentStore(new ShardStrategy(), shards);
        tempShardedDocumentStore.Initialize();
        IndexCreation.CreateIndexes(typeof(RavenDataAccess).Assembly, tempShardedDocumentStore);
        docStore1 = null;
        docStore2 = null;
        shardedDocumentStore = tempShardedDocumentStore;
        tempShardedDocumentStore = null;
        return shardedDocumentStore;
    }
    finally
    {
        if (tempShardedDocumentStore != null) { tempShardedDocumentStore.Dispose(); }
        if (docStore1 != null) { docStore1.Dispose(); }
        if (docStore2 != null) { docStore2.Dispose(); }
    }
}

CA 似乎对内联属性初始值设定项有问题,但如果将它们分解出来,这应该可以工作。关键是要确保无论在 try 块中的哪个位置抛出异常,所有可以释放的新对象都会被清理。

通过设置临时引用,您不再需要在返回之前nulldocStore1docStore2tempShardedDocumentStore),您可以签入 finally 块以查看它们是否实际上设置为 null ,如果没有,则某处发生了异常,您可以在执行离开此方法之前处理它们。

注意 docStore1docStore2 是添加到Shards集合中的临时引用。

首先,您传递到new ShardedDocumentStore() shards包含已处理的docStore1docStore2。这很可能会引起问题。

此外,在 catch 语句中,您处置可能已释放的docStores

最后,您返回的ShardedDocumentStore在返回时被处置(通过使用),这可能会使调用方无法使用它。

另外,我快速浏览了ShardedDocumentStore(在GitHub上),我会说它负责处理其docStores。也就是说,你不应该处理它。

将您的代码更改为以下内容:

private static IDocumentStore GetRavenDatabase()
{
    ShardedDocumentStore documentStore = null;
    var docStore1 = null;
    var docStore2 = null;
    try
    {
        Shards shards = new Shards();
        docStore1 = new DocumentStore { Url = ConfigurationManager.AppSettings["RavenShard1"] };
        shards.Add(docStore1);
        docStore2 = new DocumentStore { Url = ConfigurationManager.AppSettings["RavenShard2"] };
        shards.Add(docStore2);
        documentStore = new ShardedDocumentStore(new ShardStrategy(), shards);
        documentStore.Initialize();
        IndexCreation.CreateIndexes(typeof(RavenDataAccess).Assembly, documentStore);
        return documentStore;
    }
    catch
    {
        if (documentStore != null)
        {
            documentStore.Dispose();
        }
        else
        {
            if (docStore2 != null) docStore2.Dispose();
            if (docStore1 != null) docStore1.Dispose();
        }
        throw;
    }
}

。并让GetRavenDatabase()调用方处理返回IDocumentStore的处置。

下面是 using 语句中的对象初始值设定项导致 CA 警告的原因:

您的代码如下所示:

using (DocumentStore docStore2 = new DocumentStore { Url = ConfigurationManager.AppSettings["RavenShard2"] })  // Line 85
{
   ...
}

。由于对象初始值设定项的工作方式,Essentialy 变成了这样:

DocumentStore foo = new DocumentStore;
foo.Url = ConfigurationManager.AppSettings["RavenShard2"];
using(DocumentStore docStore2 = foo)
{
   ...
}

如您所见,文档存储的初始化现在发生在 using{} 块之外,因此,如果设置临时的行。URL 引发异常,您的文档存储不会被释放。

有许多解决方法,例如将参数传递给对象的构造函数,在 using 语句中设置属性而不是使用对象初始值设定项,或使用 try/finally 块。

考虑到 CA2000: 在丢失范围文档状态之前释放对象(其中的一部分):

仅受一个异常处理程序保护的嵌套构造函数。例如:

使用 (StreamReader

sr = new StreamReader(new FileStream("C:''myfile.txt", FileMode.Create))) { ... }

导致 CA2000 发生,因为构造失败 StreamReader 对象可能导致 FileStream 对象永远不会 被关闭。

考虑到我在代码中没有看到除了DocumentStore本身之外提供任何一次性对象分配,我认为这是编译器的错误。