Why Can't Yield Return Appear Inside a Try Block with a Catch

Why can't yield return appear inside a try block with a catch?

I suspect this is a matter of practicality rather than feasibility. I suspect there are very, very few times where this restriction is actually an issue that can't be worked around - but the added complexity in the compiler would be very significant.

There are a few things like this that I've already encountered:

  • Attributes not being able to be generic
  • Inability for X to derive from X.Y (a nested class in X)
  • Iterator blocks using public fields in the generated classes

In each of these cases it would be possible to gain a little bit more freedom, at the cost of extra complexity in the compiler. The team made the pragmatic choice, for which I applaud them - I'd rather have a slightly more restrictive language with a 99.9% accurate compiler (yes, there are bugs; I ran into one on SO just the other day) than a more flexible language which couldn't compile correctly.

EDIT: Here's a pseudo-proof of how it why it's feasible.

Consider that:

  • You can make sure that the yield return part itself doesn't throw an exception (precalculate the value, and then you're just setting a field and returning "true")
  • You're allowed try/catch which doesn't use yield return in an iterator block.
  • All local variables in the iterator block are instance variables in the generated type, so you can freely move code to new methods

Now transform:

try
{
Console.WriteLine("a");
yield return 10;
Console.WriteLine("b");
}
catch (Something e)
{
Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

into (sort of pseudo-code):

case just_before_try_state:
try
{
Console.WriteLine("a");
}
catch (Something e)
{
CatchBlock();
goto case post;
}
__current = 10;
return true;

case just_after_yield_return:
try
{
Console.WriteLine("b");
}
catch (Something e)
{
CatchBlock();
}
goto case post;

case post;
Console.WriteLine("Post");

void CatchBlock()
{
Console.WriteLine("Catch block");
}

The only duplication is in setting up try/catch blocks - but that's something the compiler can certainly do.

I may well have missed something here - if so, please let me know!

yield return with try catch, how can i solve it

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.

yield return' statement can't appear in try/catch block constraint

You can get the answer to this question in Eric Lippert's series on Iterator Blocks. Specifically check out the post titled Iterator Blocks, Part Five: Push vs Pull. The seven-part series starts with Iterator Blocks, Part One.

Use yield in try catch in C#

When you use yield, the compiler generates an entire class to handle the requirement of the Iterator pattern.

When you look at what is generated, you will see that the compiler wraps calls to the MoveNext() and Current methods in the generated class in a try..finally block. The requirement is no doubt because of the try..finally spaghetti that will result, possibly preventing Dispose() being called on the generated class.. which would be a problem worth avoiding. It would definitely add complexities into the compiler for guaranteeing certain conditions such as the above one.

Thanks to Chris (in the comments to this post) who shared Eric Lippert's blog post on this exact issue: http://blogs.msdn.com/b/ericlippert/archive/2009/07/16/iterator-blocks-part-three-why-no-yield-in-finally.aspx

Pretty much what I figured.. but explained much better than I did :)

Wrapping call to iterator in try/catch when using yield

Both the answers here were correct. There's no built-in shortcut for this one, you need to tease apart the iterator in a while rather than a for loop in order to separate between the call to Enumerator.MoveNext() and the use of Enumerator.Current.

IEnumerator<Things> iterator = Helpful.GetMoreThings.GetEnumerator();
bool more = true;

while (more) {
try {
more = iterator.MoveNext();
}
catch (Exception e) {
//crash, burn
}

if (more)
yield return iterator.Current;
}

How to return IEnumerable object in try catch block?

As Jamiec mentioned, you should be able to omit the 'return null'
Unauthorized exception is usually a filesystem error, so grab all your files before hand.
Also the second try catch block in case your processing goes wrong

public IEnumerable<DataFiles> GetFiles(string path)
{
var listOfFiles = new List<string>();
try
{
listOfFiles.AddRange(System.IO.Directory.GetFiles(path, "*.json", System.IO.SearchOption.AllDirectories));
listOfFiles.AddRange(System.IO.Directory.GetFiles(path, "*.txt", System.IO.SearchOption.AllDirectories));
}
catch (UnauthorizedAccessException ex)
{
// log error here
}
string fileName, fileContent;
int md5HashValue; // im assuming this is an it, not sure
//byte[] md5HashValue; // not sure
for (int i = 0; i < listOfFiles.Count; i++)
{
try
{
var cfgPath = listOfFiles[i];
if (!System.IO.File.Exists(cfgPath)) { continue; }
fileContent = System.IO.File.ReadAllText(cfgPath);
var pieces = cfgPath.Split(System.IO.Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
fileName = pieces[pieces.Length - 1];
md5HashValue = GetMD5Hash(cfgPath);
}
catch (Exception ex)
{
// log other error here
continue;
}
// error on below line
yield return new DataFiles
{
FileName = fileName,
FileContent = fileContent,
MD5Hash = md5HashValue
};
}

}

Try block continues after catch

Not

throw new std::out_of_range("Invalid index!");

but

throw std::out_of_range("Invalid index!");

To elaborate in your code you are catching type std::out_of_range& but throwing type std::out_of_range*. And as is pointed out in the comments there's no good reason not to catch const std::out_of_range&

Exception thrown in catch and finally clause

Based on reading your answer and seeing how you likely came up with it, I believe you think an "exception-in-progress" has "precedence". Keep in mind:

When an new exception is thrown in a catch block or finally block that will propagate out of that block, then the current exception will be aborted (and forgotten) as the new exception is propagated outward. The new exception starts unwinding up the stack just like any other exception, aborting out of the current block (the catch or finally block) and subject to any applicable catch or finally blocks along the way.

Note that applicable catch or finally blocks includes:

When a new exception is thrown in a catch block, the new exception is still subject to that catch's finally block, if any.

Now retrace the execution remembering that, whenever you hit throw, you should abort tracing the current exception and start tracing the new exception.



Related Topics



Leave a reply



Submit