Robert Hegner
Robert Hegner

Reputation: 9354

C# ValueTuple with disposable members

Let's say I have a method foo which returns a ValueTuple where one of it's members is disposable, for example (IDisposable, int).

What is the best way to make sure the returned disposable object is correctly disposed on the calling side?

I tried the following:

using (var (disposable, number) = foo())
{
    // do some stuff using disposable and number
}

But this won't compile:

'(IDisposable disposable, int number)': type used in a using statement must be implicitly convertible to 'System.IDisposable'

Do I really need to wrap my code in a try-finally block and dispose my object explicitly in the finally block, or is there a nicer way to do it?

Actually I'd like to see the new ValueTuples implement the IDisposable interface and call Dispose() on all their disposable members. Could this be a worthwhile feature request for Microsoft?

Upvotes: 25

Views: 3626

Answers (4)

Jatin Sanghvi
Jatin Sanghvi

Reputation: 2243

If someone wants to use using declaration for this particular case to avoid braces and code indentation, there are a few options:

var (disposable, number) = Foo();
using var disposable_ = disposable;

// code referencing 'disposable'.

If number can be discarded, then depending on the Foo()'s definition, it will either one of the following:

(IDisposable, int) Foo() { ... }

using var disposable = Foo().Item1;
(IDisposable VarName, int Number) Foo() { ... }

using var disposable = Foo().VarName;

Upvotes: 0

AnGG
AnGG

Reputation: 821

If foo its your code as well it can be modified to return an object that contains all of the value tuple members and implements IDisposable interface.

Implement its Dispose method to delegate to all its disposable members

For example:

public class Result : IDisposable
{
    public int Number { get; set; }

    public HttpClient HttpClient { get; set; }
    public HttpContent HttpContent{ get; set; }

    public void Dispose()
    {
        try
        {
            HttpClient?.Dispose();
        }
        catch
        {
            // in case there is more than one disposable member
        }

        try
        {
            HttpContent?.Dispose();
        }
        catch
        {
            // in case there is more than one disposable member
        }
    }
}

And use it like this:

public static void Main(string[] args)
{
    using Result result = Foo();

    Console.WriteLine(result.HttpClient?.ToString());
    Console.WriteLine(result.HttpContent?.ToString());

    // it will now dispose result and all the internal IDisposable members
}

public static Result Foo()
{
    return new Result();
}

There can be some difference in the behavior in case that one of the Dispose calls throws an exception, in this case you wont see it, so if there is just one Disposable member you can delegate to its Dispose without try-catch block.

Upvotes: 1

Savvas Konstantinou
Savvas Konstantinou

Reputation: 19

var (disposable, number) = foo();
using (disposable)
{
    // do some stuff using disposable and number
}

The accepted answer works but 'disposable' variable leaves the control of the using block. If the object is used after the using block (since it's still in scope) we risk an exception to be thrown.

It would be recommended to add another pair of brackets to end the scope.

{
    var (disposable, number) = foo();
    using (disposable)
    {
        // do some stuff using disposable and number
    }
}

Upvotes: 0

poke
poke

Reputation: 387507

Just move the method call outside of the using statement and then just use the disposable object:

var (disposable, number) = foo();
using (disposable)
{
    // do some stuff using disposable and number
}

The reason your version didn’t work is simply because whatever the expression inside the parentheses of the using results in needs to be disposable but the value tuple itself is not disposable. So you just need to split this up. Fortunately, the using statement is not required to construct the object within that expression, you can just pass any existing object to it.


Actually I'd like to see the new ValueTuples implement the IDisposable interface and call Dispose() on all their disposable members. Could this be a worthwhile feature request for Microsoft?

By that logic, all collections would be have to do that. For example disposing a list should dispose all its members, disposing a dictionary should dispose all its values (and keys?!?!). And when you have a type just using a simple list, that object would also need to be disposable.

So basically, you would end up spreading that and end up with a lot of objects that are suddenly disposable although they don’t actually have any actual resources that require it.

Making objects disposable shouldn’t be done lightly. Usually, objects creating a disposable object become responsible for properly disposing the object later, they own the lifetime of the object. But for collections, this is very often not the case. Collections are usually just that: Collections to hold on to objects, but that does not say anything about whether or not they are owned by the collection—most of the times they are owned by the object that also created the collection, so that object should then at some point dispose the objects by simply looping through the collection.

Upvotes: 26

Related Questions