Search code examples
c#goto

Why this 'goto' statement usage is not recommended?


We're having a big debate in the company whether or not goto statements should be used at all in projects. I personally find it to be adding clarity for the following scenario where I need to retry a web service call.

const string primaryWebServiceUrl = "https://example.com/Server.asmx";
const string secondaryWebServiceUrl = "https://example2.com/Server.asmx";

using (var ws = new Server())
{
    ws.Url = primaryWebServiceUrl;

start:
    try
    {
        wsAction?.Invoke(ws);
    }
    catch
    {
        if (ws.Url == secondaryWebServiceUrl)
            throw;

        ws.Url = secondaryWebServiceUrl;
        goto start;
    }
}

I believe that adding a loop in this case sacrifices code clarity and I find it to be an overkill to reference Polly just for having Retry logic.

Edit: Since everyone is saying that it's not recommended to use a goto statement here I'd like to learn more about why this is not recommended and what detrimental effects it can have. In my opinion this adds clarity but I can understand that the goto statement unwinding effect can be negative if not used correctly but in the example provided above, why the goto approach is not recommended?


Solution

  • It's valid, but not recommended; a more readable implementation is something like this

    using (var ws = new Server()) {
      ws.Url = primaryWebServiceUrl;
    
      // Keep doing ...
      while (true) {
        try {
          wsAction?.Invoke(ws);
    
          // ...until Invoke succeeds
          break; 
        }
        catch { //TODO: put expected Exception type here
          // Something is very wrong; rethrow the exception and leave the routine 
          if (ws.Url == secondaryWebServiceUrl)
            throw;
    
          ws.Url = secondaryWebServiceUrl;
        } 
      }
    }
    

    Or even better (especially if we want to have many urls) - thank to Panagiotis Kanavos for the idea:

     string[] urls = new string[] {
       "https://example.com/Server.asmx",
       "https://example2.com/Server.asmx",
       "https://example3.com/Server.asmx", 
        ...
       "https://example123.com/Server.asmx", 
     };
    
     using (var ws = new Server()) {
       // Try each url from urls...
       for (int i = 0; i < urls.Length; ++i) {
         try {
           ws.Url = urls[i];
           wsAction?.Invoke(ws);
    
           // ... until success 
           break;  
         }
         catch {
           // The last url failed; rethrow the error
           if (i >= urls.Length - 1)
             throw; 
         }  
       } 
     }