我有一段代码:
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()
答案 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)。