例:
myEnumerable.Select(a => ThisMethodMayThrowExceptions(a));
例外をスローしても機能させるにはどうすればよいですか?デフォルト値の場合のtrycatchブロックのように、例外がスローされます...
myEnumerable.Select(a =>
{
try
{
return ThisMethodMayThrowExceptions(a));
}
catch(Exception)
{
return defaultValue;
}
});
でも実は匂いがします。
ラムダ構文について:
x => x.something
一種のショートカットであり、次のように書くことができます
(x) => { return x.something; }
そのtry/catchを持つプロジェクションを呼び出します。
myEnumerable.Select(a => TryThisMethod(a));
...
public static Bar TryThisMethod(Foo a)
{
try
{
return ThisMethodMayThrowExceptions(a);
}
catch(BarNotFoundException)
{
return Bar.Default;
}
}
確かに私はまれにこのテクニックを使いたいと思います。一般的には例外の乱用のように感じますが、選択の余地がないAPIがある場合もあります。
(ラムダ式として「インライン」にするのではなく、ほぼ確実に別のメソッドに入れます。)
IEnumerable<T>
のすべての反復をすばやく試行/キャッチしたい場合は、小さな拡張機能が付属しています。
使用法
public void Test()
{
List<string> completedProcesses = initialEnumerable
.SelectTry(x => RiskyOperation(x))
.OnCaughtException(exception => { _logger.Error(exception); return null; })
.Where(x => x != null) // filter the ones which failed
.ToList();
}
拡張子
public static class OnCaughtExceptionExtension
{
public static IEnumerable<SelectTryResult<TSource, TResult>> SelectTry<TSource, TResult>(this IEnumerable<TSource> enumerable, Func<TSource, TResult> selector)
{
foreach (TSource element in enumerable)
{
SelectTryResult<TSource, TResult> returnedValue;
try
{
returnedValue = new SelectTryResult<TSource, TResult>(element, selector(element), null);
}
catch (Exception ex)
{
returnedValue = new SelectTryResult<TSource, TResult>(element, default(TResult), ex);
}
yield return returnedValue;
}
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.CaughtException));
}
public static IEnumerable<TResult> OnCaughtException<TSource, TResult>(this IEnumerable<SelectTryResult<TSource, TResult>> enumerable, Func<TSource, Exception, TResult> exceptionHandler)
{
return enumerable.Select(x => x.CaughtException == null ? x.Result : exceptionHandler(x.Source, x.CaughtException));
}
public class SelectTryResult<TSource,TResult>
{
internal SelectTryResult(TSource source, TResult result, Exception exception)
{
Source = source;
Result = result;
CaughtException = exception;
}
public TSource Source { get; private set; }
public TResult Result { get; private set; }
public Exception CaughtException { get; private set; }
}
}
SkipOnException
拡張子を付けて、たとえば例外ハンドラーをオプションで受け入れることで、最終的にはもう少し先に進むことができます。
ラムダ関数の代わりにExpressionが必要な場合(IQueryableから選択する場合など)、次のようなものを使用できます。
public static class ExpressionHelper
{
public static Expression<Func<TSource, TResult>> TryDefaultExpression<TSource, TResult>(Expression<Func<TSource, TResult>> success, TResult defaultValue)
{
var body = Expression.TryCatch(success.Body, Expression.Catch(Expression.Parameter(typeof(Exception)), Expression.Constant(defaultValue, typeof (TResult))));
var lambda = Expression.Lambda<Func<TSource, TResult>>(body, success.Parameters);
return lambda;
}
}
使用法:
[Test]
public void Test()
{
var strings = new object [] {"1", "2", "woot", "3", Guid.NewGuid()}.AsQueryable();
var ints = strings.Select(ExpressionHelper.TryDefaultExpression<object, int>(x => Convert.ToInt32(x), 0));
Assert.IsTrue(ints.SequenceEqual(new[] {1, 2, 0, 3, 0}));
}
理解構文のためのStefanのソリューションのバリエーション:
from a in myEnumerable
select (new Func<myType>(() => {
try
{
return ThisMethodMayThrowExceptions(a));
}
catch(Exception)
{
return defaultValue;
}
}))();
それも「におい」がしますが、それでもこのアプローチは、式内に副作用のあるコードを実行するために使用できる場合があります。
LINQを扱う場合、式が望ましくない副作用を引き起こす可能性があるシナリオがよく見られます。 Jonが言ったように、この種の問題に対処する最善の方法は、コードを爆破しない方法でこれらを適切に処理するLINQ式で使用できるユーティリティメソッドを用意することです。たとえば、TryParseをラップして、何かが数値であるかどうかを通知するメソッドを時々使用する必要がありました。もちろん、他にも多くの例があります。
式の構文の制限の1つは、特定のシナリオを処理するために式から一時的に実行を中断せずに、正常に実行できないこと、またはまったく実行できないことがたくさんあることです。 XMLファイル内のアイテムのサブセットを解析することは素晴らしい例です。単一の式内のXMLファイルからの子サブセットを含む複雑な親コレクションを解析してみてください。すぐに、すべてが一緒になって操作全体を形成するいくつかの式の断片を記述していることに気付くでしょう。