【问题标题】:yield return with try catch, how can i solve it使用try catch产生返回,我该如何解决
【发布时间】:2011-07-01 07:57:47
【问题描述】:

我有一段代码:

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

【问题讨论】:

  • 如果您能更详细地描述您想要做什么,将会很有帮助。显然这种方法是行不通的,所以不得不改代码。但是我们应该如何猜测您的限制是什么?异常处理是您必须仔细考虑的事情。没有什么可以添加的。

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


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    一个策略是有效的(如果阅读有点混乱......)是打破并包装每个可能会抛出实际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;
        }
    }
    

    【讨论】:

      【解决方案3】:

      另一个考虑因素——如果你正在使用一个实现 yieldIEnumerable 方法,该方法在内部抛出异常,你无法捕获那个单独的错误并继续枚举——请参阅 @987654321 的“异常处理”部分@

      示例:

      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
      

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

      【讨论】:

      • 如果你返回一个已知的“忽略”值,你总是可以从你的结果集中忽略它:process(operations, ex =&gt; handle(ex), true).Where(result =&gt; result != -1)
      【解决方案4】:

      编辑 - 由于 cmets 中详述的原因,这个答案实际上是不正确的 - “只有枚举器生成被包装,而不是迭代本身。” - 但我要离开这个答案这里举个例子,说明由于语言的复杂性,有时可能看起来有效的方法并不奏效。

      认为这是一个警示故事 - 感谢 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
      }
      

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

      【讨论】:

      • 这很好,而且都是真的,但我认为对读者来说重要的是要注意只有枚举器生成被包装,而不是迭代本身。如果文件在迭代过程中突然不可用或无法打开,则会向消费代码抛出异常。如果您尝试在生成器中打开流,那么您将没有机会在其作用域结束时正确处理它。
      • @uos - 啊,你是对的。私有生成器中预编译代码的命令性质使我相信包装方法中的 try/catch 将处理从生成器抛出的异常。我应该知道的更好。 =P
      • 是的,我以前就爱过这个 :) 外部尝试永远不会捕获,因为返回了 IEnumerable。在枚举之前什么都不会抛出 - 这不会执行包装器方法。
      【解决方案5】:

      这是一个代码 sn-p,它适用于我(我没有达到错误条件)。

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

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

        • 注意:这可能不应该是公认的答案,但它很有趣。正如 Jason 所说的“哎呀”,这不会让您按照 OP 捕获和映射异常。 throw ExceptionMapper.Map(ex, file.FullName)
        【解决方案7】:

        试试这个方法:

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

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

        【讨论】:

        • 在 catch 内抛出问题不是问题 - 你根本无法退出 try/catch 块。 C# 编译器不允许这样做。
        • 打开流也可能出错,他正试图捕捉。为了在处理 Stream 的同时单独处理该错误,您根本不能使用迭代器块。
        【解决方案8】:

        看看this question。在特殊情况下,您可以yield break yield valuetry/catch 子句之后。我担心性能,但据信try不会对性能产生影响,同时不会引发异常。

        【讨论】:

          【解决方案9】:

          很遗憾,您没有描述想要做什么,但您可以尝试强制您定义的功能的用户尝试/捕捉自己:

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 支持超明智的“将 try 块限制为可以抛出的代码..”
            • 这里值得注意的是,如果您只是将可能抛出的代码移动到另一个方法并尝试包装对该方法的调用,您可能会不会捕获错误,因为生成器是如何异步执行的。换句话说,代码在抛出时已经移出 try/catch 块,因此捕获块不再在实际接收错误的范围内。
            猜你喜欢
            • 1970-01-01
            • 2020-02-11
            • 2018-11-05
            • 1970-01-01
            • 2014-04-27
            • 1970-01-01
            • 2020-01-02
            • 2022-11-03
            相关资源
            最近更新 更多