私はコードの一部を持っています:
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)
}
自分のやりたいことをする方法がわかりません。
[〜#〜] edit [〜#〜]メソッドにはシグネチャがあります
public IEnumerable<string> ReadPieces(int pieces)
私は〜が必要です try catch
ExceptionMapper
の場合はcatch
を呼び出します。このメソッドは、すべての呼び出し元によって据え置かれて使用されます。
私がキャッチしなければならない例外はこれらの呼び出しから来ています
File.OpenRead()
stream.Read()
列挙の継続中、ストリームを開いたままにし、例外を処理し、ファイルハンドルを適切に閉じる必要があるため、通常の列挙ショートカット(反復子ブロック、yield-return/yield-)を使用できないと思います。ブレーク)。
代わりに、コンパイラーが行うことと同じことを行い、いくつかを追加します。
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ブロックの配置を追加するのを忘れていました。おっとっと。
これは私のために働くコードスニペットです(私はエラー状態に達しませんでした)。
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;
}
編集-コメントで詳しく説明されている理由により、この回答は実際には正しくありません-"列挙子の生成のみがラップされますが、反復自体はラップされません。"-しかし、ここでこの回答を残します言語の複雑さのために、機能しているように見えることがあるのに、そうでない場合がある例。
それを警告的な話と考えてください-uosɐſに感謝します。 =)
ここにオプションがあります-メソッドを2つのメソッドに分けます。1つはパブリック、もう1つはプライベートです。 publicメソッドは、ジェネレーターであるprivateメソッドの呼び出しのラッパー(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
}
これにより、ユーザーは組み込みの例外処理機能を持つジェネレーターに依存することができます。さらに、パブリックメソッドで入力に対してより多くの検証を実行し、不正な入力が原因で必要に応じて例外をスローし、メソッドが呼び出されたときに、列挙型が初めて列挙されるのを待つのではなく、それらの検証をすぐに実行することができます。
Try/catchブロックでyield
構造を使用することはできません。 tryブロックを、すべてではなく、スローできるコードに制限します。これを行うことができない場合、あなたは運が悪いです-あなたはそれをさらにスタックに追いつく必要があります。
この質問 を見てください。あなたはできる yield break
例外的なケースでは、yield value
後に try/catch
句。パフォーマンスについて心配していましたが、例外がスローされない間はtry
はパフォーマンスに影響を与えないと考えられます。
残念ながら、あなたはそれが何であるかを説明していませんwant実行する必要がありますが、定義している関数のユーザーに自分で試行/キャッチするよう強制することもできます。
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);
}
このアプローチを試してください:
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;
}
}
このアプローチには注意が必要です。重大度に基づいて例外をフィルタリングする必要があります。一部の例外ではプロセス全体を停止する必要がありますが、その他の例外はスキップしてログに記録することができます。
効果的な方法の1つは(実際の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;
}
}
別の考慮事項-内部で例外をスローするIEnumerable
を実装するyield
メソッドを使用している場合、個々のエラーをキャッチして列挙を続けることはできません-「例外処理」セクションを参照してください https://msdn.Microsoft.com/en-us/library/9k7k7cf0.aspx
例:
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
で「スタック」しますが、処理された例外によってループを完了することができます。
列挙子メソッド内でローカル関数を使用してみてください。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警告RCS1227 )。