Can I combine a using() {} block with a method's out parameter?
Asked Answered
H

5

12

Given a method

public static bool Connection.TryCreate(out Connection connection) {}

And a piece of calling code:

Connection connection;
if (!Connection.TryCreate(out connection))
    // handle failure gracefully.

/*
 * work with connection
 *
 * …
 *
 */

connection.Dispose();

I'm using the same pattern as bool.TryParse and friends, i.e. TryCreate returns whether the operation was successful.

I realize the using() variable needs to be read-only within its block, but is there a way to turn the above into a using() {} block (TryCreate only sets it once), like so:

using (Connection connection)
{
    if (!Connection.TryCreate(out connection))
        // this would leave the using() block prematurely

    /*
     * work with sconnection
     *
     * …
     *
     */
}

(This doesn't compile:

error CS1657: Cannot pass 'connection' as a ref or out argument because it is a 'using variable'

)

Hendecagon answered 19/11, 2011 at 18:25 Comment(2)
Did you do a simple test with NULL/non-NULL values for connection already? Did it compile? Did it run?Erickaericksen
Yeah, sorry, I should have mentioned that my fictitious example wouldn't compile. I've amended the post accordingly.Sidoney
W
8

No, that is not possible.

The using (x) {...} construct makes a copy of x when it enters the block, so you can do this:

var x = new FileStream(...);
using (x)
{
    x = null;
}

The stream will still be disposed when the using block ends.

The corollary is that this won't work either:

Stream x = null;
using (x)
{
    x = new FileStream(...);
}

here the stream you construct inside the using block will not be disposed.

What you can do, however, is this:

Connection connection;
if (Connection.TryCreate(out connection))
    using (connection)
    {
    }

In C# 7.0 and onwards you can combine this with "out variables" to form:

if (Connection.TryCreate(out var connection))
    using (connection)
    {
    }
Waterford answered 19/11, 2011 at 18:31 Comment(0)
P
4

Looks like a bad use of the Try* pattern (some would argue this is an anti-pattern).

Instead of a TryCreate, just have a Create method that throws an exception if not successful and that returns the created connection.

Then you could do the usual:

using(Connection connection = Connection.Create())
{
}

Alternatively, if you want to avoid an exception being thrown and the required try{}catch{}, have the Create method return null when a connection could not be created and test for that.

Perjury answered 19/11, 2011 at 18:30 Comment(10)
Arguably, a TryCreate that sometimes returns null would also serveSkeptical
@MarcGravell - Possibly. But the semantics of Try* (at least in the BCL) would be to return a success boolean. Or do you mean the out parameter?Perjury
I don't see how this is any different than using bool.TryParse() over bool.Create(), or Uri.TryCreate over new Uri(). It avoids dealing with exceptions, which may sometimes be desirable.Sidoney
I meant a regular return, non-null on success, else nullSkeptical
↑↑↑ It does return success (or failure, as the case may be).Sidoney
@MarcGravell - Absolutely agree - returning a null is a good alternative.Perjury
@Perjury - I assumed that is what it did when I wrote my wrong answer -- This is because the Try* used in Parse does not make sense for a Create operation IMHO.Ctn
@Ctn - I agree, which is why my answer steers clear from a Try method.Perjury
Having a factory "try" method is entirely reasonable. The all-to-common pattern of having a "try" method return the created object as an out-reference parameter, however, seems much more dubious. An interface IVehicleFactory<T> which defines a method bool TryBuildCarToSpecs(CarSpects theSpecs, out T result) can only be used by code that wants to build something of precise type T. By contrast, if the function were e.g. T TryBuildCarToSpecs(CarSpecs theSpecs, ref BuildProblemReport faults) the interface could be declared covariant, and code which wanted an IVehicleFactory<Car> could...Abelabelard
...perfectly happily accept an IVehicleVactory<ToyotaCamryHatchback> without any problem. If the factory is returning a class type, and if no problem indication is needed beyond pass/fail, one could leave off the 'faults' parameter and simply return 'null' in the problem case. If the factory produces something that's possibly a value type, but pass/fail reporting is adequate, the 'ref' parameter could be of type 'bool.'Abelabelard
P
3

You can do it like this:

Connection connection;
if (Connection.TryCreate(out connection))
{
    using (connection)
    {
        …
    }
}

But it might be better if you just returned null on failure:

using (Connection connection = Connection.Create())
{
    if (connection != null)
    {
        …
    }
}

The finally block that is created by the using checks whether connection is null and doesn't do anything if it is.

Also, if you're not declaring the variable in the using, then it doesn't have to be read-only.

Percentile answered 19/11, 2011 at 18:30 Comment(9)
From MSDN - using Statement: You can instantiate the resource object and then pass the variable to the using statement, but this is not a best practice.Perjury
Yeah, but I think it's the best solution, if you don't want to modify TryCreate() for some reason.Percentile
What happens when TryCreate fails?Ctn
@Hogan, I forgot about that at first. The necessary if is there now.Percentile
That compiles, but does it really guarantee that the resources are cleaned up in all cases?Sidoney
I'll accept the answer — the first part, that is. The second part seems bad in that, if connection does end up being null, the code would silently do nothing, and I wouldn't even be able to check for null since the block would never get entered.Sidoney
@SörenKuklau, of course not. There are several corner cases when using doesn't call Dispose(), like a power failure or uncatchable exception. But I think the only thing that can happen here (except for the corner cases) is something like ThreadAbortException.Percentile
@SörenKuklau, in the second case, if you want to react to failure, just add else to the if.Percentile
Ah, I misunderstood. So it does enter the using block, but with a null object. Never mind.Sidoney
S
1

No. If you are concerned about exceptions in the gap between a method call and the using, you could use try/finally:

Connection conn = null;
try {
    if(!conn.TryCreate(out conn)) return;
    ...
} finally {
    if(conn != null) conn.Dispose();
}
Skeptical answered 19/11, 2011 at 18:33 Comment(1)
Sure, but that's pretty much a re-implementation of using's syntactic sugar. :) I'm trying to avoid that.Sidoney
C
0

A step sideways ?

public class ConnectTo : IDisposable
{

  public Connection CurrentConnection {get; private set;}

  public ConnectTo()
  {
    CurrentConnection = null;
    // Connect up to whatever.
  }


  #region IDisposable

  // Blah blah

  #endregion
}

then

using( ConnectedTo conn = new ConnectTo())
{
  if (conn.CurrentConnection != null)
  {
    //Do Stuff
  }
}
Countryside answered 19/11, 2011 at 19:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.