伝えたかった事とずれまくっていたので書き直しです。
今回は yield
を使用したソースがターゲットです。
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main( string[ ] args )
{
try { Yield( ); Console.WriteLine( "Yield: 通過" ); }
catch { Console.WriteLine( "Yield: エラー" ); }
try { NonYield( ); Console.WriteLine( "NonYield: 通過" ); }
catch { Console.WriteLine( "NonYield: エラー" ); }
}
static IEnumerable<int> Yield( )
{
throw new InvalidOperationException( );
yield return 0;
}
static IEnumerable<int> NonYield( )
{
throw new InvalidOperationException( );
return new[ ] { 0 };
}
}
}
結果は次の通りです。
Yield: 通過
NonYield: エラー
('ω')
結果を見て分かるように、Yield
メソッドでは例外が発生しませんでした。
前回同様に逆コンパイルしてコードを見てみます。
[CompilerGenerated]
private sealed class <Yield>d__0
: IEnumerable<int>, IEnumerable,
IEnumerator<int>, IEnumerator, IDisposable
{
// 省略
}
private static void Main( string[ ] args )
{
// 省略
}
private static IEnumerable<int> Yield( )
{
return new Program.<Yield>d__0( -2 );
}
private static IEnumerable<int> NonYield( )
{
throw new InvalidOperationException( );
}
('ω') (d__0)
( 'ω')
ビルドは Debug ですが、 return
まで到達しないので NonYield
は throw
文のみに
なっています。Yield
メソッドは代わりにインスタンスを返却するコードに変換されています。
このクラスはコンパイラが作成したもので、実装(MoveNext
メソッド)に元々記述していた
処理(throw
文)が含まれています。
コンパイラは yield
を指定した時点で 対象メンバの処理 をすべて移すようで、
今回記述した throw
文もこの中に移動されていました。
// d__0 クラス内
bool IEnumerator.MoveNext( )
{
int num = this.<>1__state;
if ( num != 0 )
{
return false;
}
this.<>1__state = -1;
throw new InvalidOperationException( );
}
ようするに IEnumerable
なメンバの処理に yield
を使用すると、コンパイラ製クラスの
インスタンス作成&返却 という処理に置き換えられるため、throw
文は反復処理が
開始されるまで実行されないということでした。
元々 return
で書いていたメンバの処理を yield return
に置き換えたり、
yield return
で書いていたメンバの処理を LINQ に置き換えたりすることは、今回のように
評価タイミングにズレが生じる結果になるので、周辺コードに注意することをお勧めします。
! 条件が Code Contracts で記述された場合は結果は異なります。
以下ソースコードと実行結果&逆コンパイルで終わり。
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace ConsoleApplication1
{
class Program
{
static void Main( string[ ] args )
{
try { Yield( ); Console.WriteLine( "Yield: 通過" ); }
catch { Console.WriteLine( "Yield: エラー" ); }
try { NonYield( ); Console.WriteLine( "NonYield: 通過" ); }
catch { Console.WriteLine( "NonYield: エラー" ); }
Console.ReadKey( );
}
static IEnumerable<int> Yield( )
{
Contract.Requires<InvalidOperationException>( false );
yield return 0;
}
static IEnumerable<int> NonYield( )
{
Contract.Requires<InvalidOperationException>( false );
return new[ ] { 0 };
}
}
}
実行結果
Yield: エラー
NonYield: エラー
逆コンパイル
private static IEnumerable<int> Yield( )
{
__ContractsRuntime.Requires<InvalidOperationException>( false, null, "false" );
return new Program.<Yield>d__0( -2 );
}
private static IEnumerable<int> NonYield( )
{
__ContractsRuntime.Requires<InvalidOperationException>( false, null, "false" );
return new int[ 1 ];
}