Try-Catch用流利的表达方式

Cri*_* S. 11 c# linq select functional-programming exception-handling

此LINQ查询表达式失败,出现Win32Exception" 访问被拒绝 ":

Process.GetProcesses().Select(p => p.MainModule.FileName)
Run Code Online (Sandbox Code Playgroud)

这会因IOException" 设备未就绪 " 而失败:

DriveInfo.GetDrives().Select(d => d.VolumeLabel)
Run Code Online (Sandbox Code Playgroud)

过滤掉无法访问的对象并避免异常的最佳方法是什么?

Cli*_*int 13

写一个扩展方法!

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以自定义您想要的任何行为,并且您不必创建笨重和hacky where子句,这样您甚至可以在有异常的情况下让它返回替代值.


Aph*_*ion 10

基于注释更新:此解决方案不适用于常见的枚举器.它确实基于问题示例中使用的枚举器.因此,它不是通用的解决方案.因为它是作为通用解决方案编写的,所以我建议不要使用它(为了简单起见).我将保留这个答案,以丰富知识库.

另一种Extension方法解决方案 为什么我更喜欢它而不是现有的解决方案?

  • 我们想跳过仅导致异常的元素.这是我们LINQ扩展的唯一问题.
  • 此实现不混合的关注(S)Selecttry/catch.
  • 我们仍然可以Select在需要时使用现有的LINQ方法.
  • 它是可重用的:它允许LINQ查询中的多个用法.
  • 它遵循linq命名约定:我们实际上跳过类似于SkipSkipWhile方法.

用法:

var result = DriveInfo
    .GetDrives()
    .Select(d => d.VolumeLabel)
    .SkipExceptions() // Our extension method
    .ToList();
Run Code Online (Sandbox Code Playgroud)

码:

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我最喜欢这个解决方案,因为它与实现无关,它可以智能地使用LINQ的流式范例. (3认同)
  • 确实+1辉煌.这应该是.NET Framework的一部分!您的扩展方法可以优雅地过滤枚举器中任何无法访问的对象.它非常通用,可以应用于任何其他枚举器.LINQ只是一个可能的应用程序. (2认同)

ens*_*tis 7

你的答案是正确的.您当然可以尝试在扩展方法中隐藏检查逻辑.

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)
Run Code Online (Sandbox Code Playgroud)

或者更好:

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)
Run Code Online (Sandbox Code Playgroud)


Cri*_* S. 5

插入WHERE过滤器(尝试访问任何对象并吸收可能的访问错误):

   { try { var x = obj.MyProp; return true; } catch { return false; } }:
Run Code Online (Sandbox Code Playgroud)

第一个表达:

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

第二个表达:

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