Passing an IDisposable object by reference causes

2020-03-27 14:01发布

问题:

I am trying to create a general method for disposing an object that implements IDisposable, called DisposeObject()

To make sure I am disposing an object pointed by original reference, I am trying to pass an object by reference.

But I am getting a compilation error that says

The 'ref' argument type doesn't match parameter type

In the below (simplified) code, both _Baz and _Bar implement IDisposable.

So the questions are,

  1. Why am I getting this error?
  2. Is there a way to get around it?

[UPDATE] From provided answers so far, as long as I do not set an IDisposable argument to null, I can simply pass an object by value without using ref. I am now having another trouble whether to set disposable objects to null or not within DisposeObject method.

Here is the full source for completeness:

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

[RESULT]
I removed the code that sets argument to null (obj = null;) within DisposeObject So the final code became.

    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... */ }
    }

回答1:

There is no need for you to pass by reference, as you're passing a reference type. You should remove the ref keyword from your method definition. Do this and you shouldn't have any issues, though I'm not sure how this is more effective or clearer than simply calling Dispose() (other than the fact that you don't have to cast it for explicit implementations and this does a null check for you).

Edit

Dance, while I hope the discussion that's surrounded this topic has been helpful to you, your original intent doesn't seem to be something that's doable. In order to pass something as ref, you can't pass a variable that has a type other than what the ref parameter expects (in other words, you can't pass a variable declared as a class or other interface that implements IDisposable if the ref parameter is IDisposable). Because ref parameters allow assignments to propagate back to the caller, you would open up the possibility of allowing incompatible types being stored in your variable.

Your best bet here is to assign null yourself if that's what you want. If you want to encapsulate the null check and ignoring exceptions in to the function that's fine, but ref will not work for you in this scenario no matter how you slice it, unfortunately.



回答2:

Here's an option for your example (can't verify it against a compiler right now, but you'll get the idea):

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

To call it, use

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

As pointed in the other comments, the compiler error you get has its own purpose (preventing you to assign some other type of IDisposable inside your method, leading to an inconsistent state).



回答3:

Try this:

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

Edit: As Adam points out, your code doesn't require this to be ref. Objects are always passed as references.



回答4:

This approach smells funny but I'll ignore that for now.

To fix your problem, you need to cast the objects you are passing with "(IDisposable)"

I concede to the will of the compiler, and Jon Skeet. You need an actual object for this:

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

I'd also add a null check in your DisposeObject() in addition to the try/catch. The "obj==null" will be a quick and easy check when compared to expensive exception catching should this get hit multiple times for the same object. Hmm...was that there a minute ago? Nevermind.



回答5:

Thank you Dan C. I don't have enough rep yet to add comments, so I have to add this as an answer. However, full credit to Dan C for this solution.

This is working code:

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