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