[解決済み] この文字列拡張メソッドはなぜ例外を投げないのですか?
質問
C# の文字列拡張メソッドで
IEnumerable<int>
を返す C# 文字列拡張メソッドがあります。それは意図された目的のために完全に動作し、期待される結果が返されます (以下のテストではなく、私のテストの 1 つによって証明されます) が、別のユニット テストはそれに関する問題を発見しました。
私がテストしている拡張メソッドは以下のとおりです。
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
if (searchText == null)
{
throw new ArgumentNullException("searchText");
}
for (int index = 0; ; index += searchText.Length)
{
index = str.IndexOf(searchText, index);
if (index == -1)
break;
yield return index;
}
}
以下は、問題点を指摘したテストです。
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Extensions_AllIndexesOf_HandlesNullArguments()
{
string test = "a.b.c.d.e";
test.AllIndexesOf(null);
}
私の拡張メソッドに対してテストを実行すると、メソッド "例外をスローしませんでした"という標準エラーメッセージとともに、失敗します。
これは紛らわしいです。私は明らかに
null
を関数に渡したのに、なぜか比較は
null == null
が返されます。
false
. したがって、例外はスローされず、コードは続行されます。
私はこれがテストのバグでないことを確認しました。
Console.WriteLine
を呼び出すと、ヌル比較の
if
ブロックでは、コンソールに何も表示されず、例外も発生しません。
catch
ブロックを追加しても例外は発生しません。さらに
string.IsNullOrEmpty
の代わりに
== null
も同じ問題があります。
この単純なはずの比較はなぜ失敗するのでしょうか?
どうすれば解決するのか?
あなたが使用している
yield return
. そうすると、コンパイラはあなたのメソッドを、ステートマシンを実装した生成クラスを返す関数に書き換えてくれます。
大雑把に言うと、そのクラスのフィールドへのローカルと、アルゴリズムの各パーツが
yield return
命令の間にあるアルゴリズムの各部分がステートになります。このメソッドがコンパイル後にどうなるかはデコンパイラで確認できます(スマートデコンパイルをオフにすると
yield return
).
でも、肝心なのは は、反復処理を開始するまで、メソッドのコードは実行されません。
前提条件を確認する通常の方法は、メソッドを2つに分割することです。
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
if (str == null)
throw new ArgumentNullException("str");
if (searchText == null)
throw new ArgumentNullException("searchText");
return AllIndexesOfCore(str, searchText);
}
private static IEnumerable<int> AllIndexesOfCore(string str, string searchText)
{
for (int index = 0; ; index += searchText.Length)
{
index = str.IndexOf(searchText, index);
if (index == -1)
break;
yield return index;
}
}
これは、最初のメソッドが期待通りの動作(即時実行)をし、2番目のメソッドで実装されたステートマシンを返すので、うまくいくのです。
また
str
のパラメータも確認する必要があります。
null
というのは、エクステンションメソッド
は
を呼び出すことができます。
null
の値に対して呼び出すことができます。これは単なる構文上の糖分です。
コンパイラがあなたのコードに何をするのか興味があるのなら、あなたのメソッドを コンパイラで生成されたコードを表示する オプションを使用してドットピークでデコンパイルしたものです。
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
Test.<AllIndexesOf>d__0 allIndexesOfD0 = new Test.<AllIndexesOf>d__0(-2);
allIndexesOfD0.<>3__str = str;
allIndexesOfD0.<>3__searchText = searchText;
return (IEnumerable<int>) allIndexesOfD0;
}
[CompilerGenerated]
private sealed class <AllIndexesOf>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
private int <>2__current;
private int <>1__state;
private int <>l__initialThreadId;
public string str;
public string <>3__str;
public string searchText;
public string <>3__searchText;
public int <index>5__1;
int IEnumerator<int>.Current
{
[DebuggerHidden] get
{
return this.<>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden] get
{
return (object) this.<>2__current;
}
}
[DebuggerHidden]
public <AllIndexesOf>d__0(int <>1__state)
{
base..ctor();
this.<>1__state = param0;
this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
}
[DebuggerHidden]
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
Test.<AllIndexesOf>d__0 allIndexesOfD0;
if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
{
this.<>1__state = 0;
allIndexesOfD0 = this;
}
else
allIndexesOfD0 = new Test.<AllIndexesOf>d__0(0);
allIndexesOfD0.str = this.<>3__str;
allIndexesOfD0.searchText = this.<>3__searchText;
return (IEnumerator<int>) allIndexesOfD0;
}
[DebuggerHidden]
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
}
bool IEnumerator.MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
if (this.searchText == null)
throw new ArgumentNullException("searchText");
this.<index>5__1 = 0;
break;
case 1:
this.<>1__state = -1;
this.<index>5__1 += this.searchText.Length;
break;
default:
return false;
}
this.<index>5__1 = this.str.IndexOf(this.searchText, this.<index>5__1);
if (this.<index>5__1 != -1)
{
this.<>2__current = this.<index>5__1;
this.<>1__state = 1;
return true;
}
goto default;
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
}
これは無効なC#のコードです。なぜなら、コンパイラは言語が許可していないことを行うことができますが、ILでは合法だからです。例えば、名前の衝突を避けるために、できない方法で変数に名前を付けるなどです。
しかし、ご覧のように
AllIndexesOf
はオブジェクトを構築して返すだけで、そのコンストラクタはいくつかの状態を初期化するだけです。
GetEnumerator
はオブジェクトをコピーするだけです。実際の作業は列挙を開始するときに行われます (
MoveNext
メソッドを呼び出すことで)列挙を開始するときに行われます。
関連
-
[解決済み】「The breakpoint will not currently be hit」を改善するには?このドキュメントにはシンボルが読み込まれていません。" という警告はどうすれば改善されますか?
-
[解決済み】プログラム実行中に1秒待つ
-
[解決済み】統合マネージドパイプラインモードで適用されないASP.NETの設定が検出された
-
[解決済み】Unity 「関連するスクリプトを読み込むことができません」「Win32Exception: システムは指定されたファイルを見つけることができません"
-
[解決済み】プロセスが実行されているかどうかを知るには?
-
[解決済み] IEnumerableにForEach拡張メソッドがないのはなぜですか?
-
[解決済み] なぜList<T>を継承しないのですか?
-
[解決済み] EqualsメソッドがオーバーライドされたときにGetHashCodeをオーバーライドすることが重要な理由は何ですか?
-
[解決済み] と'is'のどちらかを使って文字列を比較すると、異なる結果になることがあるのはなぜですか?
-
[解決済み】C#には拡張プロパティがある?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み】「入力文字列が正しい形式ではありませんでした」エラーの解決方法は?[重複しています]。
-
[解決済み】クロススレッド操作が有効でない。作成されたスレッド以外のスレッドからアクセスされたコントロール
-
[解決済み】リソースの読み込みに失敗した:ステータス500(内部サーバーエラー)のサーバーの応答)
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み】Linq 構文 - 複数列の選択
-
[解決済み] 関数を終了するには?
-
[解決済み】Microsoft.Extensions.LoggingからILoggerを解決することができない
-
[解決済み] C#や.NETで最悪のガチャは何ですか?[クローズド]