jjo*_*son 112 c# monads design-patterns 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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
是否有任何可接受的模式以更好的方式实现这一目标?当然,我可以将每个计算包装在一个辅助方法中,该方法在失败时返回null,然后只使用??
运算符,但是有一种方法可以更普遍地执行此操作(即,无需为我想要使用的每个方法编写辅助方法)?我已经考虑过使用泛型编写一个静态方法,它在try/catch中包装任何给定的方法,并在失败时返回null,但我不确定如何解决这个问题.有任何想法吗?
Ani*_*Ani 126
尽可能不要在控制流程或非常规情况下使用例外.
但要直接回答你的问题(假设所有异常类型都相同):
Func<double>[] calcs = { calc1, calc2, calc3 };
foreach(var calc in calcs)
{
try { return calc(); }
catch (CalcException){ }
}
throw new NoCalcsWorkedException();
Run Code Online (Sandbox Code Playgroud)
mus*_*fan 38
只是为了提供"开箱即用"的替代方案,递归函数怎么样......
//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);
}
}
Run Code Online (Sandbox Code Playgroud)
注意:我绝不是说这是完成工作的最佳方式,只是一种不同的方式
Wyz*_*a-- 37
您可以通过将嵌套放入这样的方法来展平嵌套:
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();
}
Run Code Online (Sandbox Code Playgroud)
但我怀疑真正的设计问题是存在三种不同的方法,这些方法基本上是相同的(从调用者的角度来看)但抛出不同的,无关的异常.
这假设三个例外是无关的.如果他们都有一个共同的基类,那么最好使用一个带有单个catch块的循环,正如Ani建议的那样.
Moh*_*bed 20
尽量不要根据异常来控制逻辑; 另请注意,只有在特殊情况下才应抛出异常.在大多数情况下,计算不应抛出异常,除非它们访问外部资源或解析字符串或其他内容.无论如何,在最坏的情况下遵循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);
Run Code Online (Sandbox Code Playgroud)
使用Try模式并组合方法列表而不是嵌套if的另一种变体:
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;
Run Code Online (Sandbox Code Playgroud)
如果foreach有点复杂,你可以说清楚:
foreach (var tryCalc in tryCalcs)
{
if (tryCalc(out calcOutput)) break;
}
Run Code Online (Sandbox Code Playgroud)
创建计算函数的委托列表,然后使用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?
Run Code Online (Sandbox Code Playgroud)
这应该让你大致了解如何做到这一点(即它不是一个确切的解决方案).
这看起来像...... MONADS的工作!具体来说,也许monad.从这里描述的Maybe monad开始.然后添加一些扩展方法.我正如你所描述的那样专门为这个问题编写了这些扩展方法.关于monad的好处是你可以编写你的情况所需的确切扩展方法.
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;
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它:
[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));
}
Run Code Online (Sandbox Code Playgroud)
如果您发现自己经常进行这些类型的计算,那么monad应该减少必须编写的样板代码量,同时提高代码的可读性.
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();
}
Run Code Online (Sandbox Code Playgroud)