2014-12-23 12 views
11

Dieser LINQ-Abfrage-Ausdruck nicht mit Win32Exception "Zugriff wird verweigert ":Try-Catch mit fließend Ausdrücke

Process.GetProcesses().Select(p => p.MainModule.FileName) 

Und das nicht mit IOException" Das Gerät ist nicht bereit":

DriveInfo.GetDrives().Select(d => d.VolumeLabel) 

Was ist der beste Weg, um unzugängliche Objekte herauszufiltern und Ausnahmen zu vermeiden?

Antwort

12

Schreiben Sie eine Erweiterungsmethode!

void Main() 
{ 
    var volumeLabels = 
     DriveInfo 
     .GetDrives() 
     .SelectSafe(dr => dr.VolumeLabel); 
} 

// Define other methods and classes here 

public static class LinqExtensions 
{ 
    public static IEnumerable<T2> SelectSafe<T,T2>(this IEnumerable<T> source, Func<T,T2> selector) 
    { 
     foreach (var item in source) 
     { 
      T2 value = default(T2); 
      try 
      {   
       value = selector(item); 
      } 
      catch 
      { 
       continue; 
      } 
      yield return value; 
     } 
    } 
} 

diese Weise können Sie jedes Verhalten anpassen können Sie wollen, und Sie haben zu sperrig und Hacky where-Klauseln nicht zu schaffen, auf diese Weise Sie es sogar einen alternativen Wert bekommen könnte zurückkehren, wenn eine Ausnahme gibt.

+1

+1 Ich mag es! Es sieht auch ziemlich generisch aus, gut gemacht. (Ich warte eine Weile, um andere Antworten zu überprüfen) –

+0

Ich würde vorschlagen, etwas wie 'try {yield return selector (item); } catch {} ', wodurch" value "und" continue "überflüssig werden. – NetMage

+1

@NetMage Sie können keinen Wert innerhalb eines "try" -Blocks "errechnen". –

5

Insert eine WHERE-Filter (die jedes Objekt zuzugreifen versucht, und absorbiert den möglichen Zugriffsfehler) mit:

{ try { var x = obj.MyProp; return true; } catch { return false; } }: 

erste Ausdruck:

Process 
    .GetProcesses() 
    .Where(p => { try { var x = p.MainModule; return true; } catch { return false; } }) 
    .Select(p => p.MainModule.FileName) 

zweite Ausdruck:

DriveInfo 
    .GetDrives() 
    .Where(d => { try { var x = d.VolumeLabel; return true; } catch { return false; } }) 
    .Select(d => d.VolumeLabel) 
+0

Diese mehr scheint, wie Sie einen Test wurden zu geben ... Sie beantwortet Ihre eigene Frage in weniger als 4 Minuten. Sollte ein Wiki sein – nckbrz

+1

Wenn das in der Tat die richtige Antwort ist. Ich schaute überall hin und kam mit meiner eigenen Alternative, aber nicht sicher, dass es die beste ist. –

+1

@nixxbb ist es völlig legitim, Ihre eigenen Fragen zu beantworten. Es wird sogar von SO empfohlen, die Wissensbasis zu erweitern. – Aphelion

3

Ich würde für das erste Szenario versuchen:

//Declare logger type 
private readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

Process.GetProcesses() 
.Where(p => { 
    try { 
     var x = p.MainModule; 
     return true; 
    } 
    catch(Win32Exception e2) 
    { IgnoreError(); } 
    }) 
.Select(p => p.MainModule.FileName) 

public static void IgnoreError(Exception e) 
{ 
    #if DEBUG 
    throw e2; 
    //Save the error track, I prefer log4net 
    _log.Info("Something bad happened!"); 
    #end if 
} 

Und für das zweite Szenario, würde ich eher lieber eine IF und speichern Sie das Protokoll verwenden:

//Somewhere in the begging of your class, in a place whose name I do not care to remember ... 
//Declare logger type 
private readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 


public List<string> VolumenLabels() 
{ 
    //Return the List<T> 
    List<string> myVolumeLabels = new List<string>(); 
    //Getting the info 
    DriveInfo[] allDrives = DriveInfo.GetDrives(); 

    foreach(DriveInfo drive in allDrives) 
    { 
     if (drive.IsReady == true) 
     { 
      myVolumeLabels.Add(drive.VolumeLabel.ToString()); 
     } 
     else 
     { 
      _log.Info("Check the Drive: " + drive.Name + " the device is not ready."); 
     } 
    }  
    return myVolumeLabels; 
} 

Ich hoffe, ich habe ein wenig geholfen ... einen schönen Tag!

+1

Sorry, es hat überhaupt nicht geholfen. Warum ist das besser als die Antwort? –

+1

Ich denke nicht, dass es besser ist, es ist nur eine andere Alternative, mit einem Plus, Es speichert die Fehler mit [Log4Net] (https://logging.apache.org/log4net/), die es ziemlich toll ist, eine Erfolgsbilanz von zu haben Die Fehler. –

+4

Hinzufügen von Code Clutter ist kein Plus. –

7

Ihre Antwort ist die richtige. Sie können natürlich versuchen, die Prüflogik in einer Erweiterungsmethode zu verbergen.

public static IEnumerable<TElement> WhereSafe<TElement, TInner>(this IEnumerable<TElement> sequence, Func<TElement, TInner> selector) 
{ 
    foreach (var element in sequence) 
    { 
     try { selector(element); } 
     catch { continue; } 
     yield return element; 
    } 
} 


Process 
    .GetProcesses() 
    .WhereSafe(p => p.MainModule) 
    .Select(p => p.MainModule.FileName) 

Oder besser so:

public static IEnumerable<TInner> TrySelect<TElement, TInner>(this IEnumerable<TElement> sequence, Func<TElement, TInner> selector) 
{ 
    TInner current = default(TInner); 
    foreach (var element in sequence) 
    { 
     try { current = selector(element); } 
     catch { continue; } 
     yield return current; 
    } 
} 


Process 
    .GetProcesses() 
    .TrySelect(p => p.MainModule.FileName) 
+0

Ich bin verwirrt: Ich habe Clints Antwort vor deiner gesehen. War das wahr? –

+0

Wir müssen zur gleichen Zeit geschrieben haben. Ich war auch überrascht, seine Antwort so ähnlich zu meiner zu sehen, als ich die Seite aufgefrischt habe. Außerdem hielten wir uns an die Namenskonventionen von .NET und die guten Praktiken von LINQ, also Ähnlichkeiten. "Große Köpfe denken gleich", Kudos Clint;) – ensisNoctis

+0

Sie können auch sehen, dass ich Booleans verwendet, um korrekte Elemente zu kennzeichnen, also ist das ein Beweis, dass ich alleine gearbeitet habe. Ich habe bearbeitet, um 'continue' Schlüsselwörter zu verwenden, wie es Clint gemacht hat, weil es einfach besser ist. Der Vorrang ist sein. – ensisNoctis

10

aktualisieren, basierend auf Kommentare: Diese Lösung funktioniert nicht mit gemeinsamen Aufzählungen arbeiten. Es funktioniert auf der Grundlage der in den Beispielen der Frage verwendeten Enumeratoren. Daher ist es keine generische Lösung. Da es als allgemeine Lösung geschrieben wurde, rate ich davon ab, dies zu verwenden (um die Dinge einfach zu halten). Ich werde diese Antwort behalten, um die Wissensbasis zu bereichern.

Eine andere Extension-Methode Lösung. Warum bevorzuge ich es gegenüber den bestehenden Lösungen?

  • Wir wollen Elemente überspringen, die nur Ausnahmen verursachen. Dies ist das einzige Anliegen unserer LINQ-Erweiterung.
  • Diese Implementierung vermischt nicht die Bedenken von Select und try/catch.
  • Wir können weiterhin vorhandene LINQ-Methoden wie Select bei Bedarf verwenden.
  • Es ist wiederverwendbar: es ermöglicht mehrere Nutzungen innerhalb einer LINQ-Abfrage.
  • Es folgt Linq Namenskonventionen: Wir überspringen tatsächlich ähnlich wie Skip und SkipWhile Methoden.

Verbrauch:

var result = DriveInfo 
    .GetDrives() 
    .Select(d => d.VolumeLabel) 
    .SkipExceptions() // Our extension method 
    .ToList(); 

Code:

public static class EnumerableExt 
{ 
    // We use the `Skip` name because its implied behaviour equals the `Skip` and `SkipWhile` implementations 
    public static IEnumerable<TSource> SkipExceptions<TSource>(this IEnumerable<TSource> source) 
    { 
     // We use the enumerator to be able to catch exceptions when enumerating the source 
     using (var enumerator = source.GetEnumerator()) 
     { 
      // We use a true loop with a break because enumerator.MoveNext can throw the Exception we need to handle 
      while (true) 
      { 
       var exceptionCaught = false; 
       var currentElement = default(TSource); 
       try 
       { 
        if (!enumerator.MoveNext()) 
        { 
         // We've finished enumerating. Break to exit the while loop        
         break; 
        } 

        currentElement = enumerator.Current; 
       } 
       catch 
       { 
        // Ignore this exception and skip this item. 
        exceptionCaught = true; 
       } 

       // Skip this item if we caught an exception. Otherwise return the current element. 
       if (exceptionCaught) continue; 

       yield return currentElement; 
      } 
     } 
    } 
} 
+3

Ich mag diese Lösung am besten, weil sie implementationsunabhängig ist und das Streaming-Paradigma von LINQ intelligent nutzt. –

+2

+1 Brilliant in der Tat. Dies sollte Teil des .NET Framework sein! Ihre Erweiterungsmethode filtert jedes nicht zugängliche Objekt in einem Enumerator elegant heraus. Es ist sehr generisch und kann auf jeden anderen Enumerator angewendet werden. LINQ ist nur eine mögliche Anwendung. –

+1

Ich hatte genau die gleiche Idee, aber verworfen. Hauptsächlich, weil im Gegensatz zu dem konkreten Catch wie in 'TrySelect' aus einer anderen Antwort, wenn die unbekannte Implementierung von' MoveNext' eine Exception auslöst, es nicht definiert ist, was beim nächsten Aufruf von 'MoveNext' passieren wird einen unendlichen Zyklus verursachen. Oder gib einfach ein falsches Ergebnis zurück. Zum Beispiel "Enumerable.Range (1, 10) .Wählen Sie ((x, i) => x/i) .SkipExceptions(). ToList();' gibt 0 Elemente zurück, obwohl nur die erste Auswahl eine Ausnahme auslöst Nach Idee soll es die restlichen 9 Elemente zurückgeben. –