Search code examples
c#.net-3.5try-catchyield-return

Yield return with try catch, how can I solve it?


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()

Solution

  • 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.