13

C# 6 より前では、プロパティの初期化では、バッキング フィールドを使用して既定値を初期化しませんでした。C#6 では、バッキング フィールドを使用して、新しい自動初期化プロパティで初期化します。

C#6 より前の IL がプロパティ定義を使用して初期化する理由に興味があります。これには特定の理由がありますか?それともC#6より前に適切に実装されていませんか?

C# 6.0 より前

public class PropertyInitialization
{
    public string First { get; set; }

    public string Last { get; set; }

    public PropertyInitialization()
    {
      this.First = "Adam";
      this.Last = "Smith";
    }
}

コンパイラ生成コード (IL 表現)

public class PropertyInitialisation
  {
    [CompilerGenerated]
    private string \u003CFirst\u003Ek__BackingField;
    [CompilerGenerated]
    private string \u003CLast\u003Ek__BackingField;

    public string First
    {
      get
      {
        return this.\u003CFirst\u003Ek__BackingField;
      }
      set
      {
        this.\u003CFirst\u003Ek__BackingField = value;
      }
    }

    public string Last
    {
      get
      {
        return this.\u003CLast\u003Ek__BackingField;
      }
      set
      {
        this.\u003CLast\u003Ek__BackingField = value;
      }
    }

    public PropertyInitialisation()
    {
      base.\u002Ector();
      this.First = "Adam";
      this.Last = "Smith";
    }
  }

C#6

public class AutoPropertyInitialization
{
    public string First { get; set; } = "Adam";
    public string Last { get; set; } = "Smith";
}

コンパイラ生成コード (IL 表現)

public class AutoPropertyInitialization
  {
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string \u003CFirst\u003Ek__BackingField;
    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string \u003CLast\u003Ek__BackingField;

    public string First
    {
      get
      {
        return this.\u003CFirst\u003Ek__BackingField;
      }
      set
      {
        this.\u003CFirst\u003Ek__BackingField = value;
      }
    }

    public string Last
    {
      get
      {
        return this.\u003CLast\u003Ek__BackingField;
      }
      set
      {
        this.\u003CLast\u003Ek__BackingField = value;
      }
    }

    public AutoPropertyInitialization()
    {
      this.\u003CFirst\u003Ek__BackingField = "Adam";
      this.\u003CLast\u003Ek__BackingField = "Smith";
      base.\u002Ector();
    }
  } 
4

5 に答える 5

12

C#6 より前の IL がプロパティ定義を使用して初期化する理由に興味があります。これには特定の理由がありますか?

自動プロパティの初期化による値の設定と、コンストラクターでの値の設定は 2 つの異なるものであるためです。彼らは異なる振る舞いをします。

プロパティは、フィールドをラップするアクセサ メソッドであることを思い出してください。したがって、この行:

this.First = "Adam";

次と同等です。

this.set_First("Adam");

これは、Visual Studio でも確認できます。public string set_First(string value)あなたのクラスでシグネチャーを使ってメソッドを書いてみてください。

メソッドと同様に、これらは子クラスでオーバーライドできます。このコードをチェックしてください:

public class PropertyInitialization
{
    public virtual string First { get; set; }

    public PropertyInitialization()
    {
        this.First = "Adam";
    }
}

public class ZopertyInitalization : PropertyInitialization
{
    public override string First
    {
        get { return base.First; }
        set
        {
            Console.WriteLine($"Child property hit with the value: '{0}'");
            base.First = value;
        }
    }
}

この例では、行this.First = "Adam"は子クラスのセッターを呼び出します。メソッドを呼び出しているので、覚えていますか? コンパイラがこのメソッド呼び出しをバッキング フィールドへの直接呼び出しとして解釈した場合、子セッターを呼び出すことにはなりません。コードをコンパイルすると、プログラムの動作が変わります。良くない!

自動プロパティは異なります。自動プロパティ初期化子を使用して、最初の例を変更してみましょう。

public class PropertyInitialization
{
    public virtual string First { get; set; } = "Adam";
}

public class ZopertyInitalization : PropertyInitialization
{
    public override string First
    {
        get { return base.First; }
        set
        {
            Console.WriteLine($"Child property hit with the value: '{0}'");
            base.First = value;
        }
    }
}

このコードでは、子クラスのセッター メソッドは呼び出されません。これは意図的なものです。自動プロパティ初期化子は、バッキング フィールドを直接設定するように設計されています。これらはフィールド初期化子のように見えて動作します。そのため、次のように、setter を使用せずにプロパティで使用することもできます。

public string First { get; } = "Adam";

ここにはセッターメソッドはありません!これを行うには、バッキング フィールドに直接アクセスする必要があります。自動プロパティを使用すると、プログラマーは不変の値を作成しながら、優れた構文の恩恵を受けることができます。

于 2016-10-04T12:35:50.807 に答える
2

プロパティのデフォルトとして設定された値は、コンストラクターで設定されていないことに注意してください (コードは、代入、次にコンストラクターを示しています)。

現在、C# 仕様では、コンストラクターの前に自動初期化値が設定されると記載されています。これは理にかなっています。これらの値がコンストラクターで再度設定されると、オーバーライドされます。

現在、コンストラクターが呼び出される前に、getter メソッドと setter メソッドが初期化されていません。それらはどのように使用されるべきですか?

そのため、(それまでに初期化されていないバッキングフィールド)が直接初期化されています。

hvd が述べたように、仮想呼び出しにも問題がありますが、それらが初期化されていないことが主な理由です。


コンストラクターで値を代入すると、以前と同じように動作します。

自動初期化され、ctor で変更されるプロパティの例

これが最適化されていないのはなぜですか?

このトピックに関する私の質問を参照してください。

しかし、それを最適化するべきではありませんか?

おそらく可能ですが、そのクラスがコンストラクターでその値を使用する別のクラスから継承しない場合にのみ、それが自動プロパティであることを認識し、セッターは他に何もしません。

それは多くの(危険な)仮定です。コンパイラは、そのような最適化を行う前に、多くのことをチェックする必要があります。


サイドノート:

コンパイラが生成した C# コードを表示するために何らかのツールを使用していると思いますが、完全に正確というわけではありません。コンストラクター用に生成される IL コードの正確な表現はありません。ctor は IL のメソッドではなく、別のものです。理解するために、それは同じであると仮定できます。

http://tryroslyn.azurewebsites.net/例として、次のコメントがあります。

// This is not valid C#, but it represents the IL correctly.
于 2016-10-04T09:32:58.217 に答える
2

示されているコードを取得する 1 つの方法は、C# 5 コードを次のようにすることです。

public class Test : Base
{
    public Test()
    {
        A = "test";
    }

    public string A { get; set; }
}

これにより、次のような (IL) コードが生成されます。

public Test..ctor()
{
    Base..ctor();
    A = "test";
}

C# 6 コードは次のようになります。

public class Test : Base
{
    public Test()
    {
    }

    public string A { get; set; } = "test";
}

次のような(IL)コードを生成します。

public Test..ctor()
{
    <A>k__BackingField = "test";
    Base..ctor();
}

コンストラクターでプロパティを具体的に初期化し、getter/setter プロパティがある場合、C# 6 では上記の回答の最初のコードのように見えますが、getter のみのフィールドがある場合は次のようになります。このような:

public Test..ctor()
{
    Base..ctor();
    <A>k__BackingField = "test";
}

したがって、C# 5 コードは上記の最初のコードのように見え、C# 6 コードは 2 番目のコードのように見えたことは明らかです。

あなたの質問に答えるために: 自動プロパティ初期化のコンパイル方法に関して、C# 5 と C# 6 の動作が異なるのはなぜですか? その理由は、C# 5 以前ではプロパティの自動初期化を行うことができず、コードが異なればコンパイルも異なるためです。

于 2016-10-04T10:11:41.963 に答える
1

違いが生じるのは、プロパティ セッターが単に値を設定する以上の効果を持っている場合のみです。自動実装されたプロパティの場合、発生する可能性があるのは、それらがvirtualオーバーライドされている場合のみです。その場合、基本クラスのコンストラクターが実行される前に派生クラスのメソッドを呼び出すことは、非常に悪い考えです。C# では、まだ完全に初期化されていないオブジェクトへの参照が誤って作成されないようにするために、多くのトラブルが発生します。したがって、それを防ぐためにフィールドを直接設定する必要があります。

于 2016-10-03T22:15:45.523 に答える
0

C# 5.0 コードは次のようになっていると想定しています。

class C
{
    public C()
    {
        First = "Adam";
    }

    public string First { get; private set; }
}

そして、C# 6.0 で行った変更は、-only 自動プロパティを作成することだけFirstですget

class C
{
    public C()
    {
        First = "Adam";
    }

    public string First { get; }
}

C# 5.0 の場合、Firstはセッターを持つプロパティであり、コンストラクターで使用するため、生成された IL はそれを反映します。

C# 6.0 バージョンでFirstは、setter がないため、コンストラクターはバッキング フィールドに直接アクセスする必要があります。

どちらの場合も、私には完全に理にかなっています。

于 2016-10-03T22:10:18.657 に答える