使用try catch返回yield,我该如何解决它

时间:2011-02-21 14:33:07

标签: c# .net-3.5 try-catch yield-return

我有一段代码:

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来调用ExceptionMapper案例中的catch。 所有呼叫者都推迟使用该方法。

我必须捕获的例外来自这些电话

File.OpenRead()
stream.Read()

10 个答案:

答案 0 :(得分:27)

这是一个代码片段,对我有用(我没有达到错误条件)。

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;
}

答案 1 :(得分:14)

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

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

通过自己实现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块放置。糟糕。

答案 2 :(得分:8)

您不能在try / catch块中使用yield构造。将try块限制为可以抛出的代码,而不是全部代码。如果你不能做到这一点,那你就不走运了 - 你需要进一步抓住它。

答案 3 :(得分:8)

编辑 - 这个答案实际上是不正确的,因为评论中详细说明了原因 - “只有枚举器生成被包装,而不是迭代本身。” - 但我要离开这个答案这里作为一个例子,说明有时可能看起来有效的东西并不是由于语言的错综复杂。

认为这是一个警示故事 - 我要感谢你们。 =)


这是一个选项 - 将您的方法分为两种方法,一种是公共方法,另一种是私有方式。 public方法是一个包装器(使用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
}

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

答案 4 :(得分:5)

看看this question。在yield break子句之后的例外情况yield value中,您可以try/catch。我担心性能,但据信try没有性能影响而没有抛出异常。

答案 5 :(得分:1)

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

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);
    }

答案 6 :(得分:0)

尝试这种方法:

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;
    }
}

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

答案 7 :(得分:0)

另一个考虑因素 - 如果您正在使用内部引发异常的实现IEnumerable的{​​{1}}方法,则无法捕获该错误并继续枚举 - 请参阅&#34;异常处理&#34; https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx

的部分

示例:

yield

结果

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);
}

请注意,调查员的 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 是&#34;卡住&#34;在&#34;常规枚举&#34;期间的最后一次成功Current,而处理的异常允许它完成循环。

答案 8 :(得分:0)

一种策略是有效(如果读取有点麻烦......)是打破并包装可能抛出实际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;
    }
}

答案 9 :(得分:0)

尝试在枚举器方法中使用本地函数:将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 warning RCS1227)。