नेस्टेड से बचने के लिए पैटर्न पकड़ ब्लॉक की कोशिश करें?


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
वे मूल रूप से एक पीडीई को हल करने / सन्निकट करने के केवल विभिन्न तरीके हैं। वे तीसरी पार्टी की लाइब्रेरी से हैं, इसलिए मैं त्रुटि कोड या अशक्त लौटाने के लिए उन्हें बदल नहीं सकता। सबसे अच्छा मैं कर सकता है एक विधि में प्रत्येक को व्यक्तिगत रूप से लपेटो।
जोजेल्सन

क्या कैल्क विधियां आपकी परियोजना का हिस्सा हैं (तीसरे पक्ष के पुस्तकालय के बजाय)? यदि ऐसा है, तो आप उस तर्क को बाहर निकाल सकते हैं जो अपवादों को फेंकता है और यह तय करने के लिए उपयोग करता है कि किस कैल्क विधि को बुलाया जाना चाहिए।
क्रिस

1
इसके लिए एक और उपयोग-मामला है कि मैं जावा में आया हूं - मुझे 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एक सामान्य आधार वर्ग साझा करता है।
वायजार्ड

3
शीर्ष पर, वह एक सामान्य हस्ताक्षर मानता है - जो वास्तव में दूर नहीं है। अच्छा उत्तर देने वाला।
टॉमटॉम

1
इसके अलावा, मैंने continueकैच ब्लॉक में और कैच ब्लॉक के breakबाद जोड़ा ताकि लूप समाप्त हो जाए जब एक गणना काम करती है (इस बिट के लिए
लिरिक

6
केवल +1 क्योंकि यह कहता है कि "नियंत्रण प्रवाह के अपवादों का उपयोग न करें" हालांकि मैंने "जहाँ तक संभव हो" के बजाय "कभी भी" का उपयोग नहीं किया होगा।
बिल के

1
@jjoelson: (और कोई बयान नहीं ) के भीतर एक breakबयान थोड़ा और अधिक मुहावरेदार हो सकता है। calc();trycontinue
एडम रॉबिन्सन

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
मुझे एक बार एक भाषा में "ऑन एरर रिज्यूमे नेक्स्ट" को लागू करना था, और मैंने जो कोड बनाया, वह इस तरह से बहुत कुछ दिखता था।
याकूब क्राल

4
लूप बनाने के लिए कृपया कभी भी स्विच स्टेटमेंट का उपयोग न करें।
जेफ फेरलैंड

3
लूपिंग के लिए स्विच स्टेटमेंट रखना संभव नहीं है
मोहम्मद एबेड

1
मुझे पता है कि मेरा जवाब सबसे कुशल कोड नहीं है, लेकिन फिर इस तरह की चीज़ों के लिए फिर से कोशिश / पकड़ ब्लॉक का उपयोग करना वैसे भी जाने का सबसे अच्छा तरीका नहीं है। दुर्भाग्य से, ओपी एक 3 पार्टी लाइब्रेरी का उपयोग कर रहा है और सफलता सुनिश्चित करने के लिए उन्हें सर्वश्रेष्ठ करने की आवश्यकता है। आदर्श रूप से, इनपुट पहले मान्य किया जा सकता है और यह सुनिश्चित करने के लिए चुना गया कि सही गणना फ़ंक्शन विफल हो जाएगा - निश्चित रूप से, आप सभी को सुरक्षित रखने की कोशिश / पकड़ में डाल सकते हैं;)
म्यूज़फ़ान

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: यह समस्या का सबसे साफ-सुथरा समाधान है। अन्य समाधान जो मैं यहां देख रहा हूं, वे सिर्फ प्यारा होने की कोशिश कर रहे हैं, आईएमओ। जैसा कि ओपी ने कहा, उसने एपीआई नहीं लिखा है इसलिए वह अपवादों के साथ फंस गया है जो फेंक दिए गए हैं।
नैट सीके

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

ईमानदारी से, मुझे लगता है कि यह सिर्फ अनावश्यक सार का कारण बनता है। यह एक भयानक समाधान नहीं है, लेकिन मैं ज्यादातर मामलों के लिए इसका इस्तेमाल नहीं करूंगा।
user606723

यदि आप अपवाद प्रकार के बारे में परवाह नहीं करते हैं, और आप बस सशर्त कोड को संभालना चाहते हैं .. तो यह निश्चित रूप से अमूर्त और रखरखाव की दृष्टि से बेहतर है कि इसे वापसी के साथ सशर्त विधि में परिवर्तित किया जाए या नहीं, यह सफल रहा है या नहीं, इस तरह से आप एक स्पष्ट वर्णनात्मक विधि के साथ गन्दा सिंटैक्स को छोड़कर अपवाद को छिपाते हैं .. तो आपका कोड इसे संभाल लेगा क्योंकि यह एक नियमित सशर्त विधि है।
मोहम्मद अबेद

मुझे अंक पता हैं, और वे मान्य हैं। हालांकि, इस प्रकार के अमूर्त (गन्दापन / जटिलता को छिपाना) का उपयोग करते समय लगभग हर जगह यह हास्यास्पद हो जाता है और इसके लिए सॉफ्टवेयर के एक टुकड़े को समझना बहुत मुश्किल हो जाता है। जैसा कि मैंने कहा, यह एक भयानक समाधान नहीं है, लेकिन मैं इसे हल्के ढंग से उपयोग नहीं करूंगा।
user606723

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 जैसा कि मैंने कहा: कि "आपको यह कैसे करना है (यानी एक सटीक समाधान नहीं) का एक सामान्य विचार देना चाहिए।" इसलिए ओपी जो भी उपयुक्त अपवाद होता है उसे पकड़ सकता है ... जेनेरिक को पकड़ने की कोई आवश्यकता नहीं है Exception
किरिल

हाँ, क्षमा करें, बस वहाँ से बाहर निकलने की आवश्यकता महसूस हुई।
डेकाफ

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 # में मुहावरे से बहुत दूर है। मैं नहीं चाहूंगा कि मेरा एक सहकर्मी भविष्य में कोड के इस एक मूर्खतापूर्ण टुकड़े को संशोधित करने के लिए सिर्फ मोनाडेस सीखे। यह भविष्य के संदर्भ के लिए बहुत अच्छा है, हालांकि।
जोजेलसन

1
+1 ऑफ़ ह्यूमर, इस समस्या के लिए संभव सबसे संभावित और क्रियात्मक समाधान लिखने के लिए और फिर यह कहना कि यह "आपके कोड की पठनीयता को बढ़ाते हुए आपको लिखे गए बॉयलरप्लेट कोड की मात्रा को कम करेगा"।
नैट सीके

1
अरे, हम System.Linq में भारी मात्रा में कोड लर्किंग के बारे में शिकायत नहीं करते हैं, और पूरे दिन उन मठों का खुशी से उपयोग करते हैं। मुझे लगता है कि @ fre0n का मतलब सिर्फ इतना है कि यदि आप अपने टूलकिट में हो सकता है कि मोनड को डालने के लिए तैयार हैं, तो इस प्रकार के जंजीर मूल्यांकन को देखने और इसके बारे में तर्क करना आसान हो जाता है। ऐसे कई उपकरण हैं जो आसानी से पकड़ सकते हैं।
सेबेस्टियन गुड

सिर्फ इसलिए कि यह उपयोग Maybeकरता है यह एक विवादास्पद समाधान नहीं बनाता है; यह के monadic गुणों के शून्य का उपयोग करता है 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 से बच सकते हैं ।
डेकाफ

4

पर्ल में आप कर सकते हैं foo() or bar(), जो निष्पादित करेंगे bar()अगर foo()विफल रहता है। C # में हम इसे "विफल होने पर नहीं देखते हैं, तो" निर्माण करते हैं, लेकिन एक ऑपरेटर है जिसे हम इस उद्देश्य के लिए उपयोग कर सकते हैं: null-coalesce ऑपरेटर ??, जो केवल पहले भाग के अशक्त होने पर भी जारी रहता है।

यदि आप अपनी गणना के हस्ताक्षर को बदल सकते हैं और यदि आप या तो उनके अपवादों को लपेटते हैं (जैसा कि पिछले पोस्टों में दिखाया गया है) या फिर से लिखने के लिए उन्हें फिर से लिखें 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
मैं बस आपको धन्यवाद कहना चाहता हूँ"। आप जिस बारे में बात कर रहे थे मैंने उसे लागू करने की कोशिश की । मुझे उम्मीद है कि मैंने इसे सही तरीके से समझा।
एलेक्समैलव

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

आप टास्क / कंटिन्यू का उपयोग कर सकते हैं, और अपवाद की जांच कर सकते हैं। यहाँ यह सुंदर बनाने में मदद करने के लिए एक अच्छा विस्तार विधि है:

    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;

लेकिन क्या होगा यदि आप 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}";
}

तो, आप देख सकते हैं कि "जिस विधि का आप उपयोग करना चाहते हैं उसके लिए एक सहायक विधि लिखने की कोई आवश्यकता नहीं है "

फली के दो प्रकार (के लिए ValueTypeResultहै और ReferenceTypeResultरों) कर रहे हैं पर्याप्त


यहाँ के कोड है SafePod। यह एक कंटेनर नहीं है, हालांकि। इसके बजाय, यहValueTypeResult एस और ReferenceTypeResultएस दोनों के लिए एक अपवाद-सुरक्षित प्रतिनिधि रैपर बनाता है

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 कभी भी निष्पादित नहीं होगा!
डेकाफ

इससे समस्या हल नहीं होती है। केवल calc1 निष्पादित करें यदि calc2 विफल रहता है, तो केवल calc3 निष्पादित करें यदि calc1 और& calc2 विफल होते हैं।
जेसन

+1 orn। मैं यह करता हूं। मुझे केवल एक कैच को कोड करना है , मुझे ( trackइस मामले में) संदेश भेजा गया है , और मुझे पता है कि मेरे कोड में किस सेगमेंट में ब्लॉक विफल हो गया। हो सकता है कि आपको DeCaf जैसे सदस्यों को यह बताने के लिए विस्तृत जानकारी देनी चाहिए कि यह trackसंदेश आपकी कस्टम त्रुटि हैंडलिंग दिनचर्या में भेजा गया है जो आपको अपना कोड डीबग करने में सक्षम बनाता है। लगता है कि वह आपके तर्क को समझ नहीं पाया।
jp2code

खैर, @DeCaf सही है, मेरा कोड खंड अगले फ़ंक्शन को निष्पादित नहीं करता है जो कि जोजेल्सन ने पूछा है, मेरे समाधान के लिए संभव नहीं है
Orn Kristjansson
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.