Mẫu để tránh khối try catch lồng nhau?


114

Hãy xem xét một tình huống mà tôi có ba (hoặc nhiều) cách thực hiện phép tính, mỗi cách có thể không thành công với một ngoại lệ. Để thử từng phép tính cho đến khi chúng tôi tìm thấy một phép tính thành công, tôi đã làm như sau:

double val;

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

Có bất kỳ mô hình được chấp nhận nào đạt được điều này theo cách tốt hơn không? Tất nhiên tôi có thể gói mỗi phép tính trong một phương thức trợ giúp trả về null khi không thành công và sau đó chỉ cần sử dụng ??toán tử, nhưng có cách nào để thực hiện điều này tổng quát hơn không (tức là không cần phải viết một phương thức trợ giúp cho mỗi phương thức tôi muốn sử dụng )? Tôi đã nghĩ về việc viết một phương thức tĩnh bằng cách sử dụng generic bao bọc bất kỳ phương thức nào đã cho trong một lần thử / bắt và trả về null khi thất bại, nhưng tôi không chắc mình sẽ làm như thế nào về điều này. Có ý kiến ​​gì không?


Bạn có thể bao gồm một số chi tiết về tính toán?
James Johnson

2
Về cơ bản chúng chỉ là các phương pháp khác nhau để giải quyết / ước lượng một PDE. Chúng đến từ thư viện của bên thứ 3, vì vậy tôi không thể thay đổi chúng để trả về mã lỗi hoặc giá trị rỗng. Điều tốt nhất tôi có thể làm là gói từng cái riêng lẻ trong một phương pháp.
jjoelson

Các phương thức calc có phải là một phần của dự án của bạn (thay vì thư viện của bên thứ ba) không? Nếu vậy, bạn có thể rút ra logic đưa ra các ngoại lệ và sử dụng logic đó để quyết định phương thức calc nào cần được gọi.
Chris

1
Có một trường hợp sử dụng khác cho điều này mà tôi đã gặp trong Java - tôi cần phân tích cú pháp từ a Stringthành Datesử dụng SimpleDateFormat.parsevà tôi cần thử một số định dạng khác nhau theo thứ tự, chuyển sang phần tiếp theo khi một trường hợp ném ra ngoại lệ.
Biến số khốn khổ

Câu trả lời:


125

Trong chừng mực có thể, không sử dụng các ngoại lệ cho luồng kiểm soát hoặc các trường hợp bất thường.

Nhưng để trả lời câu hỏi của bạn trực tiếp (giả sử tất cả các loại ngoại lệ đều giống nhau):

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

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

throw new NoCalcsWorkedException();

15
Điều này giả định rằng Calc1Exception, Calc2ExceptionCalc3Exceptionchia sẻ một lớp cơ sở chung.
Wyzard

3
Trên hết, anh ta giả định một chữ ký chung - điều này không thực sự quá xa vời. Người trả lời tốt.
TomTom,

1
Ngoài ra, tôi thêm vào continuetrong khối catch và breaksau khối catch để vòng lặp kết thúc khi một công trình tính toán (nhờ Lirik cho bit này)
jjoelson

6
+1 chỉ vì nó nói "Không sử dụng ngoại lệ cho luồng điều khiển" mặc dù tôi đã sử dụng "KHÔNG BAO GIỜ BAO GIỜ" thay vì "Càng xa càng tốt".
Bill K

1
@jjoelson: Một breaktuyên bố sau calc();trong try(và không có continuetuyên bố nào cả) có thể là một chút nhiều thành ngữ.
Adam Robinson

38

Chỉ để cung cấp một giải pháp thay thế "bên ngoài hộp", còn hàm đệ quy thì sao ...

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

LƯU Ý: Tôi không có ý nói rằng đây là cách tốt nhất để hoàn thành công việc, chỉ là một cách khác


6
Tôi đã phải triển khai "On Error Resume Next" bằng một ngôn ngữ một lần và mã tôi tạo ra trông giống như thế này.
Jacob Krall,

4
Vui lòng không sử dụng câu lệnh switch để tạo vòng lặp for.
Jeff Ferland 17/10/11

3
Nó không phải là duy trì để có câu lệnh switch cho Looping
Mohamed Abed

1
Tôi biết câu trả lời của tôi không phải là mã hiệu quả nhất, nhưng sau đó sử dụng lại các khối try / catch cho loại điều này không phải là cách tốt nhất để thực hiện. Thật không may, OP đang sử dụng thư viện của bên thứ 3 và phải làm những gì tốt nhất có thể để đảm bảo thành công. Lý tưởng nhất, các đầu vào bằng thể xác nhận đầu tiên và các chức năng tính toán chính xác được lựa chọn để đảm bảo nó sẽ không thất bại - tất nhiên, sau đó bạn có thể đặt tất cả những gì trong một try / catch chỉ để được an toàn;)
musefan

1
return DoCalc(c++)tương đương với return DoCalc(c)- giá trị sau tăng dần sẽ không được chuyển sâu hơn. Để làm cho nó hoạt động (và giới thiệu nhiều điều tối nghĩa hơn), nó có thể giống như vậy return DoCalc((c++,c)).
Artur Czajka,

37

Bạn có thể làm phẳng tổ bằng cách đặt nó vào một phương pháp như sau:

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

Nhưng tôi nghi ngờ sự thật vấn đề thiết kế là sự tồn tại của ba phương pháp khác nhau về cơ bản làm cùng một thứ (từ quan điểm của người gọi) nhưng lại đưa ra các ngoại lệ khác nhau, không liên quan.

Điều này được giả định rằng ba ngoại lệ không liên quan. Nếu tất cả chúng đều có một lớp cơ sở chung, tốt hơn nên sử dụng một vòng lặp với một khối bắt duy nhất, như Ani đã đề xuất.


1
+1: Đây là giải pháp sạch sẽ nhất, không vô nghĩa nhất cho vấn đề. Các giải pháp khác mà tôi thấy ở đây chỉ là cố gắng tỏ ra dễ thương, IMO. Như OP đã nói, anh ấy không viết API nên anh ấy bị mắc kẹt với các ngoại lệ được ném ra.
Nate CK

19

Cố gắng không kiểm soát logic dựa trên các ngoại lệ; cũng lưu ý rằng các ngoại lệ chỉ nên được ném trong các trường hợp ngoại lệ. Các phép tính trong hầu hết các trường hợp không nên đưa ra các ngoại lệ trừ khi chúng truy cập vào các tài nguyên bên ngoài hoặc phân tích cú pháp chuỗi hoặc một cái gì đó. Dù sao trong trường hợp xấu nhất, hãy làm theo kiểu TryMethod (như TryParse ()) để đóng gói logic ngoại lệ và làm cho luồng điều khiển của bạn có thể duy trì và sạch sẽ:

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

Một biến thể khác sử dụng mẫu Thử và danh sách kết hợp các phương pháp thay vì lồng nhau nếu:

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;

và nếu foreach phức tạp một chút, bạn có thể làm cho nó đơn giản:

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

Thành thật mà nói, tôi nghĩ điều này chỉ gây ra những sự trừu tượng không cần thiết. Đây không phải là một giải pháp khủng khiếp, nhưng tôi sẽ không sử dụng nó cho hầu hết các trường hợp.
user606723

Nếu bạn không quan tâm đến kiểu ngoại lệ, và bạn chỉ muốn xử lý mã có điều kiện .. do đó, về mặt trừu tượng và khả năng bảo trì chắc chắn tốt hơn là chuyển đổi nó thành một phương thức có điều kiện với trả về liệu nó có thành công hay không, theo cách này bạn ẩn cú pháp xử lý ngoại lệ lộn xộn với một phương thức mô tả rõ ràng .. sau đó mã của bạn sẽ xử lý nó vì nó là một phương thức có điều kiện thông thường.
Mohamed Abed

Tôi biết các điểm, và chúng có giá trị. Tuy nhiên, khi sử dụng kiểu trừu tượng này (che giấu sự lộn xộn / phức tạp) hầu như ở mọi nơi, nó trở nên vô lý và việc hiểu một phần mềm để làm gì trở nên khó khăn hơn nhiều. Như tôi đã nói, nó không phải là một giải pháp khủng khiếp, nhưng tôi sẽ không sử dụng nó một cách nhẹ nhàng.
user606723

9

Tạo một danh sách các đại diện cho các hàm tính toán của bạn và sau đó có một vòng lặp while để chuyển qua chúng:

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?

Điều đó sẽ cung cấp cho bạn một ý tưởng chung về cách thực hiện (tức là nó không phải là một giải pháp chính xác).


1
Tất nhiên, ngoại trừ thực tế là bạn thường không bao giờ nên bắt Exception. ;)
DeCaf

@DeCaf như tôi đã nói: rằng "sẽ cung cấp cho bạn một ý tưởng chung về cách thực hiện (tức là không phải là một giải pháp chính xác)." Vì vậy, OP có thể bắt bất cứ điều gì ngoại lệ thích hợp xảy ra là ... không cần phải bắt chung Exception.
Kiril

Vâng, xin lỗi, tôi cảm thấy cần phải làm điều đó ra khỏi đó.
DeCaf

1
@DeCaf, đó là lời giải thích hợp lệ cho những người có thể không quen thuộc với các phương pháp hay nhất. Cảm ơn :)
Kiril.

9

Đây có vẻ như là một công việc cho ... MONADS! Cụ thể là đơn nguyên Có thể. Bắt đầu với đơn nguyên Có thể như được mô tả ở đây . Sau đó, thêm một số phương thức mở rộng. Tôi đã viết các phương pháp mở rộng này cụ thể cho sự cố như bạn đã mô tả. Điều tốt đẹp về monads là bạn có thể viết các phương thức mở rộng chính xác cần thiết cho tình huống của bạn.

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

Sử dụng nó như vậy:

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

Nếu bạn thấy mình thường xuyên thực hiện những loại tính toán này, thì đơn nguyên có thể nên giảm số lượng mã soạn sẵn bạn phải viết trong khi tăng khả năng đọc mã của bạn.


2
Tôi thích giải pháp này. Tuy nhiên, nó khá mờ đối với những ai chưa từng tiếp xúc với monads, điều này có nghĩa là điều này khác xa với thành ngữ trong c #. Tôi sẽ không muốn một trong những đồng nghiệp của mình học các monads chỉ để sửa đổi một đoạn mã ngớ ngẩn này trong tương lai. Tuy nhiên, điều này rất tốt để tham khảo trong tương lai.
jjoelson

1
+1 cho óc hài hước, để viết giải pháp ngắn gọn và dài dòng nhất có thể cho vấn đề này và sau đó nói rằng nó sẽ "giảm lượng mã soạn sẵn bạn phải viết trong khi tăng khả năng đọc mã của bạn".
Nate CK

1
Này, chúng tôi không phàn nàn về số lượng lớn mã ẩn trong System.Linq, và vui vẻ sử dụng những monads đó cả ngày. Tôi nghĩ @ fre0n chỉ có nghĩa là nếu bạn sẵn sàng đưa đơn nguyên Có thể vào bộ công cụ của mình, thì những loại đánh giá theo chuỗi này sẽ trở nên dễ dàng hơn để xem xét và lập luận. Có một số sự cố dễ dàng mắc phải.
Sebastian Good

Chỉ vì nó sử dụng Maybekhông làm cho đây là một giải pháp đơn lẻ; nó không sử dụng các thuộc tính đơn nguyên của Maybevà cũng có thể chỉ sử dụng null. Bên cạnh đó, được sử dụng "monadicly" đây sẽ là nghịch đảo của Maybe. Một giải pháp đơn nguyên thực sự sẽ phải sử dụng đơn nguyên Trạng thái giữ giá trị không đặc biệt đầu tiên làm trạng thái của nó, nhưng điều đó sẽ quá mức cần thiết khi đánh giá chuỗi thông thường hoạt động.
Dax Fohl

7

Một phiên bản khác của phương pháp thử phương pháp. Điều này cho phép các ngoại lệ đã nhập, vì có một loại ngoại lệ cho mỗi phép tính:

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

&&Thay vào đó, bạn có thể tránh các if lồng nhau bằng cách sử dụng giữa mỗi điều kiện.
DeCaf

4

Trong Perl, bạn có thể làm foo() or bar(), điều này sẽ thực thi bar()nếu foo()không thành công. Trong C #, chúng ta không thấy cấu trúc "if fail, then" này, nhưng có một toán tử mà chúng ta có thể sử dụng cho mục đích này: toán tử null-thanesce?? , chỉ tiếp tục nếu phần đầu tiên là null.

Nếu bạn có thể thay đổi chữ ký của các phép tính của mình và nếu bạn bọc các ngoại lệ của chúng (như được hiển thị trong các bài viết trước) hoặc viết lại chúng để trả về nullthay thế, chuỗi mã của bạn ngày càng trở nên ngắn gọn và vẫn dễ đọc:

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

Tôi đã sử dụng các thay thế sau cho các hàm của bạn, dẫn đến giá trị 40.40bằng 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! */}

Tôi nhận thấy rằng giải pháp này không phải lúc nào cũng có thể áp dụng được, nhưng bạn đã đặt ra một câu hỏi rất thú vị và tôi tin rằng, mặc dù chủ đề tương đối cũ, rằng đây là một mô hình đáng xem xét khi bạn có thể sửa đổi.


1
Tôi chỉ muốn nói lời cảm ơn". Tôi đã cố gắng triển khai những gì bạn đang nói . Tôi hy vọng rằng tôi đã hiểu nó một cách chính xác.
AlexMelw

3

Cho rằng các phương thức tính toán có cùng một chữ ký không tham số, bạn có thể đăng ký chúng trong một danh sách, và lặp qua danh sách đó và thực thi các phương thức. Có lẽ sẽ tốt hơn cho bạn khi sử dụng Func<double>nghĩa là "một hàm trả về kết quả kiểu 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

Bạn có thể sử dụng Task / ContinueWith và kiểm tra ngoại lệ. Đây là một phương pháp mở rộng hay để giúp làm cho nó đẹp hơn:

    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

Nếu loại ngoại lệ thực sự được đưa ra không quan trọng, bạn chỉ có thể sử dụng khối bắt không đánh máy:

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

Điều đó không phải luôn luôn gọi mọi hàm trong danh sách? Có thể muốn ném (chơi chữ không có ý định) vào một cái gì đó như if(succeeded) { break; }post-catch.
một 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;
        }
    }
}

Và sử dụng nó như vậy:

using Utility;

...

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

1

Có vẻ như ý định của OP là tìm ra một mô hình tốt để giải quyết vấn đề của anh ấy và giải quyết vấn đề hiện tại mà anh ấy đang phải vật lộn vào thời điểm đó.

OP: "Tôi có thể gói mỗi phép tính trong một phương thức trợ giúp trả về null khi không thành công, và sau đó chỉ cần sử dụng ??toán tử, nhưng có cách nào để thực hiện điều này tổng quát hơn không (nghĩa là không cần phải viết phương thức trợ giúp cho mỗi phương thức mà tôi muốn use)? Tôi đã nghĩ về việc viết một phương thức tĩnh bằng cách sử dụng generic bao bọc bất kỳ phương thức nào đã cho trong một lần thử / bắt và trả về null khi thất bại, nhưng tôi không chắc mình sẽ làm như thế nào về điều này. Có ý kiến ​​gì không? "

Tôi đã thấy rất nhiều mẫu hay để tránh các khối try catch lồng nhau , được đăng trong nguồn cấp dữ liệu này, nhưng không tìm thấy giải pháp cho vấn đề được trích dẫn ở trên. Vì vậy, đây là giải pháp:

Như OP đã đề cập ở trên, anh ấy muốn tạo một đối tượng wrapper sẽ trả về nullkhi bị lỗi . Tôi sẽ gọi nó là một pod ( Vỏ an toàn cho ngoại lệ ).

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;

Nhưng điều gì sẽ xảy ra nếu bạn muốn tạo một nhóm an toàn cho kết quả Kiểu Tham chiếu được trả về bởi các hàm / phương thức 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}";
}

Vì vậy, bạn có thể nhận thấy rằng không cần "viết phương thức trợ giúp cho mỗi phương thức bạn muốn sử dụng" .

Các hai loại vỏ (ví ValueTypeResults và ReferenceTypeResults) là đủ .


Đây là mã của SafePod. Tuy nhiên, nó không phải là một thùng chứa. Thay vào đó, nó tạo một trình bao bọc đại biểu an toàn ngoại lệ cho cả ValueTypeResults và 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;
    }
}

Đó là cách bạn có thể tận dụng toán tử ??kết hợp null kết hợp với sức mạnh của ( các) thực thể công dân hạng nhấtdelegate .


0

Bạn nói đúng về việc gói từng phép tính nhưng bạn nên gói theo nguyên tắc nói-không-hỏi.

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

Các hoạt động rất đơn giản và có thể thay đổi hành vi của chúng một cách độc lập. Và không quan trọng tại sao họ vỡ nợ. Để chứng minh rằng bạn có thể triển khai calc1DefaultsToCalc2 như:

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

-1

Có vẻ như các phép tính của bạn có nhiều thông tin hợp lệ hơn để trả về hơn là chỉ bản thân phép tính. Có lẽ sẽ có ý nghĩa hơn đối với họ khi tự xử lý ngoại lệ và trả về một lớp "kết quả" chứa thông tin lỗi, thông tin giá trị, v.v. Hãy nghĩ như lớp AsyncResult thực hiện theo mẫu không đồng bộ. Sau đó, bạn có thể đánh giá kết quả thực của phép tính. Bạn có thể hợp lý hóa điều này bằng cách nghĩ rằng nếu một phép tính không thành công, điều đó cũng mang tính thông tin như thể nó sẽ trôi qua. Do đó, một ngoại lệ là một phần thông tin, không phải là một "lỗi".

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

...

Tất nhiên, tôi đang tự hỏi nhiều hơn về kiến ​​trúc tổng thể mà bạn đang sử dụng để thực hiện các tính toán này.


Điều này là ổn - tương tự như những gì OP đã đề xuất khi bao gồm các tính toán. Tôi chỉ muốn một cái gì đó giống như while (!calcResult.HasValue) nextCalcResult(), thay vì một danh sách các Calc1, Calc2, Calc3, vv
Kirk Broadhurst

-3

Còn về việc theo dõi các hành động bạn đang làm ...

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
Làm thế nào để giúp đỡ? nếu calc1 () không thành công, cals2 sẽ không bao giờ được thực thi!
DeCaf

điều này không giải quyết được vấn đề. Chỉ thực thi calc1 nếu calc2 không thành công, chỉ thực thi calc3 nếu calc1 && calc2 không thành công.
Jason

+1 quả cầu. Đây là những gì tôi làm. Tôi chỉ phải viết mã một lần bắt, thông báo được gửi cho tôi ( tracktrong trường hợp này) và tôi biết chính xác phân đoạn nào trong mã của mình đã khiến khối bị lỗi. Có lẽ bạn nên nói với các thành viên như DeCaf rằng trackthông báo được gửi đến quy trình xử lý lỗi tùy chỉnh cho phép bạn gỡ lỗi mã của mình. Có vẻ như anh ấy không hiểu logic của bạn.
jp2code

Vâng, @DeCaf là đúng, đoạn mã của tôi không giữ thực hiện chức năng tiếp theo đó là những gì jjoelson yêu cầu, có cho giải pháp của tôi không phải là khả thi
Orn Kristjánsson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.