Pola untuk menghindari blok coba tangkap bersarang?

114

Pertimbangkan situasi di mana saya memiliki tiga (atau lebih) cara untuk melakukan penghitungan, yang masing-masing dapat gagal dengan pengecualian. Untuk mencoba setiap perhitungan hingga kami menemukan yang berhasil, saya telah melakukan hal berikut:

double val;

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

Apakah ada pola yang diterima yang mencapai ini dengan cara yang lebih baik? Tentu saja saya bisa membungkus setiap kalkulasi dalam metode helper yang mengembalikan nol pada kegagalan, dan kemudian hanya menggunakan ??operator, tetapi adakah cara untuk melakukan ini secara lebih umum (yaitu tanpa harus menulis metode pembantu untuk setiap metode yang ingin saya gunakan )? Saya telah berpikir tentang menulis metode statis menggunakan obat generik yang membungkus metode tertentu dalam percobaan / penangkapan dan mengembalikan nol pada kegagalan, tetapi saya tidak yakin bagaimana saya akan melakukannya. Ada ide?

jjoelson
sumber
Bisakah Anda memasukkan beberapa detail tentang penghitungan?
James Johnson
2
Mereka pada dasarnya hanya metode yang berbeda untuk menyelesaikan / mendekati PDE. Mereka dari pustaka pihak ketiga, jadi saya tidak bisa mengubahnya untuk mengembalikan kode kesalahan atau null. Yang terbaik yang bisa saya lakukan adalah membungkus masing-masing satu per satu dengan sebuah metode.
jjoelson
Apakah metode kalk merupakan bagian dari proyek Anda (bukan pustaka pihak ketiga)? Jika demikian, Anda bisa mengeluarkan logika yang melempar pengecualian dan menggunakannya untuk memutuskan metode kalk mana yang perlu dipanggil.
Chris
1
Ada kasus penggunaan lain untuk ini yang saya temui di Java - saya perlu mengurai a Stringke Datepenggunaan SimpleDateFormat.parsedan saya perlu mencoba beberapa format yang berbeda secara berurutan, pindah ke berikutnya ketika seseorang melempar pengecualian.
Variabel Miserable

Jawaban:

125

Sejauh mungkin, jangan gunakan pengecualian untuk aliran kontrol atau keadaan yang tidak wajar.

Tetapi untuk menjawab pertanyaan Anda secara langsung (dengan asumsi semua tipe pengecualian adalah sama):

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

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

throw new NoCalcsWorkedException();
Ani
sumber
15
Ini mengasumsikan bahwa Calc1Exception,, Calc2Exceptiondan Calc3Exceptionberbagi kelas dasar yang sama.
Wyzard
3
Di atas, dia mengasumsikan tanda tangan umum - yang sebenarnya tidak terlalu jauh. Jawaban yang bagus.
TomTom
1
Juga, saya menambahkan continuedi blok tangkap dan breaksetelah blok tangkap sehingga loop berakhir ketika perhitungan berhasil (terima kasih untuk Lirik untuk bit ini)
jjoelson
6
+1 hanya karena dikatakan "Jangan gunakan pengecualian untuk aliran kontrol" meskipun saya akan menggunakan "TIDAK PERNAH PERNAH" daripada "Sejauh mungkin".
Bill K
1
@jjoelson: breakPernyataan berikut calc();dalam try(dan tidak ada continuepernyataan sama sekali) mungkin sedikit lebih idiomatis.
Adam Robinson
38

Hanya untuk menawarkan alternatif "di luar kotak", bagaimana dengan fungsi rekursif ...

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

CATATAN: Saya sama sekali tidak mengatakan bahwa ini adalah cara terbaik untuk menyelesaikan pekerjaan, hanya dengan cara yang berbeda

musefan
sumber
6
Saya harus mengimplementasikan "On Error Resume Next" dalam bahasa sekali, dan kode yang saya buat terlihat sangat mirip ini.
Jacob Krall
4
Harap jangan pernah menggunakan pernyataan switch untuk membuat perulangan for.
Jeff Ferland
3
Tidak dapat dipertahankan untuk memiliki pernyataan sakelar untuk perulangan
Mohamed Abed
1
Saya tahu jawaban saya bukanlah kode yang paling efisien, tetapi sekali lagi menggunakan blok coba / tangkap untuk hal semacam ini bukanlah cara terbaik untuk melakukannya. Sayangnya, OP menggunakan perpustakaan pihak ke-3 dan harus melakukan yang terbaik untuk memastikan keberhasilan. Idealnya, masukan dapat dengan divalidasi terlebih dahulu dan fungsi kalkulasi yang benar dipilih untuk memastikan tidak akan gagal - tentu saja, Anda dapat memasukkan semua itu ke dalam percobaan / tangkap agar aman;)
musefan
1
return DoCalc(c++)setara dengan return DoCalc(c)- nilai kenaikan pasca tidak akan diteruskan lebih dalam. Untuk membuatnya bekerja (dan memperkenalkan lebih banyak ketidakjelasan) bisa jadi lebih seperti return DoCalc((c++,c)).
Artur Czajka
37

Anda dapat meratakan sarang dengan menerapkan metode seperti ini:

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

Tapi saya menduga masalah desain sebenarnya adalah adanya tiga metode berbeda yang pada dasarnya melakukan hal yang sama (dari perspektif pemanggil) tetapi memberikan pengecualian yang berbeda dan tidak terkait.

Ini mengasumsikan tiga pengecualian tidak terkait. Jika mereka semua memiliki kelas dasar yang sama, akan lebih baik menggunakan loop dengan satu blok tangkapan, seperti yang disarankan Ani.

Wyzard
sumber
1
+1: Ini adalah solusi paling bersih dan paling tidak masuk akal untuk masalah ini. Solusi lain yang saya lihat di sini hanya mencoba menjadi imut, IMO. Seperti yang dikatakan OP, dia tidak menulis API jadi dia terjebak dengan pengecualian yang dilempar.
Nate CK
19

Cobalah untuk tidak mengontrol logika berdasarkan pengecualian; perhatikan juga bahwa pengecualian harus diberikan hanya dalam kasus luar biasa. Perhitungan dalam banyak kasus tidak boleh menampilkan pengecualian kecuali mereka mengakses sumber daya eksternal atau mengurai string atau sesuatu. Dalam kasus terburuk, ikuti gaya TryMethod (seperti TryParse ()) untuk merangkum logika pengecualian dan membuat aliran kontrol Anda dapat dipelihara dan bersih:

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

Variasi lain yang memanfaatkan pola Try dan menggabungkan daftar metode alih-alih bersarang jika:

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;

dan jika foreachnya sedikit rumit, Anda dapat membuatnya jelas:

        foreach (var tryCalc in tryCalcs)
        {
            if (tryCalc(out calcOutput)) break;
        }
Mohamed Abed
sumber
Sejujurnya, saya pikir ini hanya menyebabkan abstraksi yang tidak perlu. Ini bukan solusi yang buruk, tetapi saya tidak akan menggunakan ini untuk banyak kasus.
pengguna606723
Jika Anda tidak peduli dengan jenis eksepsi, dan Anda hanya ingin menangani kode bersyarat .. maka lebih baik dalam hal abstraksi dan pemeliharaan untuk mengubahnya menjadi metode bersyarat dengan mengembalikan apakah berhasil atau tidak, cara ini Anda menyembunyikan pengecualian penanganan sintaks yang berantakan dengan metode deskriptif yang jelas .. maka kode Anda akan menanganinya karena ini adalah metode bersyarat biasa.
Mohamed Abed
Saya tahu poinnya, dan itu valid. Namun, ketika menggunakan jenis abstraksi ini (menyembunyikan kekacauan / kerumitan) hampir di semua tempat, hal itu menjadi konyol dan memahami perangkat lunak apa adanya menjadi jauh lebih sulit. Seperti yang saya katakan, ini bukan solusi yang buruk, tetapi saya tidak akan menggunakannya dengan mudah.
pengguna606723
9

Buat daftar delegasi ke fungsi kalkulasi Anda dan kemudian miliki waktu berputar untuk melewatinya:

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?

Itu akan memberi Anda gambaran umum tentang bagaimana melakukannya (yaitu, ini bukan solusi yang tepat).

Kiril
sumber
1
Kecuali tentu saja fakta bahwa Anda biasanya tidak boleh menangkap Exceptionsaat itu. ;)
Tuli
@ DeCaf seperti yang saya katakan: bahwa "seharusnya memberi Anda gambaran umum tentang bagaimana melakukannya (yaitu bukan solusi yang tepat)." Jadi OP dapat menangkap pengecualian yang sesuai yang terjadi ... tidak perlu menangkap generik Exception.
Kiril
Ya, maaf, hanya merasa perlu mengeluarkannya di sana.
DeCaf
1
@DeCaf, ini adalah klarifikasi yang valid bagi mereka yang mungkin belum begitu memahami praktik terbaik. Terima kasih :)
Kiril
9

Ini terlihat seperti pekerjaan untuk ... MONADS! Secara khusus, monad Maybe. Mulailah dengan monad Maybe seperti yang dijelaskan di sini . Kemudian tambahkan beberapa metode ekstensi. Saya menulis metode ekstensi ini khusus untuk masalah seperti yang Anda gambarkan. Hal yang menyenangkan tentang monad adalah Anda dapat menulis metode ekstensi yang tepat yang diperlukan untuk situasi Anda.

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

Gunakan seperti ini:

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

Jika Anda sering melakukan kalkulasi semacam ini, mungkin monad harus mengurangi jumlah kode boilerplate yang harus Anda tulis sambil meningkatkan keterbacaan kode Anda.

fre0n
sumber
2
Saya suka solusi ini. Namun, ini cukup buram bagi siapa pun yang sebelumnya tidak pernah terpapar monad, yang berarti ini sangat jauh dari idiomatik di c #. Saya tidak ingin salah satu rekan kerja saya belajar monad hanya untuk memodifikasi satu kode konyol ini di masa depan. Ini bagus untuk referensi di masa mendatang.
jjoelson
1
+1 untuk selera humor, untuk menulis solusi paling tumpul dan bertele-tele untuk masalah ini dan kemudian mengatakan bahwa itu akan "mengurangi jumlah kode boilerplate yang harus Anda tulis sambil meningkatkan keterbacaan kode Anda".
Nate CK
1
Hei, kami tidak mengeluh tentang banyaknya kode yang bersembunyi di System.Linq, dan dengan senang hati menggunakan monad itu sepanjang hari. Menurut saya @ fre0n hanya berarti bahwa jika Anda ingin meletakkan monad Maybe di toolkit Anda, evaluasi berantai semacam ini menjadi lebih mudah untuk dilihat dan dipertimbangkan. Ada beberapa implemntasi yang mudah diraih.
Sebastian Good
Hanya karena penggunaannya Maybetidak menjadikan ini solusi monadik; itu menggunakan nol dari sifat monadik Maybedan mungkin juga hanya menggunakan null. Selain itu, digunakan "secara monadik" ini akan menjadi kebalikan dari Maybe. Solusi monad nyata harus menggunakan monad Status yang mempertahankan nilai non-pengecualian pertama sebagai statusnya, tetapi itu akan berlebihan ketika evaluasi berantai normal berfungsi.
Dax Fohl
7

Versi lain dari pendekatan metode coba . Yang ini memungkinkan pengecualian yang diketik, karena ada jenis pengecualian untuk setiap perhitungan:

    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();
    }
Stefan
sumber
Anda sebenarnya dapat menghindari if yang bertingkat dengan menggunakan di &&antara setiap kondisi sebagai gantinya.
DeCaf
4

Di Perl Anda bisa melakukannya foo() or bar(), yang akan dijalankan bar()jika foo()gagal. Dalam C # kita tidak melihat konstruksi "jika gagal, maka" ini, tetapi ada operator yang dapat kita gunakan untuk tujuan ini: operator penggabungan-nol ??, yang berlanjut hanya jika bagian pertama adalah nol.

Jika Anda dapat mengubah tanda tangan perhitungan Anda dan jika Anda membungkus pengecualian mereka (seperti yang ditunjukkan di posting sebelumnya) atau menulis ulang nullsebagai gantinya untuk mengembalikan , rantai kode Anda menjadi semakin singkat dan masih mudah dibaca:

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

Saya menggunakan penggantian berikut untuk fungsi Anda, yang menghasilkan nilai 40.40di 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! */}

Saya menyadari bahwa solusi ini tidak selalu dapat diterapkan, tetapi Anda mengajukan pertanyaan yang sangat menarik dan saya yakin, meskipun utasnya relatif lama, bahwa ini adalah pola yang patut dipertimbangkan ketika Anda dapat memperbaikinya.

Abel
sumber
1
Saya hanya ingin mengucapkan "Terima kasih". Saya mencoba menerapkan apa yang Anda bicarakan . Saya harap saya memahaminya dengan benar.
AlexMelw
3

Mengingat bahwa metode kalkulasi memiliki tanda tangan tanpa parameter yang sama, Anda dapat mendaftarkannya dalam daftar, dan melakukan iterasi melalui daftar tersebut dan menjalankan metode. Mungkin akan lebih baik bagi Anda untuk menggunakan Func<double>arti "fungsi yang mengembalikan hasil tipe 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);
   }
}
Marcin Seredynski
sumber
3

Anda dapat menggunakan Task / ContinueWith, dan periksa pengecualiannya. Berikut metode ekstensi yang bagus untuk membantu membuatnya cantik:

    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);
    }
}
Dax Fohl
sumber
1

Jika tipe aktual dari pengecualian yang dilempar tidak menjadi masalah, Anda bisa menggunakan blok catch tanpa tipe:

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();
Jacob Krall
sumber
Bukankah itu selalu memanggil setiap fungsi dalam daftar? Mungkin ingin melempar (permainan kata tidak dimaksudkan) dalam sesuatu seperti if(succeeded) { break; }pasca-tangkapan.
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;
        }
    }
}

Dan gunakan seperti itu:

using Utility;

...

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

Sepertinya niat OP adalah mencari pola yang baik untuk menyelesaikan masalahnya dan menyelesaikan masalah yang saat ini sedang ia geluti.

OP: "Saya bisa membungkus setiap kalkulasi dalam metode helper yang mengembalikan nol pada kegagalan, dan kemudian hanya menggunakan ??operator, tetapi adakah cara untuk melakukan ini secara lebih umum (yaitu tanpa harus menulis metode pembantu untuk setiap metode yang saya inginkan menggunakan)? Saya telah berpikir tentang menulis metode statis menggunakan obat generik yang membungkus metode tertentu dalam mencoba / menangkap dan mengembalikan nol pada kegagalan, tetapi saya tidak yakin bagaimana saya akan melakukannya. Ada ide? "

Saya melihat banyak pola bagus yang menghindari blok tangkap percobaan bersarang , yang diposting di umpan ini, tetapi tidak menemukan solusi untuk masalah yang dikutip di atas. Jadi, inilah solusinya:

Seperti yang disebutkan OP di atas, dia ingin membuat objek pembungkus yang mengembalikan nullkegagalan . Saya akan menyebutnya pod ( Pod yang aman untuk pengecualian ).

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;

Tetapi bagaimana jika Anda ingin membuat pod yang aman untuk hasil Jenis Referensi yang dikembalikan oleh fungsi / metode 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}";
}

Jadi, Anda mungkin memperhatikan bahwa tidak perlu "menulis metode pembantu untuk setiap metode yang ingin Anda gunakan" .

The dua jenis polong (untuk ValueTypeResults dan ReferenceTypeResults) yang cukup .


Ini kodenya SafePod. Ini bukan wadah. Sebaliknya, ini membuat pembungkus delegasi yang aman untuk pengecualian untuk ValueTypeResults dan 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;
    }
}

Begitulah cara Anda dapat memanfaatkan operator penggabungan nol yang ??dikombinasikan dengan kekuatan entitas warga negara kelas satudelegate .

AlexMelw
sumber
0

Anda benar tentang membungkus setiap kalkulasi tetapi Anda harus membungkusnya sesuai dengan prinsip kirim-jangan-tanya.

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

Pengoperasiannya sederhana, dan dapat mengubah perilakunya secara mandiri. Dan tidak masalah mengapa mereka default. Sebagai buktinya, Anda dapat mengimplementasikan calc1DefaultingToCalc2 sebagai:

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

Sepertinya kalkulasi Anda memiliki informasi yang lebih valid untuk dikembalikan daripada hanya kalkulasi itu sendiri. Mungkin akan lebih masuk akal bagi mereka untuk melakukan penanganan pengecualian mereka sendiri dan mengembalikan kelas "hasil" yang berisi informasi kesalahan, informasi nilai, dll. Pikirkan seperti kelas AsyncResult mengikuti pola async. Anda kemudian dapat mengevaluasi hasil penghitungan yang sebenarnya. Anda dapat merasionalisasi ini dengan berpikir bahwa jika penghitungan gagal, itu sama informasinya seperti jika berhasil. Oleh karena itu, pengecualian adalah sepotong informasi, bukan "kesalahan".

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

...

Tentu saja, saya ingin tahu lebih banyak tentang keseluruhan arsitektur yang Anda gunakan untuk menyelesaikan kalkulasi ini.

Emoire
sumber
Ini tidak masalah - mirip dengan apa yang disarankan OP sejauh menyelesaikan kalkulasi. Saya lebih suka sesuatu seperti while (!calcResult.HasValue) nextCalcResult(), daripada daftar Calc1, Calc2, Calc3 dll.
Kirk Broadhurst
-3

Bagaimana dengan melacak tindakan yang Anda lakukan ...

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 );
}
Orn Kristjansson
sumber
4
Bagaimana ini membantu? jika calc1 () gagal cals2 tidak akan pernah dijalankan!
DeCaf
ini tidak menyelesaikan masalah. Hanya jalankan calc1 jika calc2 gagal, hanya jalankan calc3 jika calc1 && calc2 gagal.
Jason
+1 orn. Inilah yang saya lakukan. Saya hanya perlu membuat kode satu tangkapan, pesan dikirim kepada saya ( trackdalam kasus ini), dan saya tahu persis segmen mana dalam kode saya yang menyebabkan blok gagal. Mungkin Anda harus menjelaskan lebih lanjut untuk memberi tahu anggota seperti DeCaf bahwa trackpesan tersebut dikirim ke rutinitas penanganan kesalahan kustom Anda yang memungkinkan Anda men-debug kode Anda. Kedengarannya dia tidak mengerti logika Anda.
jp2code
Ya, @DeCaf benar, segmen kode saya tidak terus menjalankan fungsi berikutnya yang diminta oleh jjoelson, karena solusi saya tidak dapat dilakukan
Orn Kristjansson