Padrão para evitar blocos try catch aninhados?

114

Considere uma situação em que tenho três (ou mais) maneiras de realizar um cálculo, cada uma das quais pode falhar com uma exceção. A fim de tentar cada cálculo até encontrarmos um que seja bem-sucedido, tenho feito o seguinte:

double val;

try { val = calc1(); }
catch (Calc1Exception e1)
{ 
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        try { val = calc3(); }
        catch (Calc3Exception e3)
        {
            throw new NoCalcsWorkedException();
        }
    }
}

Existe algum padrão aceito que alcance isso de uma maneira mais agradável? Claro que eu poderia envolver cada cálculo em um método auxiliar que retorna nulo em caso de falha e, em seguida, apenas usar o ??operador, mas há uma maneira de fazer isso de forma mais geral (ou seja, sem ter que escrever um método auxiliar para cada método que desejo usar )? Já pensei em escrever um método estático usando genéricos que envolva qualquer método em um try / catch e retorne null em caso de falha, mas não tenho certeza de como faria isso. Alguma ideia?

Joelson
fonte
Você pode incluir alguns detalhes sobre o cálculo?
James Johnson,
2
Eles são basicamente apenas métodos diferentes de resolver / aproximar um PDE. Eles são de uma biblioteca de terceiros, portanto, não posso alterá-los para retornar códigos de erro ou nulos. O melhor que pude fazer é envolver cada um individualmente em um método.
jjoelson,
Os métodos calc fazem parte do seu projeto (em vez de uma biblioteca de terceiros)? Nesse caso, você poderia retirar a lógica que lança exceções e usá-la para decidir qual método calc deve ser chamado.
Chris,
1
Há outro caso de uso para isso que encontrei em Java - preciso analisar a Stringpara a Dateusando SimpleDateFormat.parsee preciso tentar vários formatos diferentes em ordem, passando para o próximo quando uma exceção lançar.
Variável miserável de

Respostas:

125

Na medida do possível, não use exceções para o fluxo de controle ou circunstâncias normais.

Mas, para responder à sua pergunta diretamente (assumindo que todos os tipos de exceção sejam os mesmos):

Func<double>[] calcs = { calc1, calc2, calc3 };

foreach(var calc in calcs)
{
   try { return calc(); }
   catch (CalcException){  }
} 

throw new NoCalcsWorkedException();
Ani
fonte
15
Isso pressupõe que Calc1Exception, Calc2Exceptione Calc3Exceptioncompartilham uma classe base comum.
Wyzard,
3
No topo, ele assume uma assinatura comum - o que não está muito longe. Boa resposta.
TomTom,
1
Além disso, adicionei continueno bloco catch e breakdepois do bloco catch para que o loop termine quando um cálculo funcionar (graças a Lirik por este bit)
jjoelson,
6
+1 apenas porque diz "Não use exceções para fluxo de controle", embora eu tivesse usado "NUNCA NUNCA" em vez de "Na medida do possível".
Bill K,
1
@jjoelson: A breakdeclaração seguinte calc();dentro do try(e não continuedeclaração em tudo) pode ser um pouco mais idiomática.
Adam Robinson,
38

Apenas para oferecer uma alternativa "fora da caixa", que tal uma função recursiva ...

//Calling Code
double result = DoCalc();

double DoCalc(int c = 1)
{
   try{
      switch(c){
         case 1: return Calc1();
         case 2: return Calc2();
         case 3: return Calc3();
         default: return CalcDefault();  //default should not be one of the Calcs - infinite loop
      }
   }
   catch{
      return DoCalc(++c);
   }
}

NOTA: de forma alguma estou dizendo que esta é a melhor maneira de fazer o trabalho, apenas uma maneira diferente

musefan
fonte
6
Tive de implementar "On Error Resume Next" em uma linguagem uma vez, e o código que gerei se parecia muito com isso.
Jacob Krall,
4
Nunca use uma instrução switch para criar um loop for.
Jeff Ferland,
3
Não é possível manter a instrução switch para loop
Mohamed Abed,
1
Sei que minha resposta não é o código mais eficiente, mas, novamente, usar blocos try / catch para esse tipo de coisa não é a melhor maneira de fazer isso. Infelizmente, o OP está usando uma biblioteca de terceiros e deve fazer o melhor que puder para garantir o sucesso. Idealmente, a entrada poderia ser validada primeiro e a função de cálculo correta escolhida para garantir que não falhe - é claro, você poderia então colocar tudo isso em um try / catch apenas para ficar seguro;)
musefan
1
return DoCalc(c++)é equivalente a return DoCalc(c)- o valor pós-incrementado não será passado mais profundamente. Para fazer funcionar (e introduzir mais obscuridade), poderia ser mais parecido return DoCalc((c++,c)).
Artur Czajka,
37

Você poderia nivelar o aninhamento colocando-o em um método como este:

private double calcStuff()
{
  try { return calc1(); }
  catch (Calc1Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc2(); }
  catch (Calc2Exception e1)
  {
    // Continue on to the code below
  }

  try { return calc3(); }
  catch (Calc3Exception e1)
  {
    // Continue on to the code below
  }

  throw new NoCalcsWorkedException();
}

Mas suspeito que o verdadeiro problema de design seja a existência de três métodos diferentes que fazem essencialmente a mesma coisa (da perspectiva do chamador), mas lançam exceções diferentes e não relacionadas.

Isso pressupõe que as três exceções não estão relacionadas. Se todos eles tiverem uma classe base comum, seria melhor usar um loop com um único bloco catch, como Ani sugeriu.

Wyzard
fonte
1
+1: Esta é a solução mais limpa e prática para o problema. As outras soluções que vejo aqui são apenas tentar ser bonitinho, IMO. Como disse o OP, ele não escreveu a API, então está preso às exceções que são lançadas.
Nate CK
19

Tente não controlar a lógica com base em exceções; observe também que exceções devem ser lançadas apenas em casos excepcionais. Os cálculos, na maioria dos casos, não devem lançar exceções, a menos que acessem recursos externos ou analisem strings ou algo assim. De qualquer forma, no pior caso, siga o estilo TryMethod (como TryParse ()) para encapsular a lógica de exceção e tornar seu fluxo de controle sustentável e limpo:

bool TryCalculate(out double paramOut)
{
  try
  {
    // do some calculations
    return true;
  }
  catch(Exception e)
  { 
     // do some handling
    return false;
  }

}

double calcOutput;
if(!TryCalc1(inputParam, out calcOutput))
  TryCalc2(inputParam, out calcOutput);

Outra variação utilizando o padrão Try e combinando a lista de métodos em vez de aninhados se:

internal delegate bool TryCalculation(out double output);

TryCalculation[] tryCalcs = { calc1, calc2, calc3 };

double calcOutput;
foreach (var tryCalc in tryCalcs.Where(tryCalc => tryCalc(out calcOutput)))
  break;

e se o foreach for um pouco complicado, você pode deixar claro:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }
Mohamed Abed
fonte
Honestamente, acho que isso só causa abstrações desnecessárias. Esta não é uma solução horrível, mas eu não a usaria na maioria dos casos.
user606723
Se você não se preocupa com o tipo de exceção e deseja apenas lidar com o código condicional ... portanto, é definitivamente melhor em termos de abstração e capacidade de manutenção convertê-lo em um método condicional com retorno, quer seja bem-sucedido ou não, desta forma você oculta a sintaxe confusa de tratamento de exceções com um método descritivo claro ... então seu código irá tratá-la, pois é um método condicional regular.
Mohamed Abed,
Eu conheço os pontos e eles são válidos. No entanto, ao usar este tipo de abstração (escondendo bagunça / complexidade) em quase todos os lugares, torna-se ridículo e entender um pedaço de software para o que é se torna muito mais difícil. Como disse, não é uma solução terrível, mas não a usaria levianamente.
user606723
9

Crie uma lista de delegados para suas funções de cálculo e, em seguida, faça um loop while para percorrê-los:

List<Func<double>> calcMethods = new List<Func<double>>();

// Note: I haven't done this in a while, so I'm not sure if
// this is the correct syntax for Func delegates, but it should
// give you an idea of how to do this.
calcMethods.Add(new Func<double>(calc1));
calcMethods.Add(new Func<double>(calc2));
calcMethods.Add(new Func<double>(calc3));

double val;
for(CalcMethod calc in calcMethods)
{
    try
    {
        val = calc();
        // If you didn't catch an exception, then break out of the loop
        break;
    }
    catch(GenericCalcException e)
    {
        // Not sure what your exception would be, but catch it and continue
    }

}

return val; // are you returning the value?

Isso deve lhe dar uma ideia geral de como fazer isso (ou seja, não é uma solução exata).

Kiril
fonte
1
Exceto, é claro, pelo fato de que normalmente você nunca deveria pegá-los Exception. ;)
DeCaf
@DeCaf como eu disse: que "deve dar uma idéia geral de como fazer isso (ou seja, não é uma solução exata)." Portanto, o OP pode capturar qualquer que seja a exceção apropriada ... não há necessidade de capturar o genérico Exception.
Kiril,
Sim, desculpe, apenas senti a necessidade de divulgar isso.
DeCaf de
1
@DeCaf, é um esclarecimento válido para quem não conhece as melhores práticas. Obrigado :)
Kiril
9

Isso parece um trabalho para ... MONADS! Especificamente, a mônada Maybe. Comece com a mônada Maybe conforme descrito aqui . Em seguida, adicione alguns métodos de extensão. Escrevi esses métodos de extensão especificamente para o problema conforme você o descreveu. O bom das mônadas é que você pode escrever os métodos de extensão exatos necessários para sua situação.

public static Maybe<T> TryGet<T>(this Maybe<T> m, Func<T> getFunction)
{
    // If m has a value, just return m - we want to return the value
    // of the *first* successful TryGet.
    if (m.HasValue)
    {
        return m;
    }

    try
    {
        var value = getFunction();

        // We were able to successfully get a value. Wrap it in a Maybe
        // so that we can continue to chain.
        return value.ToMaybe();
    }
    catch
    {
        // We were unable to get a value. There's nothing else we can do.
        // Hopefully, another TryGet or ThrowIfNone will handle the None.
        return Maybe<T>.None;
    }
}

public static Maybe<T> ThrowIfNone<T>(
    this Maybe<T> m,
    Func<Exception> throwFunction)
{
    if (!m.HasValue)
    {
        // If m does not have a value by now, give up and throw.
        throw throwFunction();
    }

    // Otherwise, pass it on - someone else should unwrap the Maybe and
    // use its value.
    return m;
}

Use-o assim:

[Test]
public void ThrowIfNone_ThrowsTheSpecifiedException_GivenNoSuccessfulTryGet()
{
    Assert.That(() =>
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => { throw new Exception(); })
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Throws.TypeOf<NoCalcsWorkedException>());
}

[Test]
public void Value_ReturnsTheValueOfTheFirstSuccessfulTryGet()
{
    Assert.That(
        Maybe<double>.None
            .TryGet(() => { throw new Exception(); })
            .TryGet(() => 0)
            .TryGet(() => 1)
            .ThrowIfNone(() => new NoCalcsWorkedException())
            .Value,
        Is.EqualTo(0));
}

Se você se pega fazendo esse tipo de cálculo com frequência, talvez a mônada deva reduzir a quantidade de código clichê que você precisa escrever e, ao mesmo tempo, aumentar a legibilidade do código.

fre0n
fonte
2
Eu amo essa solução. No entanto, é bastante opaco para qualquer pessoa que nunca teve contato com mônadas, o que significa que está muito longe de ser idiomático em c #. Eu não gostaria que um de meus colegas de trabalho aprendesse mônadas apenas para modificar esse código bobo no futuro. Isso é ótimo para referência futura, no entanto.
jjoelson,
1
+1 para o senso de humor, para escrever a solução mais obtusa e prolixa possível para este problema e então dizer que vai "reduzir a quantidade de código clichê que você precisa escrever enquanto aumenta a legibilidade do seu código".
Nate CK
1
Ei, nós não reclamamos das enormes quantidades de código escondidas em System.Linq, e felizmente usamos essas mônadas o dia todo. Acho que @ fre0n significa apenas que, se você estiver disposto a colocar o Maybe mônada em seu kit de ferramentas, esses tipos de avaliações encadeadas se tornam mais fáceis de olhar e raciocinar. Existem várias implementações fáceis de entender.
Sebastian Good de
Só porque usa, Maybeisso não torna a solução monádica; ele usa zero das propriedades monádicas de Maybee, portanto, pode muito bem apenas usar null. Além disso, usado "monadicamente" seria o inverso do Maybe. Uma solução monádica real teria que usar uma mônada de estado que mantém o primeiro valor não excepcional como seu estado, mas isso seria um exagero quando a avaliação normal encadeada funciona.
Dax Fohl
7

Outra versão da abordagem do método try . Este permite exceções digitadas, uma vez que existe um tipo de exceção para cada cálculo:

    public bool Try<T>(Func<double> func, out double d) where T : Exception
    {
      try
      {
        d = func();
        return true;
      }
      catch (T)
      {
        d = 0;
        return false;
      }
    }

    // usage:
    double d;
    if (!Try<Calc1Exception>(() = calc1(), out d) && 
        !Try<Calc2Exception>(() = calc2(), out d) && 
        !Try<Calc3Exception>(() = calc3(), out d))

      throw new NoCalcsWorkedException();
    }
Stefan
fonte
Na verdade, você poderia evitar os ifs aninhados usando &&entre cada condição.
DeCaf
4

Em Perl você pode fazer foo() or bar(), que será executado bar()se foo()falhar. Em C #, não vemos essa construção "se falhar, então", mas há um operador que podemos usar para essa finalidade: o operador de coalescência nula?? , que continua somente se a primeira parte for nula.

Se você puder alterar a assinatura de seus cálculos e se envolver suas exceções (como mostrado nos posts anteriores) ou reescrevê-los para retornar null, sua cadeia de código se torna cada vez mais breve e ainda fácil de ler:

double? val = Calc1() ?? Calc2() ?? Calc3() ?? Calc4();
if(!val.HasValue) 
    throw new NoCalcsWorkedException();

Usei as seguintes substituições para suas funções, o que resulta no valor 40.40em val.

static double? Calc1() { return null; /* failed */}
static double? Calc2() { return null; /* failed */}
static double? Calc3() { return null; /* failed */}
static double? Calc4() { return 40.40; /* success! */}

Sei que essa solução nem sempre será aplicável, mas você fez uma pergunta muito interessante e acredito, mesmo que o tópico seja relativamente antigo, que vale a pena considerar esse padrão quando você puder fazer as pazes.

Abel
fonte
1
Eu só quero dizer obrigado". Tentei implementar o que você estava falando . Espero ter entendido corretamente.
AlexMelw
3

Dado que os métodos de cálculo têm a mesma assinatura sem parâmetros, você pode registrá-los em uma lista e iterar por essa lista e executar os métodos. Provavelmente, seria ainda melhor para você usar o Func<double>significado de "uma função que retorna um resultado do tipo double".

using System;
using System.Collections.Generic;

namespace ConsoleApplication1
{
  class CalculationException : Exception { }
  class Program
  {
    static double Calc1() { throw new CalculationException(); }
    static double Calc2() { throw new CalculationException(); }
    static double Calc3() { return 42.0; }

    static void Main(string[] args)
    {
      var methods = new List<Func<double>> {
        new Func<double>(Calc1),
        new Func<double>(Calc2),
        new Func<double>(Calc3)
    };

    double? result = null;
    foreach (var method in methods)
    {
      try {
        result = method();
        break;
      }
      catch (CalculationException ex) {
        // handle exception
      }
     }
     Console.WriteLine(result.Value);
   }
}
Marcin Seredynski
fonte
3

Você pode usar Task / ContinueWith e verificar a exceção. Aqui está um bom método de extensão para ajudar a torná-lo bonito:

    static void Main() {
        var task = Task<double>.Factory.StartNew(Calc1)
            .OrIfException(Calc2)
            .OrIfException(Calc3)
            .OrIfException(Calc4);
        Console.WriteLine(task.Result); // shows "3" (the first one that passed)
    }

    static double Calc1() {
        throw new InvalidOperationException();
    }

    static double Calc2() {
        throw new InvalidOperationException();
    }

    static double Calc3() {
        return 3;
    }

    static double Calc4() {
        return 4;
    }
}

static class A {
    public static Task<T> OrIfException<T>(this Task<T> task, Func<T> nextOption) {
        return task.ContinueWith(t => t.Exception == null ? t.Result : nextOption(), TaskContinuationOptions.ExecuteSynchronously);
    }
}
Dax Fohl
fonte
1

Se o tipo real de exceção lançada não importa, você pode apenas usar um bloco catch sem tipo:

var setters = new[] { calc1, calc2, calc3 };
bool succeeded = false;
foreach(var s in setters)
{
    try
    {
            val = s();
            succeeded = true;
            break;
    }
    catch { /* continue */ }
}
if (!suceeded) throw new NoCalcsWorkedException();
Jacob Krall
fonte
Isso não chama sempre todas as funções da lista? Pode querer lançar (trocadilho não intencional) em algo como if(succeeded) { break; }pós-captura.
um CVn de
1
using System;

namespace Utility
{
    /// <summary>
    /// A helper class for try-catch-related functionality
    /// </summary>
    public static class TryHelper
    {
        /// <summary>
        /// Runs each function in sequence until one throws no exceptions;
        /// if every provided function fails, the exception thrown by
        /// the final one is left unhandled
        /// </summary>
        public static void TryUntilSuccessful( params Action[] functions )
        {
            Exception exception = null;

            foreach( Action function in functions )
            {
                try
                {
                    function();
                    return;
                }
                catch( Exception e )
                {
                    exception   = e;
                }
            }

            throw exception;
        }
    }
}

E use-o assim:

using Utility;

...

TryHelper.TryUntilSuccessful(
    () =>
    {
        /* some code */
    },
    () =>
    {
        /* more code */
    },
    calc1,
    calc2,
    calc3,
    () =>
    {
        throw NotImplementedException();
    },
    ...
);
Ryan Lester
fonte
1

Parece que a intenção do OP era encontrar um bom padrão para resolver seu problema e resolver o problema atual com o qual ele estava lutando naquele momento.

OP: "Eu poderia envolver cada cálculo em um método auxiliar que retorna nulo em caso de falha e, em seguida, apenas usar o ??operador, mas há uma maneira de fazer isso de forma mais geral (ou seja, sem ter que escrever um método auxiliar para cada método que desejo usar)? Já pensei em escrever um método estático usando genéricos que envolva qualquer método em um try / catch e retorne null em caso de falha, mas não tenho certeza de como faria isso. Alguma idéia? "

Eu vi muitos bons padrões que evitam blocos aninhados de try catch , postados neste feed, mas não encontrei uma solução para o problema citado acima. Então, aqui está a solução:

Como OP mencionou acima, ele queria fazer um objeto wrapper que retornasse nullem caso de falha . Eu o chamaria de pod ( pod seguro para exceções ).

public static void Run()
{
    // The general case
    // var safePod1 = SafePod.CreateForValueTypeResult(() => CalcX(5, "abc", obj));
    // var safePod2 = SafePod.CreateForValueTypeResult(() => CalcY("abc", obj));
    // var safePod3 = SafePod.CreateForValueTypeResult(() => CalcZ());

    // If you have parameterless functions/methods, you could simplify it to:
    var safePod1 = SafePod.CreateForValueTypeResult(Calc1);
    var safePod2 = SafePod.CreateForValueTypeResult(Calc2);
    var safePod3 = SafePod.CreateForValueTypeResult(Calc3);

    var w = safePod1() ??
            safePod2() ??
            safePod3() ??
            throw new NoCalcsWorkedException(); // I've tested it on C# 7.2

    Console.Out.WriteLine($"result = {w}"); // w = 2.000001
}

private static double Calc1() => throw new Exception("Intentionally thrown exception");
private static double Calc2() => 2.000001;
private static double Calc3() => 3.000001;

Mas e se você quiser criar um pod seguro para um resultado de Tipo de Referência retornado por funções / métodos CalcN ().

public static void Run()
{
    var safePod1 = SafePod.CreateForReferenceTypeResult(Calc1);
    var safePod2 = SafePod.CreateForReferenceTypeResult(Calc2);
    var safePod3 = SafePod.CreateForReferenceTypeResult(Calc3);

    User w = safePod1() ?? safePod2() ?? safePod3();

    if (w == null) throw new NoCalcsWorkedException();

    Console.Out.WriteLine($"The user object is {{{w}}}"); // The user object is {Name: Mike}
}

private static User Calc1() => throw new Exception("Intentionally thrown exception");
private static User Calc2() => new User { Name = "Mike" };
private static User Calc3() => new User { Name = "Alex" };

class User
{
    public string Name { get; set; }
    public override string ToString() => $"{nameof(Name)}: {Name}";
}

Portanto, você pode notar que não há necessidade de "escrever um método auxiliar para cada método que deseja usar" .

Os dois tipos de pods (para ValueTypeResults e ReferenceTypeResults) são suficientes .


Aqui está o código de SafePod. Porém, não é um contêiner. Em vez disso, ele cria um wrapper de delegado seguro para exceções para ValueTypeResults e ReferenceTypeResults.

public static class SafePod
{
    public static Func<TResult?> CreateForValueTypeResult<TResult>(Func<TResult> jobUnit) where TResult : struct
    {
        Func<TResult?> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }

    public static Func<TResult> CreateForReferenceTypeResult<TResult>(Func<TResult> jobUnit) where TResult : class
    {
        Func<TResult> wrapperFunc = () =>
        {
            try { return jobUnit.Invoke(); } catch { return null; }
        };

        return wrapperFunc;
    }
}

É assim que você pode alavancar o operador de coalescência nula ??combinado com o poder das entidades de cidadãos de primeira classedelegate .

AlexMelw
fonte
0

Você está certo sobre embrulhar cada cálculo, mas deve embrulhar de acordo com o princípio diga-não-pergunte.

double calc3WithConvertedException(){
    try { val = calc3(); }
    catch (Calc3Exception e3)
    {
        throw new NoCalcsWorkedException();
    }
}

double calc2DefaultingToCalc3WithConvertedException(){
    try { val = calc2(); }
    catch (Calc2Exception e2)
    {
        //defaulting to simpler method
        return calc3WithConvertedException();
    }
}


double calc1DefaultingToCalc2(){
    try { val = calc2(); }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}

As operações são simples e podem mudar seu comportamento de forma independente. E não importa por que eles ficam inadimplentes. Como prova, você pode implementar calc1DefaultingToCalc2 como:

double calc1DefaultingToCalc2(){
    try { 
        val = calc2(); 
        if(specialValue(val)){
            val = calc2DefaultingToCalc3WithConvertedException()
        }
    }
    catch (Calc1Exception e1)
    {
        //defaulting to simpler method
        return calc2defaultingToCalc3WithConvertedException();
    }
}
raisercostin
fonte
-1

Parece que seus cálculos têm mais informações válidas para retornar do que apenas o cálculo em si. Talvez faça mais sentido para eles fazerem seu próprio tratamento de exceções e retornar uma classe de "resultados" que contém informações de erro, informações de valor etc. Pense como a classe AsyncResult faz seguindo o padrão assíncrono. Você pode então avaliar o resultado real do cálculo. Você pode racionalizar isso pensando em termos que, se um cálculo falhar, isso será tão informativo quanto se ele fosse aprovado. Portanto, uma exceção é uma informação, não um "erro".

internal class SomeCalculationResult 
{ 
     internal double? Result { get; private set; } 
     internal Exception Exception { get; private set; }
}

...

SomeCalculationResult calcResult = Calc1();
if (!calcResult.Result.HasValue) calcResult = Calc2();
if (!calcResult.Result.HasValue) calcResult = Calc3();
if (!calcResult.Result.HasValue) throw new NoCalcsWorkedException();

// do work with calcResult.Result.Value

...

Claro, estou me perguntando mais sobre a arquitetura geral que você está usando para fazer esses cálculos.

Emoire
fonte
Isso está ok - semelhante ao que o OP sugeriu no que diz respeito aos cálculos. Eu apenas prefiro algo como while (!calcResult.HasValue) nextCalcResult(), em vez de uma lista de Calc1, Calc2, Calc3 etc.
Kirk Broadhurst,
-3

Que tal rastrear as ações que você está fazendo ...

double val;
string track = string.Empty;

try 
{ 
  track = "Calc1";
  val = calc1(); 

  track = "Calc2";
  val = calc2(); 

  track = "Calc3";
  val = calc3(); 
}
catch (Exception e3)
{
   throw new NoCalcsWorkedException( track );
}
Orn Kristjansson
fonte
4
Como isso ajuda? se calc1 () falhar, cals2 nunca será executado!
DeCaf de
isso não resolve o problema. Apenas execute calc1 se calc2 falhar, apenas execute calc3 se calc1 && calc2 falhar.
Jason,
+1 orn. Isto é o que eu faço. Eu só preciso codificar uma captura, a mensagem enviada para mim ( trackneste caso), e sei exatamente qual segmento em meu código causou a falha do bloco. Talvez você devesse ter elaborado para dizer aos membros como o DeCaf que a trackmensagem é enviada para sua rotina de tratamento de erros personalizada que permite depurar seu código. Parece que ele não entendeu sua lógica.
jp2code
Bem, @DeCaf está correto, meu segmento de código não continua executando a próxima função que é o que jjoelson pediu, pois minha solução não é viável
Orn Kristjansson