C# ValueTuple with disposable members
Asked Answered
C

3

21

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?

Coccidiosis answered 26/9, 2017 at 12:21 Comment(2)
When would Dispose be called on this IDisposable ValueTuple? Not even the Garbage Collector calls Dispose. You must write that code and at least put it in the finalizer if you want the Garbage collector to trigger it.Deadlight
If you ought to implement IDisposable then just don't use a tuple.Ran
F
26

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.

Fluke answered 26/9, 2017 at 12:23 Comment(2)
I was just about to accept this answer... But here is a question: What if foo() throws an exception after it has created the disposable object? Looks like to be on the safe side I still need to wrap everything in a try-finally block??Coccidiosis
Since foo cannot return anything when it throws an exception, it’s not the responsibility of the calling code to clean up the function’s internal state. So yeah, if the function may throw an exception after the object is created, it should properly handle that case and dispose the object before the exception bubbles up.Fluke
S
0

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.

Sanderlin answered 18/10, 2022 at 17:57 Comment(0)
M
0
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
    }
}
Mcripley answered 27/10, 2022 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.