C# 包装IEnumerable并捕获异常
我有很多类可以C# 包装IEnumerable并捕获异常,c#,exception,ienumerable,wrapper,C#,Exception,Ienumerable,Wrapper,我有很多类可以Process()对象,并返回它们自己的对象: public override IEnumerable<T> Process(IEnumerable<T> incoming) { ... } 但这不起作用,因为 所以我想写一些概念上等价但可编译的东西。:-)我有这个: public override IEnumerable<T> Process(IEnumerable<T> incoming) { IEnumerator&l
Process()
对象,并返回它们自己的对象:
public override IEnumerable<T> Process(IEnumerable<T> incoming) { ... }
但这不起作用,因为
所以我想写一些概念上等价但可编译的东西。:-)我有这个:
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
IEnumerator<T> walker;
try {
walker = this.processor.Process(incoming).GetEnumerator();
} catch (Exception e) {
WriteToLog(e);
throw;
}
while (true) {
T value;
try {
if (!walker.MoveNext()) {
break;
}
value = walker.Current;
} catch (Exception e) {
WriteToLog(e);
throw;
}
yield return value;
}
}
公共重写IEnumerable进程(IEnumerable传入){
IEnumerator walker;
试一试{
walker=this.processor.Process(传入).GetEnumerator();
}捕获(例外e){
书面记录(e);
投掷;
}
while(true){
T值;
试一试{
如果(!walker.MoveNext()){
打破
}
值=沃克电流;
}捕获(例外e){
书面记录(e);
投掷;
}
收益回报值;
}
}
但这比我希望的要复杂得多,我不能完全肯定它的正确性,或者说没有更简单的方法
我走对了吗?有更简单的方法吗?不需要循环和产量(至少在您的示例中是这样)。只需简单地移除它们,就不再限制捕捉块
public override IEnumerable<T> Process(IEnumerable<T> incoming) {
try {
return this.processor.Process(incoming);
} catch (Exception e) {
WriteToLog(e);
throw;
}
}
如果
process
对象一次只处理列表中的一项(如果可能的话),那么可能会更好,这样您就可以收集每个单独的异常并返回一个aggregateeexception
,就像任务并行库一样
[如果流程作为一个整体在列表上运行,或者如果单个异常需要中止整个流程,则此建议显然不合适。]您可以在工具箱中使用以下帮助器功能,而不是仅解决您的特定问题:
static IEnumerable<T> RunEnumerator<T>(Func<IEnumerator<T>> generator,
Action<Exception> onException)
{
using (var enumerator = generator())
{
if (enumerator == null)
yield break;
for (; ; )
{
//Avoid creating a default value with
//unknown type T, as we don't know is it possible to do so
T[] value = null;
try
{
if (enumerator.MoveNext())
value = new T[] { enumerator.Current };
}
catch (Exception e)
{
onException(e);
}
if (value != null)
yield return value[0];
else
yield break;
}
}
}
public static IEnumerable<T> WithExceptionHandler<T>(this IEnumerable<T> orig,
Action<Exception> onException)
{
return RunEnumerator(() =>
{
try
{
return orig.GetEnumerator();
}
catch (Exception e)
{
onException(e);
return null;
}
}, onException);
}
}
通过这种方式,您还可以执行其他操作,如完全忽略异常,让迭代简单地停止。您还可以使用
Func
而不是操作来稍微调整它,并允许异常回调决定迭代是否继续。可以编写linq扩展来跳过导致异常的所有元素,并允许您传递操作来处理引发的异常
public static IEnumerable<T> CatchExceptions<T> (this IEnumerable<T> src, Action<Exception> action = null)
{
using (var enumerator = src.GetEnumerator())
{
bool next = true;
while (next)
{
try
{
next = enumerator.MoveNext();
}
catch (Exception ex)
{
if (action != null)
action(ex);
continue;
}
if (next)
yield return enumerator.Current;
}
}
}
注意:此解决方案仅捕获在处理最初生成的值或预测值时引发的异常,因此,当您按照OP的请求链接IEnumerable表达式时,这是一种处理异常的好方法。如果在IEnumerable提供程序中引发异常,则很有可能中止枚举,此方法将优雅地处理异常,但无法恢复枚举
提供程序中引发的异常通常会发生在文件系统或数据库读取器中,因为它们容易出现超时和其他运行时上下文问题。这把小提琴展示了如何创建这样一个场景:您必须直接在源代码中处理这些问题
如果您想做的是在处理枚举结果的过程中处理异常,那么您只需直接进入for/while循环即可
但是您的示例读起来好像您正在尝试捕获并跳过枚举提供程序引发的异常
据我所知,在C#中,没有办法在枚举器上迭代并跳过枚举器本身中发生的异常。
如果枚举数引发异常,则将来对MoveNext()的所有调用都将导致错误输出
解释为什么会发生这种情况的最简单方法是使用以下非常简单的枚举:
IEnumerable<int> TestCases()
{
yield return 1;
yield return 2;
throw new ApplicationException("fail eunmeration");
yield return 3;
yield return 4;
}
这将产生以下输出:
0
1
Error on item: 2, Exception: This bit failed
3
4
Error on item: 5, Exception: This bit failed
6
7
8
9
我希望这能为将来的其他开发人员解决这个问题,因为一旦我们开始深入Linq和枚举,这是一个非常普遍的想法。功能强大,但也有一些逻辑限制。直接实现IEnumerable并捕获MoveNext和Current中的异常如何?@Matt,我认为这是正确的答案。你应该这样做。短而甜没有错。:)为什么不删除foreach循环并产生?可能重复的“等效”代码问题是walker
变量没有正确地Dispose
ed。您应该始终Dispose
IEnumerator
s,因为它们可能包含资源。它在哪里说this.processor.Process(传入)
懒惰吗?@Bear Monkey,我将重新措辞。如果在调用GetEnumerator()
时发生异常,那么您的代码将无法捕获它,因为它从未调用GetEnumerator()
@Bear Monkey,这有什么意义呢?@Bear Monkey,您误解了。IEnumerable
的整个契约都是惰性的——这意味着它有一个GetEnumerator()
方法返回一个IEnumerator
。因此,IEnumerable
的所有用法都会自动延迟。从技术上讲,您对答案的更新是可行的,但它在某种程度上破坏了IEnumerable的随用随用契约。由于马特的建议不会显示出这种局限性,我更喜欢他的答案。@List.GetEnumerator()返回的IEnumerator kirk并不懒得计算列表。它所做的只是迭代集合。当您对其他集合进行迭代时,它们可能会延迟计算,但列表不是其中之一。它可能更好,也可能不是,但这是一个(半外部)接口,我无法更改,至少不能在合理的时间尺度上更改。:-)这将无法按预期工作,如果enumerator.MoveNext()引发异常,则该枚举数实际上已死亡,您将永远无法以这种方式从该枚举数访问下一项。您的迭代总是在第一个异常时停止,因此您的包装器没有实现比您在try-catch块中包装原始处理循环更大的效果。这取决于枚举器的实现方式。您可以轻松编写自己的IEnumerator
,绕过此限制。
public static IEnumerable<T> CatchExceptions<T> (this IEnumerable<T> src, Action<Exception> action = null)
{
using (var enumerator = src.GetEnumerator())
{
bool next = true;
while (next)
{
try
{
next = enumerator.MoveNext();
}
catch (Exception ex)
{
if (action != null)
action(ex);
continue;
}
if (next)
yield return enumerator.Current;
}
}
}
ienumerable.Select(e => e.something).CatchExceptions().ToArray()
ienumerable.Select(e => e.something).CatchExceptions((ex) => Logger.Log(ex, "something failed")).ToArray()
IEnumerable<int> TestCases()
{
yield return 1;
yield return 2;
throw new ApplicationException("fail eunmeration");
yield return 3;
yield return 4;
}
IEnumerable<int> TestCases2()
{
foreach (var item in Enumerable.Range(0,10))
{
switch(item)
{
case 2:
case 5:
throw new ApplicationException("This bit failed");
default:
yield return item;
break;
}
}
}
IEnumerable<int> TestCases3(Action<int, Exception> exceptionHandler)
{
foreach (var item in Enumerable.Range(0, 10))
{
int value = default(int);
try
{
switch (item)
{
case 2:
case 5:
throw new ApplicationException("This bit failed");
default:
value = item;
break;
}
}
catch(Exception e)
{
if (exceptionHandler != null)
{
exceptionHandler(item, e);
continue;
}
else
throw;
}
yield return value;
}
}
foreach (var item in TestCases3(
(int item, Exception ex)
=>
Console.Out.WriteLine("Error on item: {0}, Exception: {1}", item, ex.Message)))
{
Console.Out.WriteLine(item);
}
0
1
Error on item: 2, Exception: This bit failed
3
4
Error on item: 5, Exception: This bit failed
6
7
8
9