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];
开发者_StackOverflow 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.
EDIT 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()
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;
}
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.
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.
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.
Take a look at this question. You can yield break
in the exceptional case, yield value
after the try/catch
clause. I was concerned about performance, but there it is believed that try
doesn't have a performance influence while no exceptions are thrown.
Unfortunately you haven't described what it is you want to do, but you could try just forcing users of the function you're defining to try/catch themselves:
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);
}
One strategy is that effective (if a bit messier to read...) is to break out and wrap each section that might throw around the actual yield return
call. This works around the issue so that the yield
itself is not in a try/catch block, but the parts that could fail are still contained.
Here's a possible implementation of your code:
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;
}
}
Try using a local function within the enumerator method: move the contents of the try..catch to the local function, then call the function from within the try..catch.
Using your example:
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);
}
}
}
}
Using a local function is actually a good idea in a lot of cases. Using this pattern can force the method to validate arguments immediately instead of waiting until the caller begins enumeration (Roslyn warning RCS1227).
try this approach :
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;
}
}
You just have to be careful with this approach: you will have to filter exceptions based on the severity. Some exceptions will have to stop the whole process, others just can be skipped and logged.
Another consideration -- if you're consuming an IEnumerable
method implementing yield
that internally throws an exception, you can't catch that individual error and continue enumerating -- see the "Exception Handling" section of https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx
example:
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);
}
results in
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
Note that the enumerator's Current
is "stuck" on the last successful MoveNext
during "regular enumeration", whereas the handled exceptions allows it to complete the loop.
精彩评论