Using goto with DataReader to get sproc return value
Is my use of goto then justified?


First of all, let me push back on the question. A goto does not require a justification any more than any other language feature requires a justification.

That said, a goto is widely considered to be a sign that the code is inelegant, confusing or hard to maintain.  It is definitely a sign that you should be thinking hard about how the control flow of the code works, and whether there is a refactoring that would make it more clear.

You are doing that, and that's excellent.  The problem is that you've only identified two alternatives to a goto: refactoring into nested "if" statements, which as you note may be harder to read than the goto version, and using "throw" to simulate a goto. That's a bad practice; throw should be used to indicate an unexpected error condition, not as a very expensive way to simulate a goto.

You missed the preferred way to remove a goto: turn it into a return.

void DoTheFirstThing()
{
  using( DbDataReader rdr = cmd.ExecuteReader() ) 
  {
    if( !rdr.Read() ) return;
    ret.Foo  = ReadFoo( rdr );
    if( !rdr.NextResult() ) return;
    List&lt;Bar&gt; bars = new List&lt;Bar&gt;();
    while( rdr.Read() ) bars.Add( ReadBar( rdr ) );
    ret.Bars = bars.ToArray();
    if( !rdr.NextResult() ) return;
    ret.Qux = ReadQux( rdr );
    if( !rdr.NextResult() ) return;
    List&lt;Blargh&gt; blarghs = new List&lt;Blargh&gt;();
    while( rdr.Read() ) blarghs.Add( ReadBlargh( rdr, ) );
    ret.Blarghs = blarghs.ToArray();
    ...
   }
}

void DoTheSecondThing()
{
  Int32 returnValue = (Int32)returnParameter.Value;
  switch( returnValue ) {
    case 0: // do stuff
    case 1: // do other stuff
    case ...
  }
  ...
}

void DoTheThings()
{
  DoTheFirstThing();
  DoTheSecondThing();
}


There, no gotos required, and your program is now refactored into smaller methods that each do one task well, rather than one god method that tries to create the whole universe in one go.

While we are criticizing your code, I note that this also looks a bit dodgy:

    if( !rdr.NextResult() ) return;
    List&lt;Blargh&gt; blarghs = new List&lt;Blargh&gt;();
    while( rdr.Read() ) blarghs.Add( ReadBlargh( rdr, ) );
    ret.Blarghs = blarghs.ToArray();


Suppose NextResult is false, and so we take the return. What now is the value of the blarghs field of object ret?  Is it null?  Is it intended to be null?  Is the user of this field supposed to understand that it can and will be null if the reader ran out of results, and that if it is not null, then it is a non-empty array?  This is a lousy contract. It is much better to say "blarghs will be a non-null array that might be empty", and not "blarghs is either null or a non-empty array".  I would be inclined to re-think your deserialization strategy here so that more of the deserialized object is in a good state:

IEnumerable&lt;Bar&gt; ReadBars(Reader rdr)
{
    if( !rdr.NextResult() ) yield break;
    while( rdr.Read() ) 
      yield return ReadBar( rdr );
}

IEnumerable&lt;Blargh&gt; ReadBlarghs(Reader rdr)
{
    if( !rdr.NextResult() ) yield break;
    while( rdr.Read() ) 
      yield return ReadBlargh( rdr );
}


void DoTheFirstThing()
{
  using( DbDataReader rdr = cmd.ExecuteReader() ) 
  {
    // Move the check for the goodness of the reader into ReadFoo,
    // readQux, etc.

    ret.Foo  = ReadFoo( rdr );
    ret.Bars = ReadBars( rdr ).ToArray();        
    ret.Qux = ReadQux( rdr );
    ret.Blarghs = ReadBlarghs ( rdr ).ToArray();
   }
}


OMG look at how much better that code reads now.

BUT WAIT THERE'S MORE. Let's make this an extension method and parameterize it with a function:

static class Extensions
{

  public static IEnumerable&lt;T&gt; ReadThings&lt;T&gt;(this Reader rdr, Func&lt;Reader, T&gt; f)
  {
    if( !rdr.NextResult() ) yield break;
    while( rdr.Read() ) 
      yield return f( rdr );
  }
  public static Foo ReadFoo(this Reader rdr)
  {
     if (!rdr.Read) return DefaultFoo;
     ...
  }
  // Similarly for ReadQux ...
}
...
void DoTheFirstThing()
{
  using( DbDataReader rdr = cmd.ExecuteReader() ) 
  {
    ret.Foo  = rdr.ReadFoo();
    ret.Bars = rdr.ReadThings(ReadBar).ToArray();        
    ret.Qux = rdr.ReadQux();
    ret.Blarghs = rdr.ReadThings(ReadBlargh).ToArray();
  }
}


Isn't that about a million times nicer to read than what you wrote?  Look at how in this code the meaning shines through. In your code it looks like the most important thing is the reader and managing whether it is done or not. In this code it looks like the most important thing is that we are reading a Foo, some Bars, a Qux and some Blarghs.  Which way do you think will be easier to understand and maintain?

We've used higher level functions and extension methods for the win. You could be making this code tiny and super easy to read, compared to its present enormous sprawling repetitive goto-laden spaghetti.

Think about where this refactoring gets you. Now if you want to someday in the future say hey, you know, I actually only want to deserialize the frobby bars:

    ret.Bars = rdr.ReadThings(ReadBar).Where(bar =&gt; IsFrobby(bar)).ToArray();        


DONE. 

Or, you know what, we serialized out a bunch of bars, but what we need in memory is actually a bunch of fubars:

    ret.Fubars = rdr.ReadThings(ReadBar).Select(bar =&gt; MakeFubar(bar)).ToArray();        


Nice. You want to make a change like that, you make it in one place with clear, self-documenting code. 

And now you see the true power of thinking "is this goto a bad idea?"  By thinking it through carefully you can take the intuition that the goto is inelegant and manage to refactor your program into something that is small, powerful, and cheap to change when requirements change. So by all means, keep on thinking about whether those gotos are inelegant -- just think the problem all the way through, rather than stopping on some not-actually-better solution like using a throw or a break or a continue to simulate a goto.