名前付き引数 - c# 引数 省略 ref



Nullable<T>を使用した '=='の引数の順序 (2)

次の2つのC#関数は、引数の左/右の順序を等号演算子==にスワップするだけで異なります。 ( IsInitializedのタイプはbool )。 C#7.1および.NET 4.7の使用

static void A(ISupportInitialize x)
{
    if ((x as ISupportInitializeNotification)?.IsInitialized == true)
        throw null;
}

static void B(ISupportInitialize x)
{
    if (true == (x as ISupportInitializeNotification)?.IsInitialized)
        throw null;
}

しかし、第2のILコードははるかに複雑に見える。 たとえば、 Bは次のとおりです。

  • 36バイト長く(ILコード);
  • newobjinitobjを含む追加の関数を呼び出します。
  • 4人の地元人を1人だけ宣言します。

関数「A」のIL

[0] bool flag
        nop
        ldarg.0
        isinst [System]ISupportInitializeNotification
        dup
        brtrue.s L_000e
        pop
        ldc.i4.0
        br.s L_0013
L_000e: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
L_0013: stloc.0
        ldloc.0
        brfalse.s L_0019
        ldnull
        throw
L_0019: ret

関数 'B'のIL

[0] bool flag,
[1] bool flag2,
[2] valuetype [mscorlib]Nullable`1<bool> nullable,
[3] valuetype [mscorlib]Nullable`1<bool> nullable2
        nop
        ldc.i4.1
        stloc.1
        ldarg.0
        isinst [System]ISupportInitializeNotification
        dup
        brtrue.s L_0018
        pop
        ldloca.s nullable2
        initobj [mscorlib]Nullable`1<bool>
        ldloc.3
        br.s L_0022
L_0018: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
        newobj instance void [mscorlib]Nullable`1<bool>::.ctor(!0)
L_0022: stloc.2
        ldloc.1
        ldloca.s nullable
        call instance !0 [mscorlib]Nullable`1<bool>::GetValueOrDefault()
        beq.s L_0030
        ldc.i4.0
        br.s L_0037
L_0030: ldloca.s nullable
        call instance bool [mscorlib]Nullable`1<bool>::get_HasValue()
L_0037: stloc.0
        ldloc.0
        brfalse.s L_003d
        ldnull
        throw
L_003d: ret

Quesions

  1. ABの間に機能的、意味的、またはその他の実質的なランタイムの相違がありますか? (ここでは、パフォーマンスではなく、正確さのみに関心があります)
  2. 機能的に同等でない場合、観察可能な差異を明らかにする実行時条件は何ですか?
  3. 機能的に同等であれば、 Bは何をしているのですか(常にAと同じ結果に終わります)、そしてその痙攣を引き起こしたのは何ですか? Bには決して実行できないブランチがありますか?
  4. 相違が、 ==左側に表示されるもの(ここでは、式をリテラル値と対比するプロパティ)の違いによって説明されている場合、C#仕様の詳細を記述するセクションを指定できますか?
  5. コーディング時に肥大したILを予測するために使用できる信頼できるルールがあるので、それを作成しないでください。

ボーナス。 最終的にJITされたそれぞれのx86またはAMD64コードはどのようにスタックアップするのですか?

[編集]
コメントのフィードバックに基づく追加の注釈。 最初に、3番目の変種が提案されましたが、それはAと同じILを( DebugビルドとReleaseビルドの両方で)提供します。 しかし、音韻論的には、新しいもののC#Aよりも滑らかに見えます。

static void C(ISupportInitialize x)
{
    if ((x as ISupportInitializeNotification)?.IsInitialized ?? false)
        throw null;
}

ここには、各機能のRelease ILもあります。 アシンメトリーA / CBRelease ILでまだ明白であるので、元の質問は依然として立っています。

関数 'A'、 'C​​'のILを解放する

        ldarg.0
        isinst [System]ISupportInitializeNotification
        dup
        brtrue.s L_000d
        pop
        ldc.i4.0
        br.s L_0012
L_000d: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
        brfalse.s L_0016
        ldnull
        throw
L_0016: ret

関数 'B'のILを解放する...

[0] valuetype [mscorlib]Nullable`1<bool> nullable,
[1] valuetype [mscorlib]Nullable`1<bool> nullable2
        ldc.i4.1
        ldarg.0
        isinst [System]ISupportInitializeNotification
        dup
        brtrue.s L_0016
        pop
        ldloca.s nullable2
        initobj [mscorlib]Nullable`1<bool>
        ldloc.1
        br.s L_0020
L_0016: callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
        newobj instance void [mscorlib]Nullable`1<bool>::.ctor(!0)
L_0020: stloc.0
        ldloca.s nullable
        call instance !0 [mscorlib]Nullable`1<bool>::GetValueOrDefault()
        beq.s L_002d
        ldc.i4.0
        br.s L_0034
L_002d: ldloca.s nullable
        call instance bool [mscorlib]Nullable`1<bool>::get_HasValue()
L_0034: brfalse.s L_0038
        ldnull
        throw
L_0038: ret

最後に、新しいC#7構文を使用したバージョンが挙げられました。これはすべての中で最もクリーンなILを生成するようです:

static void D(ISupportInitialize x)
{
    if (x is ISupportInitializeNotification y && y.IsInitialized)
        throw null;
}

関数 'D'のILを解放する...

[0] class [System]ISupportInitializeNotification y
        ldarg.0 
        isinst [System]ISupportInitializeNotification
        dup 
        stloc.0 
        brfalse.s L_0014
        ldloc.0 
        callvirt instance bool [System]ISupportInitializeNotification::get_IsInitialized()
        brfalse.s L_0014
        ldnull 
        throw 
L_0014: ret 

Answer #1

だから、私は答えに不思議で、C#6仕様(C#7仕様がどこにホストされているか分かりません)を見ました。 完全な免責事項:私は私の答えが正しいことを保証しません、なぜなら私はC#spec / compilerを書いておらず、内部についての理解が限られているからです。

しかし、私はその答えがoverloadable ==演算子の結果にあると思います。 ==に対する最適な適用可能なオーバーロードは、 優れた関数メンバーのルールを使用して決定されます。

仕様から:

パラメータ型{P1、P2、...、Pn}および{Q1、Q2、...}を有する2つの適用可能な関数メンバMpおよびMqと、引数式{E1、E2、 ...、Qn}の場合、MpはMqよりも優れた関数メンバであると定義されるif

各引数について、ExからQxへの暗黙的変換は、ExからPxへの暗黙的変換ではなく、少なくとも1つの引数に対して、ExからPxへの変換はExからQxへの変換よりも優れています。

{E1, E2, .., En}の引数リストは私の目{E1, E2, .., En}Nullable<bool>Nullable<bool>を比較すると、引数リストは{Nullable<bool> a, bool b}なり、その引数リストではNullable<bool>.Equals(object o)メソッドが最良のようですなぜなら、 boolからobjectへの暗黙の変換が1回しかないからです。

しかし、引数リストの順序を{bool a, Nullable<bool> b}戻すと、 Nullable<bool>.Equals(object o)メソッドは最善の関数ではなくなりました。これはNullable<bool>を使用して第1引数のboolを行い、次に第2引数のboolからobjectboolobject 。 そのため、ケースAでは 、異なる過負荷が選択され、よりクリーンなILコードが生成されるように見えます。

繰り返しますが、これは私の好奇心を満たし、C#の仕様に沿っているようです。 しかし、私は実際に何が起こっているかを見るためにコンパイラをデバッグする方法をまだ理解していません。


Answer #2

比較のために第1オペランドが第2の型に変換されているように見えます。

ケースBの超過操作では、 Nullable<bool>(true)を作成する必要がありNullable<bool>(true) 。 ケースAでは、何かをtrueと比較するために、それを実行する単一のIL命令( brfalse.s )があります。

私はC#5.0仕様で特定のリファレンスを見つけることができませんでした。 7.10リレーショナルおよびタイプテストの演算子は、 7.3.4バイナリ演算子のオーバーロードの分解能を指します。これは、 7.5.3オーバーロードの分解能を指しますが、後者は非常に曖昧です。





commutativity