Yield 返回与 try catch,如何解决?

发布于 2024-10-18 13:02:55 字数 1085 浏览 6 评论 0原文

我有一段代码:

using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
{
    char[] buffer = new char[chunksize];
    while (stream.Peek() >= 0)
    {
       int readCount = stream.Read(buffer, 0, chunksize);

       yield return new string(buffer, 0, readCount);
    }
 }

现在我必须用 try-catch 块包围它,

try
{
   using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
   {
       char[] buffer = new char[chunksize];
       while (stream.Peek() >= 0)
       {
          int readCount = stream.Read(buffer, 0, chunksize);

          yield return new string(buffer, 0, readCount);
       }
    } 
}
catch (Exception ex)
{
    throw ExceptionMapper.Map(ex, file.FullName)
}

我看不到任何方法来做我想做的事情。

该方法具有签名:

public IEnumerable<string> ReadPieces(int pieces)

我需要一个 try/catch 并在 catch 情况下调用 ExceptionMapper 。 该方法由所有调用者延迟使用。

我必须捕获的异常来自这些调用:

File.OpenRead()
stream.Read()

I've a piece of code:

using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
{
    char[] buffer = new char[chunksize];
    while (stream.Peek() >= 0)
    {
       int readCount = stream.Read(buffer, 0, chunksize);

       yield return new string(buffer, 0, readCount);
    }
 }

Now I have to surround this with an try-catch block

try
{
   using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
   {
       char[] buffer = new char[chunksize];
       while (stream.Peek() >= 0)
       {
          int readCount = stream.Read(buffer, 0, chunksize);

          yield return new string(buffer, 0, readCount);
       }
    } 
}
catch (Exception ex)
{
    throw ExceptionMapper.Map(ex, file.FullName)
}

I can't see any way to do what I want.

The method has the signature:

public IEnumerable<string> ReadPieces(int pieces)

I need a try/catch with a call to the ExceptionMapper in the catch case.
The method is used deferred by all callers.

The exceptions I have to catch are coming from these calls:

File.OpenRead()
stream.Read()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

恋竹姑娘 2024-10-25 13:02:55

这是一个适合我的代码片段(我没有达到错误条件)。

while (true)
{
    T ret = null;
    try
    {
        if (!enumerator.MoveNext())
        {
            break;
        }
        ret = enumerator.Current;
    }
    catch (Exception ex)
    {
        // handle the exception and end the iteration
        // probably you want it to re-throw it
        break;
    }
    // the yield statement is outside the try catch block
    yield return ret;
}

Here is a code snippet, which works for me (I did not reach the error condition).

while (true)
{
    T ret = null;
    try
    {
        if (!enumerator.MoveNext())
        {
            break;
        }
        ret = enumerator.Current;
    }
    catch (Exception ex)
    {
        // handle the exception and end the iteration
        // probably you want it to re-throw it
        break;
    }
    // the yield statement is outside the try catch block
    yield return ret;
}
旧话新听 2024-10-25 13:02:55

因为您希望在枚举期间保持 Stream 打开并处理异常并正确关闭文件句柄,所以我认为您不能使用常规枚举快捷方式(迭代器块、yield-return/yield-休息)。

相反,只需执行编译器会为您执行的操作并添加一些:

通过自己实现 IEnumerator,您还可以添加 IDisposable

public class LazyStream : IEnumerable<string>, IDisposable
{
  LazyEnumerator le;

  public LazyStream(FileInfo file, Encoding encoding)
  {
    le = new LazyEnumerator(file, encoding);
  }

  #region IEnumerable<string> Members
  public IEnumerator<string> GetEnumerator()
  {
    return le;
  }
  #endregion

  #region IEnumerable Members
  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return le;
  }
  #endregion

  #region IDisposable Members
  private bool disposed = false;

  public void Dispose()
  {
    Dispose(true);

    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (!this.disposed)
    {
      if (disposing)
      {
        if (le != null) le.Dispose();
      }

      disposed = true;
    }
  }
  #endregion

  class LazyEnumerator : IEnumerator<string>, IDisposable
  {
    StreamReader streamReader;
    const int chunksize = 1024;
    char[] buffer = new char[chunksize];

    string current;

    public LazyEnumerator(FileInfo file, Encoding encoding)
    {
      try
      {
        streamReader = new StreamReader(file.OpenRead(), encoding);
      }
      catch
      {
        // Catch some generator related exception
      }
    }

    #region IEnumerator<string> Members
    public string Current
    {
      get { return current; }
    }
    #endregion

    #region IEnumerator Members
    object System.Collections.IEnumerator.Current
    {
      get { return current; }
    }

    public bool MoveNext()
    {
      try
      {
        if (streamReader.Peek() >= 0)
        {
          int readCount = streamReader.Read(buffer, 0, chunksize);

          current = new string(buffer, 0, readCount);

          return true;
        }
        else
        {
          return false;
        }
      }
      catch
      {
        // Trap some iteration error
      }
    }

    public void Reset()
    {
      throw new NotSupportedException();
    }
    #endregion

    #region IDisposable Members
    private bool disposed = false;

    public void Dispose()
    {
      Dispose(true);

      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (!this.disposed)
      {
        if (disposing)
        {
          if (streamReader != null) streamReader.Dispose();
        }

        disposed = true;
      }
    }
    #endregion
  }
}

我没有对此进行测试,但我认为它已经很接近了。

像这样使用:

using (var fe = new LazyStream(new FileInfo("c:\\data.log"), Encoding.ASCII))
{
  foreach (var chunk in fe)
  {
    Console.WriteLine(chunk);
  }
}

编辑:我完全忘记添加 try-catch 块放置。哎呀。

Because you want to keep the Stream open for the duration of the enumeration AND deal with exceptions AND properly close the file handle either way, I don't think you can use a regular enumeration shortcut (the iterator block, yield-return/yield-break).

Instead, just do what the compiler would have done for you and add some:

By implementing IEnumerator yourself, you can also add IDisposable

public class LazyStream : IEnumerable<string>, IDisposable
{
  LazyEnumerator le;

  public LazyStream(FileInfo file, Encoding encoding)
  {
    le = new LazyEnumerator(file, encoding);
  }

  #region IEnumerable<string> Members
  public IEnumerator<string> GetEnumerator()
  {
    return le;
  }
  #endregion

  #region IEnumerable Members
  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return le;
  }
  #endregion

  #region IDisposable Members
  private bool disposed = false;

  public void Dispose()
  {
    Dispose(true);

    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (!this.disposed)
    {
      if (disposing)
      {
        if (le != null) le.Dispose();
      }

      disposed = true;
    }
  }
  #endregion

  class LazyEnumerator : IEnumerator<string>, IDisposable
  {
    StreamReader streamReader;
    const int chunksize = 1024;
    char[] buffer = new char[chunksize];

    string current;

    public LazyEnumerator(FileInfo file, Encoding encoding)
    {
      try
      {
        streamReader = new StreamReader(file.OpenRead(), encoding);
      }
      catch
      {
        // Catch some generator related exception
      }
    }

    #region IEnumerator<string> Members
    public string Current
    {
      get { return current; }
    }
    #endregion

    #region IEnumerator Members
    object System.Collections.IEnumerator.Current
    {
      get { return current; }
    }

    public bool MoveNext()
    {
      try
      {
        if (streamReader.Peek() >= 0)
        {
          int readCount = streamReader.Read(buffer, 0, chunksize);

          current = new string(buffer, 0, readCount);

          return true;
        }
        else
        {
          return false;
        }
      }
      catch
      {
        // Trap some iteration error
      }
    }

    public void Reset()
    {
      throw new NotSupportedException();
    }
    #endregion

    #region IDisposable Members
    private bool disposed = false;

    public void Dispose()
    {
      Dispose(true);

      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (!this.disposed)
      {
        if (disposing)
        {
          if (streamReader != null) streamReader.Dispose();
        }

        disposed = true;
      }
    }
    #endregion
  }
}

I didn't test this, but I think it's close.

used like this:

using (var fe = new LazyStream(new FileInfo("c:\\data.log"), Encoding.ASCII))
{
  foreach (var chunk in fe)
  {
    Console.WriteLine(chunk);
  }
}

EDIT: I had totally forgotten to add the try-catch block placements. Oops.

唱一曲作罢 2024-10-25 13:02:55

您不能在 try/catch 块中使用 yield 构造。将 try 块限制为可以抛出异常的代码,而不是全部。如果你做不到这一点,那你就运气不好了——你需要把它放在堆栈的更上方。

You can't use yield constructs in a try/catch block. Restrict the try block to code that can throw, not all of it. If you are unable to do this, you are out of luck - you'll need to catch it further up the stack.

土豪我们做朋友吧 2024-10-25 13:02:55

编辑 - 这个答案实际上是不正确的,由于评论中详细阐述的原因 - “仅包装了枚举器生成,而不是迭代本身。” - 但我将这个答案留在这里作为举个例子,有时看似有效的方法由于语言的复杂性而并非如此。

把它当作一个警示故事——我感谢 uosɐſ。 =)


这是一个选项 - 将您的方法分为两种方法,一种是公共方法,一种是私有方法。公共方法是对私有方法(即生成器)的调用的包装器(带有 try/catch)。例如:

public IEnumerable<string> YourFunction(...)
{
    try
    {
        return _yourFunction(...);
    }
    catch (Exception e)
    {
        throw ExceptionMapper.Map(e, file.FullName);
    }
}

private IEnumerable<string> _yourFunction(...)
{
    // Your code here
}

这将允许您的用户依赖具有内置异常处理功能的生成器。此外,您可以对公共方法中的输入执行更多验证,根据需要抛出由于错误输入而导致的任何异常,并在调用方法时立即执行这些验证,而不是等待第一次枚举可枚举项。

Edit - this answer is actually incorrect, due to the reasons elaborated on in the comments - "ONLY the enumerator generation is wrapped, but not the iteration itself." - but I am leaving this answer here as an example of how sometimes what may appear to work does not due to the intricacies of the language.

Consider it a cautionary tale - my thanks to uosɐſ. =)


Here's an option - separate your method into two methods, one public and one private. The public method is a wrapper (with try/catch) around a call to the private method, which is your generator. For example:

public IEnumerable<string> YourFunction(...)
{
    try
    {
        return _yourFunction(...);
    }
    catch (Exception e)
    {
        throw ExceptionMapper.Map(e, file.FullName);
    }
}

private IEnumerable<string> _yourFunction(...)
{
    // Your code here
}

This will allow your users to rely on the generator having built-in exception handling. Additionally you could perform more validation on your inputs in the public method, throwing any exceptions as needed due to bad inputs, and have those validations performed immediately when the method is called, rather than waiting for the first time the enumerable is enumerated.

东风软 2024-10-25 13:02:55

看看这个问题。在特殊情况下,您可以在 try/catch 子句之后使用 yield breakyield value。我担心性能,但据信 try 不会对性能产生影响,同时不会引发异常。

Take a look at this question. You can yield break in the exceptional case, yield value after the try/catch clause. I was concerned about performance, but there it is believed that try doesn't have a performance influence while no exceptions are thrown.

你是暖光i 2024-10-25 13:02:55

您可以考虑使用 System.Interactive 包的高级 LINQ 功能,特别是Catch抛出 运算符。只需将现有代码移动到 local function,并对本地函数的结果应用 Catch 运算符:

public IEnumerable<string> ReadPieces(int pieces)
{
    return Implementation(pieces)
        .Catch<string, Exception>(ex => EnumerableEx
            .Throw<string>(ExceptionMapper.Map(ex, file.FullName)));

    static IEnumerable<string> Implementation(int pieces)
    {
        /* Your existing code, without try/catch */
    }
}

Catch 运算符的签名:

// Creates a sequence that corresponds to the source sequence,
// concatenating it with the sequence resulting from calling
// an exception handler function in case of an error.
public static IEnumerable<TSource> Catch<TSource, TException>(
    this IEnumerable<TSource> source,
    Func<TException, IEnumerable<TSource>> handler)
    where TException : Exception;

Throw 的签名 运算符:

// Returns a sequence that throws an exception upon enumeration.
public static IEnumerable<TResult> Throw<TResult>(Exception exception);

上面的代码将无法编译,因为 file 变量不存在于 handler 委托的上下文中。如果必须以延迟方式构建文件,您可以使用Defer 运算符:

public IEnumerable<string> ReadPieces(int pieces)
{
    return EnumerableEx.Defer(() =>
    {
        FileInfo file = /* Initialize the variable */
        return Implementation(pieces, file)
            .Catch<string, Exception>(ex => EnumerableEx
                .Throw<string>(ExceptionMapper.Map(ex, file.FullName)));
    });

    static IEnumerable<string> Implementation(int pieces, FileInfo file)
    {
        /* Your existing code, without try/catch */
    }
}

如果您想抑制异常(而不是将其映射到不同的例外),请执行以下操作:

    .Catch<string, Exception>(ex => Enumerable.Empty<string>());

如果您的序列是异步的(IAsyncEnumerable),您可以找到相同的 Catch, 抛出Defer System.Interactive.Async 包。

You could consider using the advanced LINQ functionality of the System.Interactive package, and specifically the Catch and Throw operators. Just move your existing code in a local function, and apply the Catch operator on the result of the local function:

public IEnumerable<string> ReadPieces(int pieces)
{
    return Implementation(pieces)
        .Catch<string, Exception>(ex => EnumerableEx
            .Throw<string>(ExceptionMapper.Map(ex, file.FullName)));

    static IEnumerable<string> Implementation(int pieces)
    {
        /* Your existing code, without try/catch */
    }
}

The signature of the Catch operator:

// Creates a sequence that corresponds to the source sequence,
// concatenating it with the sequence resulting from calling
// an exception handler function in case of an error.
public static IEnumerable<TSource> Catch<TSource, TException>(
    this IEnumerable<TSource> source,
    Func<TException, IEnumerable<TSource>> handler)
    where TException : Exception;

The signature of the Throw operator:

// Returns a sequence that throws an exception upon enumeration.
public static IEnumerable<TResult> Throw<TResult>(Exception exception);

The above code will not compile, because the file variable does not exist in the context of the handler delegate. In case the file must be constructed in a deferred manner, you can use the Defer operator:

public IEnumerable<string> ReadPieces(int pieces)
{
    return EnumerableEx.Defer(() =>
    {
        FileInfo file = /* Initialize the variable */
        return Implementation(pieces, file)
            .Catch<string, Exception>(ex => EnumerableEx
                .Throw<string>(ExceptionMapper.Map(ex, file.FullName)));
    });

    static IEnumerable<string> Implementation(int pieces, FileInfo file)
    {
        /* Your existing code, without try/catch */
    }
}

In case you want to suppress the exception (instead of mapping it to a different exception), do this:

    .Catch<string, Exception>(ex => Enumerable.Empty<string>());

In case your sequence is asynchronous (IAsyncEnumerable<string>), you can find identical Catch, Throw and Defer operators for asynchronous sequences in the System.Interactive.Async package.

挥剑断情 2024-10-25 13:02:55

不幸的是,您还没有描述您想要做什么,但您可以尝试强制您定义的函数的用户尝试/捕获自己:

public IEnumerable<string> YourFunction(...)
{
    //Your code
}

//later:
    //...
    try{
        foreach( string s in YourFunction(file) )
        {
            //Do Work
        }
    }
    catch(Exception e){
        throw ExceptionMapper.Map(e, file.FullName);
    }

Unfortunately you haven't described what it is you want to do, but you could try just forcing users of the function you're defining to try/catch themselves:

public IEnumerable<string> YourFunction(...)
{
    //Your code
}

//later:
    //...
    try{
        foreach( string s in YourFunction(file) )
        {
            //Do Work
        }
    }
    catch(Exception e){
        throw ExceptionMapper.Map(e, file.FullName);
    }
几度春秋 2024-10-25 13:02:55

一种有效的策略(如果读起来有点混乱……)是分解并包装可能会绕过实际 yield return 调用的每个部分。这可以解决该问题,以便 yield 本身不在 try/catch 块中,但仍包含可能失败的部分。

这是您的代码的可能实现:

StreamReader stream = null;
char[] buffer = new char[chunksize];

try
{
    try
    {
        stream = new StreamReader(file.OpenRead(), Encoding);
    }
    catch (Exception ex)
    {
        throw ExceptionMapper.Map(ex, file.FullName);
    }

    int readCount;
    Func<bool> peek = () =>
    {
        try
        {
            return stream.Peek() >= 0;
        }
        catch (Exception ex)
        {
            throw ExceptionMapper.Map(ex, file.FullName);
        }
    };

    while (peek())
    {
        try
        {
            readCount = stream.Read(buffer, 0, chunksize);
        }
        catch (Exception ex)
        {
            throw ExceptionMapper.Map(ex, file.FullName);
        }

        yield return new string(buffer, 0, readCount);
    }
}
finally
{
    if (stream != null)
    {
        stream.Dispose();
        stream = null;
    }
}

One strategy is that effective (if a bit messier to read...) is to break out and wrap each section that might throw around the actual yield return call. This works around the issue so that the yield itself is not in a try/catch block, but the parts that could fail are still contained.

Here's a possible implementation of your code:

StreamReader stream = null;
char[] buffer = new char[chunksize];

try
{
    try
    {
        stream = new StreamReader(file.OpenRead(), Encoding);
    }
    catch (Exception ex)
    {
        throw ExceptionMapper.Map(ex, file.FullName);
    }

    int readCount;
    Func<bool> peek = () =>
    {
        try
        {
            return stream.Peek() >= 0;
        }
        catch (Exception ex)
        {
            throw ExceptionMapper.Map(ex, file.FullName);
        }
    };

    while (peek())
    {
        try
        {
            readCount = stream.Read(buffer, 0, chunksize);
        }
        catch (Exception ex)
        {
            throw ExceptionMapper.Map(ex, file.FullName);
        }

        yield return new string(buffer, 0, readCount);
    }
}
finally
{
    if (stream != null)
    {
        stream.Dispose();
        stream = null;
    }
}
寂寞美少年 2024-10-25 13:02:55

尝试在枚举器方法中使用本地函数:将 try..catch 的内容移动到本地函数,然后从 try..catch 中调用该函数。

使用您的示例:

public IEnumerable<string> YourFunction()
{
    // do stuff...

    try
    {
       // Move the try..catch content to the local function
       return getStrings()
    }
    catch (Exception ex)
    {
        throw ExceptionMapper.Map(ex, file.FullName)
    }

    // The local function
    IEnumerable<string> getStrings()
    {
       using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
       {
           char[] buffer = new char[chunksize];
           while (stream.Peek() >= 0)
           {
              int readCount = stream.Read(buffer, 0, chunksize);

              yield return new string(buffer, 0, readCount);
           }
        }
    }

}

在很多情况下,使用本地函数实际上是一个好主意。使用此模式可以强制该方法立即验证参数,而不是等到调用者开始枚举(Roslyn 警告 RCS1227)。

Try using a local function within the enumerator method: move the contents of the try..catch to the local function, then call the function from within the try..catch.

Using your example:

public IEnumerable<string> YourFunction()
{
    // do stuff...

    try
    {
       // Move the try..catch content to the local function
       return getStrings()
    }
    catch (Exception ex)
    {
        throw ExceptionMapper.Map(ex, file.FullName)
    }

    // The local function
    IEnumerable<string> getStrings()
    {
       using (StreamReader stream = new StreamReader(file.OpenRead(), Encoding))
       {
           char[] buffer = new char[chunksize];
           while (stream.Peek() >= 0)
           {
              int readCount = stream.Read(buffer, 0, chunksize);

              yield return new string(buffer, 0, readCount);
           }
        }
    }

}

Using a local function is actually a good idea in a lot of cases. Using this pattern can force the method to validate arguments immediately instead of waiting until the caller begins enumeration (Roslyn warning RCS1227).

猫弦 2024-10-25 13:02:55

尝试这种方法:

public IEnumerable<ReturnData> toto()
{
    using (StreamReader stream = new StreamReader(File.OpenRead(""), Encoding.UTF8))
    {
        char[] buffer = new char[1];
        while (stream.Peek() >= 0)
        {
            ReturnData result;
            try
            {
                int readCount = stream.Read(buffer, 0, 1);
                result = new ReturnData(new string(buffer, 0, readCount));
            }
            catch (Exception exc)
            {
                result = new ReturnData(exc);
            }
            yield return result;
        }
    }
}

public class ReturnData
{
    public string Data { get; private set; }
    public Exception Error { get; private set; }
    public bool HasError { get { return Error != null; } }
    public ReturnData(string data)
    {
        this.Data = data;
    }
    public ReturnData(Exception exc)
    {
        this.Error = exc;
    }
}

您只需要小心使用这种方法:您必须根据严重性过滤异常。有些异常必须停止整个过程,而其他异常则可以跳过并记录。

try this approach :

public IEnumerable<ReturnData> toto()
{
    using (StreamReader stream = new StreamReader(File.OpenRead(""), Encoding.UTF8))
    {
        char[] buffer = new char[1];
        while (stream.Peek() >= 0)
        {
            ReturnData result;
            try
            {
                int readCount = stream.Read(buffer, 0, 1);
                result = new ReturnData(new string(buffer, 0, readCount));
            }
            catch (Exception exc)
            {
                result = new ReturnData(exc);
            }
            yield return result;
        }
    }
}

public class ReturnData
{
    public string Data { get; private set; }
    public Exception Error { get; private set; }
    public bool HasError { get { return Error != null; } }
    public ReturnData(string data)
    {
        this.Data = data;
    }
    public ReturnData(Exception exc)
    {
        this.Error = exc;
    }
}

You just have to be careful with this approach: you will have to filter exceptions based on the severity. Some exceptions will have to stop the whole process, others just can be skipped and logged.

地狱即天堂 2024-10-25 13:02:55

另一个考虑因素 - 如果您使用实现内部引发异常的 IEnumerable 方法实现 yield,则无法捕获该单个错误并继续枚举 - 请参阅“ https://msdn.microsoft.com/en-us/ 的“异常处理”部分Library/9k7k7cf0.aspx

示例:

void Main()
{
    // even is okay, odd will cause exception
    var operations = new[] { 2, 16, 5 /* ! */, 8, 91 /* ! */ };

    var results = process(operations);
    var en = results.GetEnumerator();

    "Regular Enumeration".Title();
    testEnumeration(en);

    results = process(operations, ex => log("Handled: {0}", ex.Message));
    en = results.GetEnumerator();   

    "Handled Exceptions".Title();
    testEnumeration(en);

    results = process(operations, ex => log("Handled+: {0}", ex.Message), true);
    en = results.GetEnumerator();   

    "Handled Exceptions and Continue".Title();
    testEnumeration(en);
}

/// run the test and debug results
void testEnumeration(IEnumerator en) {
    int successCount = 0, failCount = 0;
    bool keepGoing = false;
    do {
        try {
            log("==={0}===", "before next");
            keepGoing = en.MoveNext();
            log("==={0}=== (keepGoing={1}, curr={2})", "after next", keepGoing, en.Current);

            // did we have anything?
            if(keepGoing) {
                var curr = en.Current;
                log("==={0}===", "after curr");

                log("We did it? {0}", curr);
                successCount++;
            }
        }
        catch(InvalidOperationException iopex) {
            log(iopex.Message);
            failCount++;
        }
    } while(keepGoing);

    log("Successes={0}, Fails={1}", successCount, failCount);
}

/// enumerable that will stop completely on errors
IEnumerable<int> process(IEnumerable<int> stuff) {
    foreach(var thing in stuff) {
        if(thing % 2 == 1) {
            throw new InvalidOperationException("Aww, you broked it");
        }

        yield return thing;
    }
}
/// enumerable that can yield from exceptions
IEnumerable<int> process(IEnumerable<int> stuff, Action<Exception> handleException, bool yieldOnExceptions = false) {
    bool shouldYield = false;
    foreach(var thing in stuff) {
        var result = thing;
        try {
            if(thing % 2 == 1) {
                throw new InvalidOperationException("Aww, you broked it");
            }

            shouldYield = true;
        }
        catch(Exception ex) {
            handleException(ex);
            // `yield break` to stop loop
            shouldYield = yieldOnExceptions;
            if(yieldOnExceptions) result = -1; // so it returns a different result you could interpret differently
        }
        if(shouldYield) yield return result;
    }
}

void log(string message, params object[] tokens) {
    Console.WriteLine(message, tokens);
}

结果为

 常规枚举    

---------------------------- 
===下一个之前===
===下一个之后===(keepGoing=True,curr=2)
===当前之后===
我们做到了? 2
===下一个之前===
===下一个之后===(keepGoing=True,curr=16)
===当前之后===
我们做到了? 16
===下一个之前===
哇哦,你把它弄坏了
===下一个之前===
===下一个之后===(keepGoing=False,curr=16)
成功=2,失败=1


    已处理的异常    

-------------------------- 
===下一个之前===
===下一个之后===(keepGoing=True,curr=2)
===当前之后===
我们做到了? 2
===下一个之前===
===下一个之后===(keepGoing=True,curr=16)
===当前之后===
我们做到了? 16
===下一个之前===
处理:哇哦,你把它弄坏了
===下一个之后===(keepGoing=True,curr=8)
===当前之后===
我们做到了? 8
===下一个之前===
处理:哇哦,你把它弄坏了
===下一个之后===(keepGoing=False,curr=8)
成功=3,失败=0


    处理异常并继续    

--------------------------------------- 
===下一个之前===
===下一个之后===(keepGoing=True,curr=2)
===当前之后===
我们做到了? 2
===下一个之前===
===下一个之后===(keepGoing=True,curr=16)
===当前之后===
我们做到了? 16
===下一个之前===
Handled+:哇哦,你把它弄坏了
===下一个之后===(keepGoing=True,curr=-1)
===当前之后===
我们做到了? -1
===下一个之前===
===下一个之后===(keepGoing=True,curr=8)
===当前之后===
我们做到了? 8
===下一个之前===
Handled+:哇哦,你把它弄坏了
===下一个之后===(keepGoing=True,curr=-1)
===当前之后===
我们做到了? -1
===下一个之前===
===下一个之后===(keepGoing=False,curr=-1)
成功=5,失败=0

请注意,枚举器的 Current 在“常规枚举”期间“卡在”最后一次成功的 MoveNext 上,而处理的异常允许它完成循环。

Another consideration -- if you're consuming an IEnumerable method implementing yield that internally throws an exception, you can't catch that individual error and continue enumerating -- see the "Exception Handling" section of https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx

example:

void Main()
{
    // even is okay, odd will cause exception
    var operations = new[] { 2, 16, 5 /* ! */, 8, 91 /* ! */ };

    var results = process(operations);
    var en = results.GetEnumerator();

    "Regular Enumeration".Title();
    testEnumeration(en);

    results = process(operations, ex => log("Handled: {0}", ex.Message));
    en = results.GetEnumerator();   

    "Handled Exceptions".Title();
    testEnumeration(en);

    results = process(operations, ex => log("Handled+: {0}", ex.Message), true);
    en = results.GetEnumerator();   

    "Handled Exceptions and Continue".Title();
    testEnumeration(en);
}

/// run the test and debug results
void testEnumeration(IEnumerator en) {
    int successCount = 0, failCount = 0;
    bool keepGoing = false;
    do {
        try {
            log("==={0}===", "before next");
            keepGoing = en.MoveNext();
            log("==={0}=== (keepGoing={1}, curr={2})", "after next", keepGoing, en.Current);

            // did we have anything?
            if(keepGoing) {
                var curr = en.Current;
                log("==={0}===", "after curr");

                log("We did it? {0}", curr);
                successCount++;
            }
        }
        catch(InvalidOperationException iopex) {
            log(iopex.Message);
            failCount++;
        }
    } while(keepGoing);

    log("Successes={0}, Fails={1}", successCount, failCount);
}

/// enumerable that will stop completely on errors
IEnumerable<int> process(IEnumerable<int> stuff) {
    foreach(var thing in stuff) {
        if(thing % 2 == 1) {
            throw new InvalidOperationException("Aww, you broked it");
        }

        yield return thing;
    }
}
/// enumerable that can yield from exceptions
IEnumerable<int> process(IEnumerable<int> stuff, Action<Exception> handleException, bool yieldOnExceptions = false) {
    bool shouldYield = false;
    foreach(var thing in stuff) {
        var result = thing;
        try {
            if(thing % 2 == 1) {
                throw new InvalidOperationException("Aww, you broked it");
            }

            shouldYield = true;
        }
        catch(Exception ex) {
            handleException(ex);
            // `yield break` to stop loop
            shouldYield = yieldOnExceptions;
            if(yieldOnExceptions) result = -1; // so it returns a different result you could interpret differently
        }
        if(shouldYield) yield return result;
    }
}

void log(string message, params object[] tokens) {
    Console.WriteLine(message, tokens);
}

results in

    Regular Enumeration    

--------------------------- 
===before next===
===after next=== (keepGoing=True, curr=2)
===after curr===
We did it? 2
===before next===
===after next=== (keepGoing=True, curr=16)
===after curr===
We did it? 16
===before next===
Aww, you broked it
===before next===
===after next=== (keepGoing=False, curr=16)
Successes=2, Fails=1


    Handled Exceptions    

-------------------------- 
===before next===
===after next=== (keepGoing=True, curr=2)
===after curr===
We did it? 2
===before next===
===after next=== (keepGoing=True, curr=16)
===after curr===
We did it? 16
===before next===
Handled: Aww, you broked it
===after next=== (keepGoing=True, curr=8)
===after curr===
We did it? 8
===before next===
Handled: Aww, you broked it
===after next=== (keepGoing=False, curr=8)
Successes=3, Fails=0


    Handled Exceptions and Continue    

--------------------------------------- 
===before next===
===after next=== (keepGoing=True, curr=2)
===after curr===
We did it? 2
===before next===
===after next=== (keepGoing=True, curr=16)
===after curr===
We did it? 16
===before next===
Handled+: Aww, you broked it
===after next=== (keepGoing=True, curr=-1)
===after curr===
We did it? -1
===before next===
===after next=== (keepGoing=True, curr=8)
===after curr===
We did it? 8
===before next===
Handled+: Aww, you broked it
===after next=== (keepGoing=True, curr=-1)
===after curr===
We did it? -1
===before next===
===after next=== (keepGoing=False, curr=-1)
Successes=5, Fails=0

Note that the enumerator's Current is "stuck" on the last successful MoveNext during "regular enumeration", whereas the handled exceptions allows it to complete the loop.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文