Try-Catch с плавными выражениями

Это выражение запроса LINQ терпит неудачу с Win32Exception "Доступ запрещен":

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

И это не с IOException "Устройство не готово":

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

Каков наилучший способ отфильтровать недоступные объекты и избежать исключений?

Ответ 1

Напишите метод расширения!

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

Таким образом вы можете настроить любое поведение, которое вы хотите, и вам не нужно создавать громоздкие и хакерские предложения, так что вы даже можете получить его, чтобы вернуть альтернативное значение, если есть исключение.

Ответ 2

Обновление на основе комментариев: это решение не работает с обычными счетчиками. Он работает на основе счетчиков, используемых в примерах вопросов. Поэтому это не общее решение. Поскольку это было написано как общее решение, я советую не использовать это (чтобы все было просто). Я сохраню этот ответ, чтобы обогатить базу знаний.

Другое решение метода расширения. Почему я предпочитаю его по существующим решениям?

  • Мы хотим пропустить элементы, вызывающие исключения. Это единственная проблема нашего расширения LINQ.
  • Эта реализация не смешивает проблемы (-ы) Select и try/catch.
  • При необходимости мы можем использовать существующие методы LINQ, такие как Select.
  • Он многократно используется: он позволяет использовать несколько запросов внутри запроса LINQ.
  • Это соответствует соглашениям об именах linq: мы фактически пропускаем похожие методы Skip и SkipWhile.

Использование:

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

код:

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

Ваш ответ правильный. Конечно, вы можете скрыть логику проверки внутри метода расширения.

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)

Или лучше так:

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)

Ответ 4

Вставьте фильтр WHERE (который пытается получить доступ к любому объекту и поглощает возможную ошибку доступа):

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

Первое выражение:

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

Второе выражение:

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

Ответ 5

Я попытался бы выполнить первый сценарий:

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

И для второго сценария я предпочел бы использовать IF и сохранить журнал:

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

Надеюсь, я немного помог... Приятного дня!