yield return with try catch, how can i solve it

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.


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.


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

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.