【问题标题】:Passing an IDisposable object by reference causes an error?通过引用传递 IDisposable 对象会导致错误?
【发布时间】:2010-10-22 02:27:41
【问题描述】:

我正在尝试创建一个通用方法来处理实现IDisposable 的对象,称为DisposeObject()

为了确保我正在处理原始引用指向的对象,我试图通过引用传递一个对象。

但我收到一个编译错误提示

“ref”参数类型与参数类型不匹配

在下面的(简化的)代码中,_Baz_Bar 都实现了 IDisposable

所以问题是,

  1. 为什么会出现此错误?
  2. 有没有办法绕过它?

[更新] 从到目前为止提供的答案来看,只要我不将 IDisposable 参数设置为 null,我就可以简单地按值传递对象,而无需使用 ref。 我现在遇到了另一个麻烦,是否在DisposeObject 方法中将一次性对象设置为null

这是完整的完整来源:

public class Foo : IDisposable
{
    private Bar _Bar;
    private Baz _Baz;
    private bool _IsDisposed;

    ~Foo() { Dispose(false); }

    public void Dispose(bool disposing)
    {
        if (!_IsDisposed)
        {
            if (disposing)
            {
                DisposeObject(ref _Baz);
                DisposeObject(ref _Bar);
            }
        }

        _IsDisposed = true;
    }

    private void DisposeObject(ref IDisposable obj)
    {
        try
        {
            if (obj == null) 
                return;
            obj.Dispose();
            obj = null;
        } catch (ObjectDisposedException) { /* Already Disposed... */ }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

public class Bar : IDisposable
{
    public void Dispose() {}
}

public class Baz : IDisposable
{
    public void Dispose() {}
}

[结果]
我删除了在DisposeObject 中将参数设置为 null (obj = null;) 的代码 所以最终的代码就变成了。

    public void Dispose(bool disposing)
    {
        if (!_IsDisposed)
        {
            if (disposing)
            {
                DisposeObject(_Baz);
                DisposeObject(_Bar);
            }
        }

        _IsDisposed = true;
    }

    private void DisposeObject(IDisposable obj)
    {
        try
        {
            if (obj == null) 
                return;
            obj.Dispose();
        } catch (ObjectDisposedException) { /* Already Disposed... */ }
    }

【问题讨论】:

  • 我看不出这里使用参考参数的具体原因。你能详细说明一下吗?
  • @Noldorin:我最初的实现是处理一个参数并将其设置为 null。 “obj == 空;”这就是“ref”的原因。
  • obj == null 是一个比较,而不是一个赋值。 obj = null 是一个赋值。
  • @Adam:你说得对,错字......应该有一个“=”。
  • 这是通过引用传递引用类型参数的完全正当理由。

标签: c# dispose idisposable pass-by-reference


【解决方案1】:

这是您示例的一个选项(目前无法针对编译器进行验证,但您会明白的):

private void DisposeObject<T>(ref T obj) where T : IDisposable
{
    // same implementation
}

要调用它,请使用

DisposeObject<Baz>(ref _Baz);
DisposeObject<Bar>(ref _Bar);

正如在其他 cmets 中所指出的,您得到的编译器错误有其自身的目的(阻止您在方法中分配其他类型的 IDisposable,从而导致不一致的状态)。

【讨论】:

  • 这很好。它可以编译,但由于我决定不将参数设置为 null,因此我已经选择了一个答案。我想我将能够将上述代码用于将来的用途。谢谢丹 C。
  • 非常聪明,大多数时候你可以依赖类型引用(例如DisposeObject(ref _Baz)DisposeObject(ref _Bar)
  • 请注意,如果尝试执行 obj = null;(这是 OP 使用 ref 的原始原因),在尝试分配 null 时会出现编译器错误。要解决此问题,请将约束更改为 where T : class, IDisposable
【解决方案2】:

您无需通过引用传递,因为您传递的是引用类型。您应该从方法定义中删除 ref 关键字。这样做你应该不会有任何问题,尽管我不确定这比简单地调用Dispose() 更有效或更清晰(除了你不必为显式实现而强制转换它的事实之外) null 为您检查)。

编辑

舞蹈,虽然我希望围绕这个主题的讨论对你有所帮助,但你的初衷似乎并不可行。为了传递ref 的内容,您不能传递类型不同于ref 参数所期望的类型的变量(换句话说,您不能传递声明为class 或其他类型的变量) interface,如果 ref 参数是 IDisposable,则实现 IDisposable。因为ref 参数允许赋值传播回调用者,所以您可能会允许将不兼容的类型存储在您的变量中。

如果你想要的话,最好的办法是自己分配null。如果您想将 null 检查并忽略异常封装到函数中,那很好,但不幸的是,ref 在这种情况下无论您如何切片都不适合您。

【讨论】:

  • 我想将“NULL”和“ObjectDisposedException”委托给一个方法,而不是为每个对象编写“try..catch”。
  • 我同意你的观点,亚当。我猜他这样做是为了使对象引用无效,这样如果再次使用相同的引用调用 DisposeObject,它将提前退出。
  • 根据 Microsoft 指南,调用 Dispose() 不应导致 ObjectDisposedException(或者,更一般地说,重复调用 Dispose() 不应生成错误)。即便如此,也不需要 ref 关键字。它只会增加复杂性和更高的可访问性要求。你应该删除它。
  • @Michael:如果引用的对象被分配给 null,我可能会同意你的观点;)
  • 你可能是对的,如果我只是在一个方法上调用“Dispose()”而不在“DisposeObject”中将_Bar或_Baz设置为null,我认为我不需要“参考”。
【解决方案3】:

试试这个:

IDisposable d = (IDisposable)_Baz;
DisposeObject(ref d);

编辑: 正如亚当指出的那样,您的代码不需要将其作为参考。对象总是作为引用传递。

【讨论】:

  • 这实际上不起作用。您会收到“ref 或 out 参数必须是可赋值变量”的错误。
  • @dance2die,不,变量是按值传递的。但是,此变量包含一个 REFERENCE 类型,因此引用本身(不是对象的内容)是按值传递的内容。这与您在这方面的其他线程相似。 ref 关键字给您的唯一功能差异是能够为该变量分配一个值并将其反映在调用代码中。您没有这样做,因此不需要 ref 关键字。
  • @Jon B:对象引用按值传递。默认情况下,对象不通过 [ref] 传递:您不能将新对象引用分配给实际参数并在调用函数中调用后查看该值。
  • @danc2die:布赖恩是绝对正确的。 reference 默认是按值传递的。对象本身根本没有通过。我不相信我的文章声称他们是......我当然希望它没有这么说。
  • @Jon B:除非您想更改调用者变量的值,否则通过引用传递参数是非常有意义的。声称“对象是 ref”混淆了 IMO 的两个问题。仅仅理解引用类型变量的值是一个引用,而不是一个对象,就更简单、更准确了。在那一点上,一切都很好而且一致。当参数是由 ref 以及时,“对象是由 ref”的解释变得非常混乱。
【解决方案4】:

这种方法听起来很有趣,但我暂时忽略它。

要解决您的问题,您需要使用“(IDisposable)”转换您传递的对象

我承认编译器和 Jon Skeet 的意愿。为此,您需要一个实际对象:

IDisposable _BazD = (IDisposable)_Baz;
DisposeObject(ref _BazD);

除了 try/catch 之外,我还会在 DisposeObject() 中添加一个空检查。与昂贵的异常捕获相比,“obj==null”将是一个快速而简单的检查,如果这会为同一个对象多次命中。嗯......一分钟前有吗?没关系。

【讨论】:

  • 为什么我必须明确将对象转换为“IDisposable”?编译器可以推断出我实际上是在传递一个实现 IDisposable 的对象吗?
  • 当然可以,但不能。
  • @Michael: ref 关键字是限制它的原因。如果没有 ref 关键字,它会这样做。
  • 那行不通。你会得到错误 CS1510: A ref or out argument must be an assignable variable。问题是该方法可以为变量分配 any IDisposable 值,但调用者的变量具体是 Bar 或 Baz。
  • @Michael Haren:正如 Jon Skeet 所指出的,您的回答会产生以下错误。 “ref”参数不属于变量
【解决方案5】:

谢谢 Dan C。我还没有足够的代表来添加 cmets,所以我必须添加这个作为答案。但是,此解决方案完全归功于 Dan C。

这是工作代码:

public override void Dispose()
{
    base.Dispose();

    DisposeOf<UserTableAdapter>(ref userAdapter);
    DisposeOf<ProductsTableAdapter>(ref productsAdapter);

    if (connection != null)
    {
        if (connection.State == ConnectionState.Open)
        {
            connection.Close();
        }
        DisposeOf<SqlConnection>(ref connection);
    }
}

private void DisposeOf<T>(ref T objectToDispose) where T : IDisposable
{
    if (objectToDispose != null)
    {
        objectToDispose.Dispose();
        objectToDispose = default(T);
    }
}

【讨论】:

    猜你喜欢
    • 2020-03-08
    • 2020-03-04
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    • 2017-09-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多