2010-09-30 9 views
8

मैं वर्गों है कि Process() वस्तुओं कर सकते हैं का एक समूह मिल गया है, और अपने स्वयं के वस्तुओं वापसी:लपेटें एक IEnumerable और पकड़ अपवाद

public override IEnumerable<T> Process(IEnumerable<T> incoming) { ... } 

मैं एक प्रोसेसर वर्ग है कि इन प्रोसेसर में से एक लपेट कर सकते हैं लिखना चाहते हैं, और किसी भी अपरिचित अपवाद को लॉग इन करें कि लिपटे Process() विधि फेंक सकती है।

public override IEnumerable<T> Process(IEnumerable<T> incoming) { 
    try { 
     foreach (var x in this.processor.Process(incoming)) { 
      yield return x; 
     } 
    } catch (Exception e) { 
     WriteToLog(e); 
     throw; 
    } 
} 

लेकिन यह काम नहीं करता, CS1626: Cannot yield a value in the body of a try block with a catch clause की वजह से: मेरा पहला विचार कुछ इस तरह था।

तो मैं कुछ ऐसा लिखना चाहता हूं जो अवधारणात्मक समकक्ष लेकिन संकलित हो। :-) मैं इस मिल गया है:

public override IEnumerable<T> Process(IEnumerable<T> incoming) { 
    IEnumerator<T> walker; 
    try { 
     walker = this.processor.Process(incoming).GetEnumerator(); 
    } catch (Exception e) { 
     WriteToLog(e); 
     throw; 
    } 

    while (true) { 
     T value; 
     try { 
      if (!walker.MoveNext()) { 
       break; 
      } 
      value = walker.Current; 
     } catch (Exception e) { 
      WriteToLog(e); 
      throw; 
     } 
     yield return value; 
    } 
} 

लेकिन यह है कि और अधिक जटिल की तुलना में मैं आशा व्यक्त की थी, और मैं पूरी तरह से या तो इसकी सत्यता के कुछ या नहीं कर रहा हूँ वहाँ एक बहुत सरल तरीका नहीं है कि।

क्या मैं यहां सही रास्ते पर हूं? क्या कोई आसान तरीका है?

+1

IENumerable को सीधे और अपने MoveNext और Current के अंदर अपवादों को पकड़ने के बारे में क्या? –

+0

@ मैट, मुझे लगता है कि यह सही जवाब है। आपको इसे ऐसा करना चाहिए था। छोटे और मीठे के साथ कुछ भी गलत नहीं है। :) –

+0

क्यों न केवल foreach पाश और उपज को हटा दें? –

उत्तर

-1

लूप और उपज की आवश्यकता नहीं है (कम से कम आपके उदाहरण में)। सरल उन्हें हटा दें और अब पकड़ ब्लॉक पर प्रतिबंध नहीं है।

public override IEnumerable<T> Process(IEnumerable<T> incoming) { 
    try { 
     return this.processor.Process(incoming); 
    } catch (Exception e) { 
     WriteToLog(e); 
     throw; 
    } 
} 

मैं this.processor.Process(incoming) संभालने हूँ एक संग्रह IEnumerable<T> को लागू करने देता है, इसलिए आप एक नया इटरेटर बनाने की जरूरत नहीं है। यदि this.processor.Process(incoming) आलसी है तो मूल्यांकन कर

public override IEnumerable<T> Process(IEnumerable<T> incoming) { 
    try { 
     return this.processor.Process(incoming).ToList(); 
    } catch (Exception e) { 
     WriteToLog(e); 
     throw; 
    } 
} 
+0

कहां कहता है 'this.processor.Process (आने वाली)' आलसी है? –

+0

@Bear बंदर, मैं rephrase होगा। यदि अपवाद 'GetEnumerator() 'के आमंत्रण पर होता है तो आपका कोड इसे पकड़ नहीं पाएगा क्योंकि यह कभी भी' GetEnumerator()' को आमंत्रित नहीं करता है। –

+0

@ किर्क आपको कोई समझ नहीं है। –

0

अगर process वस्तु कुछ है कि एक समय में सूची में केवल एक आइटम संसाधित (यदि भी संभव है), इस तरह आप प्रत्येक व्यक्ति के अपवाद एकत्र कर पाया था यह अच्छा हो सकता है और टास्क समांतर लाइब्रेरी की तरह AggregateException वापस करें।

[प्रक्रिया एक पूरे के रूप सूची पर चल रही है, या एक ही अपवाद पूरी प्रक्रिया को निरस्त करने की जरूरत है स्पष्ट रूप से यह सुझाव उचित नहीं है।]

+0

करता है यह अच्छा हो सकता है, या ऐसा नहीं हो सकता है, लेकिन यह एक (सेमी-बाहरी) इंटरफ़ेस है जिसे मैं नहीं कर सकता कम से कम उचित समय पर नहीं बदलें। :-) – Ken

-1

के बजाय केवल अपने विशिष्ट समस्या को हल करने के लिए, आप अपने टूलकिट में इन सहायक कार्यों कर सकते हैं:

static IEnumerable<T> RunEnumerator<T>(Func<IEnumerator<T>> generator, 
     Action<Exception> onException) 
    { 
     using (var enumerator = generator()) 
     { 
      if (enumerator == null) 
       yield break; 
      for (; ;) 
      { 
       //Avoid creating a default value with 
       //unknown type T, as we don't know is it possible to do so 
       T[] value = null; 
       try 
       { 
        if (enumerator.MoveNext()) 
         value = new T[] { enumerator.Current }; 
       } 
       catch (Exception e) 
       { 
        onException(e); 
       } 
       if (value != null) 
        yield return value[0]; 
       else 
        yield break; 
      } 
     } 
    } 

    public static IEnumerable<T> WithExceptionHandler<T>(this IEnumerable<T> orig, 
     Action<Exception> onException) 
    { 
     return RunEnumerator(() => 
     { 
      try 
      { 
       return orig.GetEnumerator(); 
      } 
      catch (Exception e) 
      { 
       onException(e); 
       return null; 
      } 
     }, onException); 
    } 
} 

WithExceptionHandler<T> दूसरे में एक IEnumerable<T> धर्मान्तरित, और जब अपवाद तब होता है, onException कॉलबैक बुलाया जाएगा। इस तरह आप अपने process समारोह इस तरह कार्यान्वित हो सकता है:

public override IEnumerable<T> Process(IEnumerable<T> incoming) { 
    return incoming.WithExceptionHandler(e => { 
     WriteToLog(e); 
     throw; 
    } 
} 

इस तरह, आप भी अपवाद पूरी तरह से उपेक्षा और यात्रा बस बंद हो जाता है जाने की तरह कुछ और कर सकते हैं। आप Action<Exception> के बजाय Func<Exception, bool> का उपयोग करके इसे थोड़ा सा समायोजित भी कर सकते हैं और अपवाद कॉलबैक को यह तय करने की अनुमति देता है कि पुनरावृत्ति जारी रहे या नहीं।

+0

यह अनुमान के अनुसार काम नहीं करेगा, अगर enumerator.MoveNext() एक अपवाद फेंकता है, तो गणनाकर्ता प्रभावी ढंग से मर चुका है, आप इस तरह से गणनाकर्ता से अगले आइटम तक कभी नहीं पहुंचेंगे। आपका पुनरावृत्ति हमेशा पहले अपवाद पर रुक जाएगा, इसलिए यदि आपने रीयल कैच ब्लॉक में मूल प्रोसेसिंग लूप को लपेट लिया है तो आपके रैपर ने कुछ भी हासिल नहीं किया है। –

+0

अच्छा, यह इस बात पर निर्भर करता है कि आपका गणक कैसे कार्यान्वित किया जा रहा है। आप आसानी से अपना स्वयं का 'आईन्यूमेरेटर' लिख सकते हैं जो इस सीमा को बाईपास कर रहा है। यद्यपि आप किसी भी अन्य सिस्टम पर भरोसा नहीं कर सकते हैं, कम से कम आप अपने कोड के लिए उपयोग कर सकते हैं। –

+1

मुझे गलत मत समझो, मुझे इस तरह के समाधान में वास्तव में दिलचस्पी है। मुझे एक उदाहरण देखना अच्छा लगेगा जो काम करता है :) लेकिन यदि MoveNext() एक अपवाद फेंकता है तो टोस्ट है, यदि MoveNext अपवाद को संभालता है, तो MoveNext सत्य वापस आ रहा है, इसलिए यह सभी रैपरिंग पूरी तरह से अनावश्यक है। यही वह है जो मैं प्राप्त कर रहा हूं, यह रैपर कभी भी अपवाद को छोड़ नहीं पाएगा, यह पहले को संभालेगा और खूबसूरती से बाहर निकल जाएगा, लेकिन यह पहले अपवाद के बाद अगले आइटम पर कभी नहीं मिलेगा। –

4

एक लिनक्स एक्सटेंशन को उन सभी तत्वों को छोड़ने के लिए लिखा जा सकता है जो अपवाद का कारण बनते हैं और इसे लॉगिंग फ़ंक्शन में पास करते हैं।

public static IEnumerable<T> CatchExceptions<T> (this IEnumerable<T> src, Action<Exception> action = null) { 
     using (var enumerator = src.GetEnumerator()) { 
      bool next = true; 

      while (next) { 
       try { 
        next = enumerator.MoveNext(); 
       } catch (Exception ex) { 
        if (action != null) { 
         action(ex); 
        } 
        continue; 
       } 

       if (next) { 
        yield return enumerator.Current; 
       } 
      } 
     } 
    } 

उदाहरण:

ienumerable.Select(e => e.something).CatchExceptions().ToArray() 

ienumerable.Select(e => e.something).CatchExceptions((ex) => Logger.Log(ex, "something failed")).ToArray() 
+1

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

3

कि आप क्या करना चाहते हैं एक गणना के परिणाम के प्रसंस्करण के दौरान एक अपवाद को संभालने है, तो आप तर्क कोशिश बस अपने लिए/जबकि अंदर सीधे जाने की जरूरत है पाश।

लेकिन आपका उदाहरण पढ़ता है जैसे कि आप गणना प्रदाता द्वारा उठाए गए अपवादों को पकड़ने और छोड़ने की कोशिश कर रहे हैं।

जहां तक ​​मैं पता लगा सकता हूं, सी # में एक गणनाकर्ता को छोड़ने और छोड़ने और अपवाद के भीतर अपवाद के लिए कोई रास्ता नहीं है। यदि गणनाकर्ता अपवाद उठाता है, तो MoveNext() के लिए सभी भावी कॉल के परिणामस्वरूप झूठी आउटपुट होगी। ,

IEnumerable<int> TestCases() 
{ 
    yield return 1; 
    yield return 2; 
    throw new ApplicationException("fail eunmeration"); 
    yield return 3; 
    yield return 4; 
} 

जाहिर है जब हम इस उदाहरण पर नजर यह स्पष्ट है कि फेंका अपवाद इस पूरे ब्लॉक से बाहर निकलने के कारण होगा है:

समझाने के लिए कि ऐसा क्यों होता सबसे आसान तरीका यह बहुत सरल गणनीय साथ है और तीसरा और चौथा उपज कथन कभी संसाधित नहीं किया जाएगा। वास्तव में तीसरे उपज कथन पर सामान्य 'पहुंचने योग्य कोड का पता चला' संकलक चेतावनी प्राप्त करें।

तो जब गणनीय एक अधिक जटिल है, वही नियम लागू:

IEnumerable<int> TestCases2() 
{ 
    foreach (var item in Enumerable.Range(0,10)) 
    { 
     switch(item) 
     { 
      case 2: 
      case 5: 
       throw new ApplicationException("This bit failed"); 
      default: 
       yield return item; 
       break; 
     } 
    } 
} 

जब अपवाद उठाया है, इस ब्लॉक के प्रसंस्करण रहता है और वापस ऊपर निकटतम अपवाद संचालक को कॉल स्टैक से गुजरता है।

एसओ पर मिले इस मुद्दे को पाने के लिए सभी व्यावहारिक उदाहरणों को गणना में अगले आइटम पर नहीं जाते हैं, वे सभी अपवाद पर तोड़ते हैं।

इसलिए पर गणना में अपवाद में आपको अपवाद की आवश्यकता होगी ताकि आपको इसे सुविधाजनक बनाने के लिए प्रदाता की आवश्यकता होगी। यह केवल वास्तव में यदि संभव हो तो अपने प्रदाता कोडित है, या आप डेवलपर किसने किया संपर्क कर सकते हैं, तो निम्न तुम ऐसा कैसे प्राप्त कर सकते थे की एक अति सरल उदाहरण है:

IEnumerable<int> TestCases3(Action<int, Exception> exceptionHandler) 
{ 
    foreach (var item in Enumerable.Range(0, 10)) 
    { 
     int value = default(int); 
     try 
     { 
      switch (item) 
      { 
       case 2: 
       case 5: 
        throw new ApplicationException("This bit failed"); 
       default: 
        value = item; 
        break; 
      } 
     } 
     catch(Exception e) 
     { 
      if (exceptionHandler != null) 
      { 
       exceptionHandler(item, e); 
       continue; 
      } 
      else 
       throw; 
     } 
     yield return value; 
    } 
} 

...

foreach (var item in TestCases3(
    (int item, Exception ex) 
    => 
    Console.Out.WriteLine("Error on item: {0}, Exception: {1}", item, ex.Message))) 
{ 
    Console.Out.WriteLine(item); 
} 

यह निम्नलिखित उत्पादन का उत्पादन होगा:

0 
1 
Error on item: 2, Exception: This bit failed 
3 
4 
Error on item: 5, Exception: This bit failed 
6 
7 
8 
9 

मुझे आशा है कि यह भविष्य में अन्य डेवलपर्स के लिए मुद्दे को साफ करता है के रूप में यह एक बहुत आम विचार है कि हम सब एक बार मिलता है हम getti शुरू लिंक और गणना में गहराई से। शक्तिशाली सामान लेकिन कुछ तार्किक सीमाएं हैं।