Considere una situación en la que tengo tres (o más) formas de realizar un cálculo, cada una de las cuales puede fallar con una excepción. Para intentar cada cálculo hasta encontrar uno que tenga éxito, he estado haciendo lo siguiente:
double val;
try { val = calc1(); }
catch (Calc1Exception e1)
{
try { val = calc2(); }
catch (Calc2Exception e2)
{
try { val = calc3(); }
catch (Calc3Exception e3)
{
throw new NoCalcsWorkedException();
}
}
}
¿Existe algún patrón aceptado que logre esto de una manera más agradable? Por supuesto, podría envolver cada cálculo en un método auxiliar que devuelve un valor nulo en caso de falla, y luego simplemente usar el ??
operador, pero ¿hay alguna forma de hacer esto de manera más general (es decir, sin tener que escribir un método auxiliar para cada método que quiero usar? )? He pensado en escribir un método estático usando genéricos que envuelva cualquier método dado en un try / catch y devuelva nulo en caso de falla, pero no estoy seguro de cómo lo haría. ¿Algunas ideas?
fuente
String
unDate
usoSimpleDateFormat.parse
y necesito probar varios formatos diferentes en orden, pasando al siguiente cuando uno lanza una excepción.Respuestas:
En la medida de lo posible, no use excepciones para el flujo de control o circunstancias no excepcionales.
Pero para responder a su pregunta directamente (asumiendo que todos los tipos de excepción son los mismos):
fuente
Calc1Exception
,Calc2Exception
yCalc3Exception
comparten una clase base común.continue
en el bloque de captura ybreak
después del bloque de captura para que el ciclo finalice cuando funcione un cálculo (gracias a Lirik por este bit)break
declaración a continuacióncalc();
dentro detry
(y ningunacontinue
declaración en absoluto) podría ser un poco más idiomática.Solo para ofrecer una alternativa "fuera de la caja", ¿qué tal una función recursiva ...
NOTA: De ninguna manera estoy diciendo que esta sea la mejor manera de hacer el trabajo, solo de una manera diferente
fuente
return DoCalc(c++)
es equivalente areturn DoCalc(c)
: el valor posincrementado no se pasará más profundamente. Para que funcione (e introduzca más oscuridad) podría ser más parecidoreturn DoCalc((c++,c))
.Puede aplanar el anidamiento poniéndolo en un método como este:
Pero sospecho que lo real problema diseño es la existencia de tres métodos diferentes que hacen esencialmente lo mismo (desde la perspectiva de la persona que llama) pero arrojan excepciones diferentes y no relacionadas.
Esto es asumiendo que las tres excepciones no están relacionadas. Si todos tienen una clase base común, sería mejor usar un bucle con un solo bloque de captura, como sugirió Ani.
fuente
Trate de no controlar la lógica basada en excepciones; tenga en cuenta también que las excepciones deben lanzarse sólo en casos excepcionales. En la mayoría de los casos, los cálculos no deben generar excepciones a menos que accedan a recursos externos o analicen cadenas o algo así. De todos modos, en el peor de los casos, siga el estilo TryMethod (como TryParse ()) para encapsular la lógica de excepción y hacer que su flujo de control sea mantenible y limpio:
Otra variación que utiliza el patrón Try y combina la lista de métodos en lugar de anidados si:
y si el foreach es un poco complicado, puedes aclararlo:
fuente
Cree una lista de delegados para sus funciones de cálculo y luego tenga un ciclo while para recorrerlas:
Eso debería darle una idea general de cómo hacerlo (es decir, no es una solución exacta).
fuente
Exception
entonces. ;)Exception
.Esto parece un trabajo para ... ¡MONADAS! Específicamente, la mónada Quizás. Comience con la mónada Quizás como se describe aquí . Luego agregue algunos métodos de extensión. Escribí estos métodos de extensión específicamente para el problema tal como lo describiste. Lo bueno de las mónadas es que puede escribir los métodos de extensión exactos necesarios para su situación.
Úselo así:
Si se encuentra haciendo este tipo de cálculos con frecuencia, la mónada quizás debería reducir la cantidad de código repetitivo que tiene que escribir mientras aumenta la legibilidad de su código.
fuente
Maybe
no la convierte en una solución monádica; usa cero de las propiedades monádicas deMaybe
y, por lo tanto, también puede usarnull
. Además, si se usa "monádicamente", sería el inverso deMaybe
. Una solución monádica real tendría que usar una mónada de estado que mantenga el primer valor no excepcional como su estado, pero eso sería excesivo cuando la evaluación encadenada normal funciona.Otra versión del enfoque del método try . Éste permite excepciones escritas, ya que hay un tipo de excepción para cada cálculo:
fuente
&&
entre cada condición.En Perl puedes hacerlo
foo() or bar()
, que se ejecutarábar()
sifoo()
falla. En C # no vemos esta construcción "si falla, entonces", pero hay un operador que podemos usar para este propósito: el operador de coalescencia nula??
, que continúa solo si la primera parte es nula.Si puede cambiar la firma de sus cálculos y si envuelve sus excepciones (como se muestra en publicaciones anteriores) o las reescribe para regresar
null
, su cadena de código se vuelve cada vez más breve y aún más fácil de leer:Usé los siguientes reemplazos para sus funciones, lo que da como resultado el valor
40.40
enval
.Me doy cuenta de que esta solución no siempre será aplicable, pero usted planteó una pregunta muy interesante y creo, aunque el hilo es relativamente antiguo, que este es un patrón que vale la pena considerar cuando pueda hacer las enmiendas.
fuente
Dado que los métodos de cálculo tienen la misma firma sin parámetros, puede registrarlos en una lista, iterar a través de esa lista y ejecutar los métodos. Probablemente sería incluso mejor para usted usar el
Func<double>
significado de "una función que devuelve un resultado de tipodouble
".fuente
Puede usar Task / ContinueWith y verificar la excepción. Aquí hay un buen método de extensión para ayudar a que sea bonito:
fuente
Si el tipo real de la excepción lanzada no importa, puede usar un bloque catch sin tipo:
fuente
if(succeeded) { break; }
una captura posterior.Y úsalo así:
fuente
Parece que la intención del OP era encontrar un buen patrón para resolver su problema y resolver el problema actual con el que estaba luchando en ese momento.
Vi muchos patrones buenos que evitan bloques de captura de prueba anidados , publicados en esta fuente, pero no encontré una solución al problema que se cita anteriormente. Entonces, aquí está la solución:
Como OP mencionó anteriormente, quería hacer un objeto contenedor que regrese
null
en caso de falla . Yo lo llamaría una cápsula ( cápsula segura para excepciones ).Pero, ¿qué sucede si desea crear un pod seguro para un resultado de Tipo de referencia devuelto por las funciones / métodos de CalcN ()?
Por tanto, es posible que observe que no es necesario "escribir un método auxiliar para cada método que desee utilizar" .
Los dos tipos de vainas (para
ValueTypeResult
syReferenceTypeResult
s) son suficientes .Aquí está el código de
SafePod
. Sin embargo, no es un contenedor. En su lugar, crea un contenedor delegado seguro para excepciones tanto paraValueTypeResult
s como paraReferenceTypeResult
s.Así es como puede aprovechar el operador de fusión nula
??
combinado con el poder de las entidades ciudadanas de primera clasedelegate
.fuente
Tiene razón acerca de ajustar cada cálculo, pero debe ajustarlo de acuerdo con el principio de decir-no-preguntar.
Las operaciones son simples y pueden cambiar su comportamiento de forma independiente. Y no importa por qué no lo hacen. Como prueba, podría implementar calc1DefaultingToCalc2 como:
fuente
Parece que sus cálculos tienen más información válida para devolver que solo el cálculo en sí. Quizás tendría más sentido para ellos hacer su propio manejo de excepciones y devolver una clase de "resultados" que contiene información de error, información de valor, etc. Piense como lo hace la clase AsyncResult siguiendo el patrón asíncrono. A continuación, puede evaluar el resultado real del cálculo. Puede racionalizar esto pensando en términos de que si un cálculo falla, es tan informativo como si pasara. Por lo tanto, una excepción es un dato, no un "error".
Por supuesto, me pregunto más sobre la arquitectura general que está utilizando para realizar estos cálculos.
fuente
while (!calcResult.HasValue) nextCalcResult()
, en lugar de una lista de Calc1, Calc2, Calc3, etc.¿Qué pasa con el seguimiento de las acciones que está haciendo ...
fuente
track
en este caso), y sé exactamente qué segmento de mi código causó que el bloque fallara. Tal vez debería haber elaborado para decirle a miembros como DeCaf que eltrack
mensaje se envía a su rutina personalizada de manejo de errores que le permite depurar su código. Parece que no entendió tu lógica.