【问题标题】:CA2000 passing object reference to base constructor in C#CA2000 将对象引用传递给 C# 中的基本构造函数
【发布时间】:2010-04-22 00:09:30
【问题描述】:

当我通过 Visual Studio 的代码分析实用程序运行一些代码时收到一条警告,我不确定如何解决。也许这里有人遇到过类似的问题,解决了它,并愿意分享他们的见解。

我正在编写 DataGridView 控件中使用的自定义绘制单元格。代码类似于:

public class DataGridViewMyCustomColumn : DataGridViewColumn
{
    public DataGridViewMyCustomColumn() : base(new DataGridViewMyCustomCell())
    {
    }

它会生成以下警告:

CA2000 : Microsoft.Reliability : 在方法“DataGridViewMyCustomColumn.DataGridViewMyCustomColumn()”中调用 System.IDisposable.Dispose 在对象“new DataGridViewMyCustomCell()”上的所有引用都超出范围。

我知道它在警告我 DataGridViewMyCustomCell(或它继承自的类)实现了 IDisposable 接口,并且应该调用 Dispose() 方法来清理 DataGridViewMyCustomCell 不再声明的任何资源。

我在 Internet 上看到的示例建议使用 using 块来确定对象的生命周期并让系统自动处理它,但是当移入构造函数的主体时无法识别 base,所以我不能在它周围写一个 using 块......我不确定我是否想要这样做,因为这不会指示运行时释放仍然可以稍后在基类中使用的对象吗?

那么我的问题是,代码还可以吗?或者,如何重构它来解决警告?除非确实合适,否则我不想抑制警告。

【问题讨论】:

    标签: c# constructor code-analysis reliability ca2000


    【解决方案1】:

    如果您使用的是 Visual Studio 2010,那么 CA2000 将完全损坏。它也可能在其他版本的 FxCop(又名代码分析)中被破坏,但 VS2010 是我唯一可以保证的。我们的代码库正在为这样的代码发出 CA2000 警告...

    internal static class ConnectionManager 
    {
        public static SqlConnection CreateConnection()
        {
             return new SqlConnection("our connection string");
        }
    }
    

    ...表明连接在超出方法范围之前没有被释放。嗯,是的,没错,但它并没有超出应用程序的范围,因为它返回给调用者——这就是方法的重点!同样,您的构造函数参数并没有超出范围,而是被传递给基类,因此这是规则的误报,而不是实际问题。

    这曾经是一个有用的规则,但现在你真正能做的就是关闭它,直到他们修复它。这是不幸的,因为(极少数)实际的积极因素是应该修复的。

    【讨论】:

    • +1 表示“在 VS10 中完全崩溃”。当工具让我看起来如此愚蠢时,很难说服人们加入我的代码质量改进之旅......
    【解决方案2】:

    没有安全和优雅的方式让链式构造函数将新的IDisposable 对象传递给基本构造函数,因为您注意到不可能将链式构造函数调用包装在任何类型的try finally 块中。有一种安全的方法,但它并不优雅:定义一个实用方法,例如:

    internal static TV storeAndReturn<TR,TV>(ref TR dest, TV value) where TV:TR
    { 
      dest = value; return value;
    }
    

    让构造函数看起来像:

    protected DataGridViewMyCustomColumn(ref IDisposable cleaner) : 
       base(storeAndReturn(ref cleaner, new DataGridViewMyCustomCell()))
    {
    }
    

    需要一个新对象的代码必须调用一个公共静态工厂方法,该方法将在 try/finally 块中调用适当的构造函数,其主线将在完成之前将 cleaner 清空,并且其finally 块将在cleaner 上调用Dispose,如果它不为空。如果每个子类都定义了一个类似的工厂方法,那么这种方法将确保新的IDisposable 对象将被释放,即使在它创建和封装对象暴露给客户端代码之间发生异常也是如此。该模式很难看,但我不确定其他更好的模式能否保证正确性。

    【讨论】:

    • 令人沮丧的是,父/自构造函数调用在语法上没有充分的理由受到限制 - 我希望 C# 允许对 base()this() 的调用在构造函数的主体内,即将消除所有类型的问题(并且可以通过在调用 base()/this() 之前禁止取消引用 this 来保证安全性)。
    • 我认为设计意图是防止代码操作尚未构建的基础对象。恕我直言,这应该通过语法要求来实现,即没有引用this 的代码路径或在不调用基本构造函数的情况下成功返回,也没有任何代码路径多次调用基本构造函数。但是,出于该目的,语法是不必要的限制。我还认为应该有单独的语法来适应类字段的早期和晚期初始化,因为这两种结构都有合法用途。
    • 尽管如此,C# 确实允许从超类构造函数中调用virtual,这完全违背了这一点:/
    • @Dai:我认为它没有为在控制到达基类构造函数之前执行此类调用提供任何机制,是吗?如果基类契约指定子类必须容忍在其构造函数运行之前调用某些虚拟方法或属性(例如,指示派生类将需要哪些基类特性的属性),则此类属性在派生之前被调用的事实类构造函数几乎不应该“令人惊讶”。然而,基类的作者无法阅读尚未编写的派生类的文档。
    • 正确,ctors 中的虚拟调用是在主体中而不是之前执行的,但问题是实际调用的虚拟方法覆盖可以访问派生类的状态之前它已构建 - 官方不鼓励这样做 everywhere including in C#。您提到这取决于“合同”-但是 C# 的表达能力不足以让编译器检查它并且没有人阅读文档:)
    猜你喜欢
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 2014-06-04
    • 2020-07-22
    相关资源
    最近更新 更多