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;
}
Надеюсь, я немного помог... Приятного дня!