[c#] 중첩 된 try catch 블록을 피하기위한 패턴?

계산을 수행하는 방법이 세 가지 (또는 그 이상)가있는 상황을 생각해보십시오. 각 방법은 예외로 인해 실패 할 수 있습니다. 성공할 때까지 각 계산을 시도하기 위해 다음을 수행했습니다.

double val;

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

더 좋은 방법으로 이것을 달성하는 허용되는 패턴이 있습니까? 물론 실패시 null을 반환하는 도우미 메서드로 각 계산을 래핑 한 다음 ??연산자를 사용할 수 있지만보다 일반적으로이 작업을 수행하는 방법이 있습니다 (즉, 사용하려는 각 메서드에 대한 도우미 메서드를 작성할 필요없이 )? 주어진 메서드를 try / catch에 래핑하고 실패시 null을 반환하는 제네릭을 사용하여 정적 메서드를 작성하는 것에 대해 생각했지만 어떻게해야할지 모르겠습니다. 어떤 아이디어?



답변

가능한 한 제어 흐름이나 예외적 인 상황에 예외를 사용하지 마십시오.

그러나 질문에 직접 답변하려면 (모든 예외 유형이 동일하다고 가정) :

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

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

throw new NoCalcsWorkedException();


답변

“상자 밖”대안을 제공하기 위해 재귀 함수는 어떻습니까?

//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);
   }
}

참고 :이 최선의 방법 작업을 수행하려면이 말하는 결코 오전, 단지 다른 방법


답변

다음과 같은 메서드에 넣어 중첩을 평평하게 만들 수 있습니다.

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();
}

그러나 실제 디자인 문제는 본질적으로 동일한 작업을 수행하지만 (호출자의 관점에서) 서로 다른 관련없는 예외를 던지는 세 가지 다른 메서드의 존재 라고 생각합니다 .

이것은 세 가지 예외 관련 이 없다고 가정합니다 . 모두 공통 기본 클래스를 가지고 있다면 Ani가 제안한 것처럼 단일 catch 블록이있는 루프를 사용하는 것이 좋습니다.


답변

예외를 기반으로 논리를 제어하지 마십시오. 예외는 예외적 인 경우에만 throw되어야합니다. 대부분의 경우 계산은 외부 리소스에 액세스하거나 문자열을 구문 분석하지 않는 한 예외를 throw하지 않아야합니다. 어쨌든 최악의 경우 TryMethod 스타일 (예 : TryParse ())을 따라 예외 논리를 캡슐화하고 제어 흐름을 유지 관리 가능하고 깔끔하게 만듭니다.

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);

Try 패턴을 사용하고 다음과 같은 경우 중첩 된 메서드 목록을 결합하는 또 다른 변형 :

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;

foreach가 약간 복잡하다면 간단하게 만들 수 있습니다.

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }


답변

계산 함수에 대한 대리자 목록을 만든 다음 while 루프를 사용하여 순환합니다.

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?

그것은 당신에게 그것을하는 방법에 대한 일반적인 아이디어를 줄 것입니다 (즉, 정확한 해결책이 아닙니다).


답변

이건 직업 같네요 … MONADS! 특히 Maybe 모나드입니다. 여기에 설명 된대로 Maybe 모나드 시작 합니다 . 그런 다음 몇 가지 확장 방법을 추가하십시오. 설명하신대로 문제에 대해 특별히 이러한 확장 방법을 작성했습니다. 모나드의 좋은 점은 상황에 필요한 정확한 확장 메소드를 작성할 수 있다는 것입니다.

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;
}

다음과 같이 사용하십시오.

[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));
}

이러한 종류의 계산을 자주 수행하는 경우 모나드는 코드의 가독성을 높이면서 작성해야하는 상용구 코드의 양을 줄여야합니다.


답변

try 메소드 접근법 의 또 다른 버전입니다 . 이것은 각 계산에 대한 예외 유형이 있기 때문에 유형화 된 예외를 허용합니다.

    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();
    }