Шаблон, щоб уникнути вкладених блоків спробувати?


114

Розглянемо ситуацію, коли у мене є три (або більше) способів проведення розрахунку, кожен з яких може вийти з ладу за винятком. Для того, щоб спробувати кожен розрахунок, поки не знайдемо успішний, я робив наступне:

double val;

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

Чи є якась прийнята модель, яка досягає цього в приємніший спосіб? Звичайно, я міг обернути кожен обчислення в допоміжний метод, який повертає нуль після відмови, а потім просто скористатися ??оператором, але чи є спосіб зробити це більш загально (тобто, не потрібно писати допоміжний метод для кожного методу, який я хочу використовувати )? Я думав над тим, щоб написати статичний метод, використовуючи дженерики, який обертає будь-який даний метод у спробі / ловити та повертає нульове значення після відмови, але я не впевнений, як би мені це зробити. Будь-які ідеї?


Чи можете ви включити деякі деталі щодо розрахунку?
Джеймс Джонсон

2
Вони в основному просто різні методи розв'язання / наближення PDE. Вони із сторонньої бібліотеки, тому я не можу їх змінити, щоб вони повернули коди помилок або нульові. Найкраще, що я міг зробити, це обмотати кожного окремо методом.
jjoelson

Чи є методи обчислення частиною вашого проекту (замість сторонньої бібліотеки)? Якщо так, ви можете вивести логіку, яка викидає винятки, і використовувати її, щоб визначити, який метод calc потрібно викликати.
Кріс

1
Існує ще один варіант використання для цього , що я натрапив на Java - Мені потрібно , щоб розібрати Stringдо Dateвикористання , SimpleDateFormat.parseі мені потрібно спробувати кілька різних формати для того, як перейти до наступного , коли один кидає виняток.
Жалюгідна змінна

Відповіді:


125

Наскільки це можливо, не використовуйте винятки для контрольного потоку або надзвичайних обставин.

Але відповісти на ваше запитання безпосередньо (якщо всі типи винятків однакові):

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

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

throw new NoCalcsWorkedException();

15
Це передбачає, що Calc1Exception, Calc2Exceptionі Calc3Exceptionспільний базовий клас.
Wyzard

3
Зверху він припускає загальний підпис - що насправді не так вже й далеко. Хороший відповідь.
TomTom

1
Також я додав continueу блок лову та breakпісля блоку лову, щоб цикл закінчувався, коли працює обчислення (спасибі Лірику за цей біт)
jjoelson

6
+1 лише тому, що в ньому написано "Не використовуйте винятки для контрольного потоку", хоча я б використовував "НІКОЛИ НІКОЛИ" замість "Наскільки це можливо".
Білл К

1
@jjoelson: breakЗаява, що випливає calc();в межах try(і зовсім немає continueзаяви), може бути трохи ідіоматичнішою.
Адам Робінсон

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

ПРИМІТКА: Я аж ніяк не кажу, що це найкращий спосіб зробити роботу, просто іншим способом


6
Мені довелося один раз реалізувати "On Error Resume Next" мовою, і генерований нами код виглядав приблизно так.
Якоб Кралл

4
Будь ласка, ніколи не використовуйте оператор перемикання для створення циклу.
Джефф Ферланд

3
Неможливо мати заяву перемикання для циклу
Мохаммед Абед

1
Я знаю, що моя відповідь - це не найефективніший код, але знову ж таки використання блоків спробу / лову для подібних речей - це не найкращий спосіб. На жаль, ОП використовує сторонні бібліотеки і має зробити все можливе, щоб забезпечити успіх. В ідеалі, вхід може бути попередньо перевірений і обрана правильна функція обчислення, щоб забезпечити його невдачу - звичайно, ви могли б потім поставити все це в спробу / ловити лише для безпечності;)
musefan

1
return DoCalc(c++)еквівалентно return DoCalc(c)- післякріплене значення не передаватиметься глибше. Щоб змусити його працювати (і ввести більше незрозумілості), це могло б бути більше схожим return DoCalc((c++,c)).
Артур Чайка

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

Але я підозрюю, що справжня проблема дизайну - це існування трьох різних методів, які роблять по суті те саме (з точки зору абонента), але викидають різні, не пов'язані між собою винятки.

Це припускаючи, що три винятки не пов'язані між собою. Якщо всі вони мають загальний базовий клас, було б краще використовувати цикл з одним блоком лову, як запропонувала Ані.


1
+1: Це найчистіше, найнеобхідніше рішення проблеми. Інші рішення, які я бачу тут, просто намагаються бути милими, IMO. Як сказав ОП, він не написав API, тому він застряг із винятками, які кидаються.
Nate CK

19

Намагайтеся не контролювати логіку на основі винятків; зауважте також, що винятки слід кидати лише у виняткових випадках. Обчислення в більшості випадків не повинні викидати винятки, якщо вони не мають доступу до зовнішніх ресурсів, рядків розбору або чогось іншого. У будь-якому випадку в найгіршому випадку дотримуйтесь стилю 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);

Інший варіант, що використовує шаблон "Спробуйте" та комбінує список методів замість вкладених, якщо:

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 (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }

Чесно кажучи, я думаю, що це просто викликає зайві абстракції. Це не жахливе рішення, але я б не використовував це в більшості випадків.
користувач606723

Якщо ви не переймаєтесь типом винятку, а ви просто хочете обробляти умовний код .. таким чином, безумовно, краще з точки зору абстрагування та ремонтопридатності перетворити його на умовний метод із поверненням, вдалось це чи ні, таким чином ви приховуєте виняток, що обробляє брудний синтаксис чітким описовим методом .. тоді ваш код буде обробляти його, як це звичайний умовний метод.
Мохаммед Абед

Я знаю моменти, і вони справедливі. Однак при використанні цього виду абстракції (приховування безладу / складності) майже скрізь стає смішним і розуміти частину програмного забезпечення для того, що це стає набагато складніше. Як я вже сказав, це не жахливе рішення, але я б не використовував його з легкістю.
користувач606723

9

Створіть список делегатів для своїх обчислювальних функцій, а потім увімкніть цикл, щоб перейти через них:

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?

Це повинно дати вам загальне уявлення про те, як це зробити (тобто це не точне рішення).


1
За винятком того факту, що ви, як правило, Exceptionтоді ніколи не ловите . ;)
DeCaf

@DeCaf, як я вже сказав: це "повинно дати вам загальне уявлення про те, як це зробити (тобто не точне рішення)". Таким чином, ОП може спіймати будь-який відповідний виняток ... немає необхідності ловити загальне Exception.
Кирило

Так, вибачте, я просто відчув потребу вийти звідти.
DeCaf

1
@DeCaf, це вагоме уточнення для тих, хто, можливо, не знайомий з найкращими методами. Дякую :)
Кирило

9

Це виглядає як робота для ... МОНАДІВ! Зокрема, монада "Можливо". Почніть з монади "Можливо", як описано тут . Потім додайте кілька методів розширення. Я написав ці методи розширення спеціально для проблеми, як ви її описали. Приємна річ про монадах - ви можете написати точні методи розширення, необхідні для вашої ситуації.

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

Якщо ви часто виявляєте такі обчислення, монада, можливо, повинна зменшити кількість кодового коду, який ви повинні написати, збільшуючи читабельність вашого коду.


2
Я люблю це рішення. Однак це досить непрозоро для тих, хто раніше не мав впливу монад, а це означає, що це дуже далеко від ідіоматичного в c #. Я не хотів би, щоб хтось із моїх колег мав вивчити монади просто для того, щоб в майбутньому змінити цей нерозумний фрагмент коду. Однак це чудово підходить для подальшого використання.
jjoelson

1
+1 для почуття гумору, для написання найбільш тугого та багатослівного рішення цієї проблеми, а потім сказати, що це "зменшить кількість кодового коду, який ви повинні написати, збільшивши читабельність вашого коду".
Nate CK

1
Гей, ми не скаржимось на велику кількість коду, що ховається в System.Linq, і радісно використовуємо ці монади цілий день. Я думаю, що @ fre0n просто означає, що якщо ви готові помістити монаду "Можливо" у свій інструментарій, подібні ланцюгові оцінки стануть простішими для розгляду та обґрунтування. Є кілька реалізацій, які легко схопити.
Себастьян Хороший

Тільки тому, що він використовує, Maybeце не робить це монадичним рішенням; він використовує нуль монадичних властивостей Maybeі так може просто використовувати null. Крім того, використовується «monadicly» це було б зворотне з Maybe. Справжнім монадичним рішенням доведеться використовувати державну монаду, яка зберігає перше не виняткове значення як свій стан, але це буде непосильним, коли працює нормальна ланцюгова оцінка.
Дакс Фол

7

Інший варіант спробувати метод підходу. Цей дозволяє вводити винятки, оскільки для кожного обчислення існує тип виключення:

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

Ви могли фактично уникнути вкладених ifs, використовуючи &&натомість між кожними умовами.
DeCaf

4

У Perl ви можете зробити foo() or bar(), що виконає, bar()якщо foo()не вдасться. У C # ми не бачимо цього "if fail, then" конструюємо, але є оператор, який ми можемо використовувати для цього: оператор null-coalesce?? , який продовжується, лише якщо перша частина є null.

Якщо ви можете змінити підпис своїх розрахунків і якщо ви або перекладете їх винятки (як показано в попередніх публікаціях), або переписати їх, щоб повернутися nullзамість цього, ваш кодовий ланцюг стає все більш коротким і все ще легко читається:

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

Я використовував наступні заміни для ваших функцій, що призводить до значення 40.40в 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! */}

Я усвідомлюю, що це рішення не завжди може бути застосовно, але ви поставили дуже цікаве питання, і я вважаю, навіть якщо нитка є відносно старою, що це шаблон, який варто розглянути, коли ви можете внести зміни.


1
Я просто хочу сказати "Дякую". Я намагався реалізувати те, про що ти говорив . Я сподіваюся, що я це правильно зрозумів.
AlexMelw

3

Зважаючи на те, що методи обчислення мають однаковий підпис без параметрів, ви можете зареєструвати їх у списку, а також повторити цей список і виконати методи. Напевно, вам було б навіть краще використовувати Func<double>значення "функція, яка повертає результат типу 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);
   }
}

3

Ви можете використовувати Task / ContinueWith і перевірити на виняток. Ось приємний метод розширення, який допоможе зробити його гарним:

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

1

Якщо фактичний тип викинутого винятку не має значення, ви можете просто використовувати безпілотний блок лову:

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

Чи не завжди це викликає кожну функцію у списку? Можливо, хочеться кинути (каламбур не призначений) у щось на кшталт if(succeeded) { break; }пост-лову.
CVn

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

І використовуйте його так:

using Utility;

...

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

1

Схоже, що наміром ОП було знайти вдалу модель для вирішення його питання та вирішення поточної проблеми, з якою він на той момент боровся.

ОП: "Я міг би обернути кожен обчислення в допоміжний метод, який повертає нуль після відмови, а потім просто скористатися ??оператором, але чи є спосіб зробити це більш загально (тобто без необхідності писати допоміжний метод для кожного методу, який я хочу ?) Я думав над написанням статичного методу з використанням дженериків, який перетворює будь-який заданий метод на спробу / ловити і повертає нуль при відмові, але я не впевнений, як би я з цим пішов. Будь-які ідеї? "

Я побачив багато хороших зразків, які уникають вкладених блоків спробу вловлювання , розміщених у цьому каналі, але не знайшли рішення проблеми, про яку йдеться вище. Отже, ось рішення:

Як згадувалося в ОП, він хотів зробити обгортковий об'єкт, який повертається nullпісля відмови . Я б назвав це стручком ( безпечний для винятку стручок ).

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;

Але що робити, якщо ви хочете створити безпечний стручок для результату Reference Type, що повертається 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}";
}

Отже, ви можете помітити, що не потрібно "писати допоміжний метод для кожного методу, який ви хочете використовувати" .

Ці два типи стручків (для ValueTypeResults- і ReferenceTypeResultс) є досить .


Ось код SafePod. Це не контейнер. Натомість він створює безпечну для винятків делегатну обгортку і для ValueTypeResults і 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;
    }
}

Ось таким чином можна використовувати оператора ??зведення нуля в поєднанні з потужністю першокласних організацій громадянdelegate .


0

Ви маєте рацію в обговоренні кожного розрахунку, але вам слід перетворити його відповідно до принципу "сказати-не запитай".

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

Операції прості і можуть змінювати свою поведінку самостійно. І не має значення, чому вони за замовчуванням. Як доказ ви можете реалізувати calc1DefaultingToCalc2 як:

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

-1

Здається, що ваші розрахунки мають повернути більш достовірну інформацію, ніж просто сам розрахунок. Можливо, їм було б більше сенсу робити обробку власних винятків і повертати клас "результати", який містить інформацію про помилки, інформацію про значення тощо. Подумайте, як робить клас AsyncResult за схемою async. Потім можна оцінити реальний результат розрахунку. Ви можете раціоналізувати це, подумавши, що якщо розрахунок не вдасться, це так само інформаційно, як якщо б він пройшов. Тому виняток - це інформація, а не «помилка».

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

...

Звичайно, мені цікаво більше про загальну архітектуру, яку ви використовуєте для виконання цих розрахунків.


Це нормально - подібно до того, що пропонував ОП, що стосується завершених розрахунків. Я просто віддаю перевагу щось на зразок while (!calcResult.HasValue) nextCalcResult()замість списку Calc1, Calc2, Calc3 тощо
Кірк Бродхерст

-3

Як щодо відстеження дій, які ви робите ...

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

4
Як це допомагає? якщо calc1 () не вдається, cals2 ніколи не буде виконаний!
DeCaf

це не вирішує проблему. Виконати calc1 лише якщо calc2 не вдасться, виконувати лише calc3, якщо calc1 && calc2 не вдасться.
Джейсон

+1 орт. Це я і роблю. Я маю кодувати лише один улов, повідомлення, надіслане мені ( trackв даному випадку), і я точно знаю , який сегмент у моєму коді спричинив збій блоку. Можливо, вам слід було б детально сказати таким членам, як DeCaf, що trackповідомлення надсилається до вашої користувальницької процедури поводження з помилками, яка дозволяє налагоджувати код. Здається, він не зрозумів вашої логіки.
jp2code

Що ж, @DeCaf вірно, мій сегмент коду не продовжує виконувати наступну функцію, про що просив jjoelson, там для мого рішення це неможливо
Orn Kristjansson
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.