38

Value type 内の型コンストラクターに関する質問があります。この質問は、Jeffrey Richter が C# 3rd ed を介して CLR で書​​いたものに触発されたもので、CLR が呼び出さない場合があるため、実際には値型内で型コンストラクターを定義するべきではないと彼は言います (195 ページ - 第 8 章)。それ。

したがって、たとえば (実際には...Jeffrey Richters の例)、IL を見ても、型コンストラクターが次のコードで呼び出されていない理由を理解できません。

internal struct SomeValType
{
    static SomeValType()
    {
        Console.WriteLine("This never gets displayed");
    }
    public Int32 _x;
}
public sealed class Program
{
    static void Main(string[] args)
    {
        SomeValType[] a = new SomeValType[10];
        a[0]._x = 123;
        Console.WriteLine(a[0]._x);     //Displays 123
    }
}

したがって、型コンストラクターに次の規則を適用すると、上記の値型コンストラクターがまったく呼び出されない理由がわかりません。

  1. 静的値型コンストラクターを定義して、型の初期状態を設定できます。
  2. 型には複数のコンストラクターを含めることはできません。デフォルトのコンストラクターはありません。
  3. 型コンストラクターは暗黙的にプライベートです
  4. JIT コンパイラは、型の型コンストラクターがこの AppDomain で既に実行されているかどうかを確認します。そうでない場合は、呼び出しをネイティブ コードに発行し​​ます。

だから...この型配列が構築されているのが見えない理由がわかりません。

私の最善の推測は、次のようになる可能性があるということです。

  1. CLR が型配列を構築する方法。最初のアイテムが作成されたときに静的コンストラクターが呼び出されると思っていたでしょう
  2. コンストラクターのコードは静的フィールドを初期化していないため、無視されます。コンストラクター内でプライベート静的フィールドを初期化して実験しましたが、フィールドはデフォルトの 0 値のままです。したがって、コンストラクターは呼び出されません。
  3. または...パブリックInt32が設定されているため、コンパイラーは何らかの方法でコンストラクター呼び出しを最適化しています-しかし、それはせいぜいあいまいな推測です!!

ベストプラクティスなどは別として、なぜ呼び出されないのかを自分で確認したいので、非常に興味をそそられます。

編集:私は以下の私自身の質問に答えを追加しました.Jeffrey Richterがそれについて言っていることの引用です.

誰かがアイデアを持っていれば、それは素晴らしいことです。どうもありがとう、ジェームズ

4

7 に答える 7

19

Microsoft C#4仕様は以前のバージョンからわずかに変更され、ここで見られる動作をより正確に反映するようになりました。

11.3.10静的コンストラクター

構造体の静的コンストラクターは、クラスの場合とほとんど同じルールに従います。構造体型の静的コンストラクターの実行は、アプリケーションドメイン内で発生する次のイベントの最初のものによってトリガーされます。

  • 構造体型の静的メンバーが参照されます。
  • 構造体型の明示的に宣言されたコンストラクターが呼び出されます。

構造体タイプのデフォルト値(§11.3.4)を作成しても、静的コンストラクターはトリガーされません。(この例は、配列内の要素の初期値です。)

ECMA仕様MicrosoftC#3仕様の両方で、そのリストに「構造体タイプのインスタンスメンバーが参照されています」という追加のイベントがあります。したがって、C#3はここで独自の仕様に違反しているように見えます。C#4仕様は、C#3および4の実際の動作とより緊密に連携しています。

編集...

さらに調査したところ、直接フィールドアクセスを除くほとんどすべてのインスタンスメンバーアクセスが静的コンストラクターをトリガーするようです(少なくとも現在のMicrosoftのC#3および4の実装では)。

したがって、現在の実装は、C#4仕様よりもECMAおよびC#3仕様で指定されたルールとより密接に相関しています。C#3ルールは、フィールドを除くすべてのインスタンスメンバーにアクセスするときに正しく実装されます。C#4ルールは、フィールドアクセスに対してのみ正しく実装されます。

(静的メンバーアクセスと明示的に宣言されたコンストラクターに関連するルールに関しては、さまざまな仕様がすべて一致しており、明らかに正しく実装されています。)

于 2010-07-14T13:56:20.260 に答える
11

From §18.3.10 of the standard (see also The C# programming language book):

The execution of a static constructor for a struct is triggered by the first of the following events to occur within an application domain:

  • An instance member of the struct is referenced.
  • A static member of the struct is referenced.
  • An explicitly declared constructor of the struct is called.

[Note: The creation of default values (§18.3.4) of struct types does not trigger the static constructor. (An example of this is the initial value of elements in an array.) end note]

So I would agree with you that the last two lines of your program should each trigger the first rule.

After testing, the consensus seems to be that it consistently triggers for methods, properties, events, and indexers. That means it's correct for all explicit instance members except fields. So if Microsoft's C# 4 rules were chosen for the standard, that would make their implementation go from mostly right to mostly wrong.

于 2010-07-14T12:40:36.813 に答える
2

これを「回答」として入れて、Richter氏自身が書いたことを共有できるようにします(ちなみに、最新のCLR仕様へのリンクを持っている人はいますか?2006年版を入手するのは簡単ですが、それを見つけるのは少し難しいです)最新のものを入手してください):

この種のものについては、通常、C# 仕様よりも CLR 仕様を確認する方が適切です。CLR仕様には次のように書かれています:

4. BeforeFieldInit がマークされていない場合、その型の初期化メソッドが実行されます (つまり、トリガーされます):

• そのタイプの静的フィールドへの最初のアクセス、または

• そのタイプの静的メソッドの最初の呼び出し、または

• 値型または値型の場合、その型のインスタンスまたは仮想メソッドの最初の呼び出し

• その型のコンストラクターの最初の呼び出し。

これらの条件のいずれも満たされないため、静的コンストラクターは呼び出されません。注意すべき唯一のトリッキーな部分は、「_x」が静的フィールドではなくインスタンス フィールドであり、構造体の配列を構築しても、配列要素に対してインスタンス コンストラクターが呼び出されないことです。

于 2010-07-16T05:41:42.400 に答える
1

Update: my observation is that unless static state is used, the static constructor will never be touched - something the runtime seems to decide and doesn't apply to reference types. This begs the question if it's a bug left because it has little impact, it's by design, or it's a pending bug.

Update 2: personally, unless you are doing something funky in the constructor, this behaviour from the runtime should never cause a problem. As soon as you access static state, it behaves correctly.

Update3: further to a comment by LukeH, and referencing Matthew Flaschen's answer, implementing and calling your own constructor in the struct also triggers the static constructor to be called. Meaning that in one out of the three scenarios, the behaviour is not what it says on the tin.

I just added a static property to the type and accessed that static property - it called the static constructor. Without the access of the static property, just creating a new instance of the type, the static constructor wasn't called.

internal struct SomeValType
    {
        public static int foo = 0;
        public int bar;

        static SomeValType()
        {
            Console.WriteLine("This never gets displayed");
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Doesn't hit static constructor
            SomeValType v = new SomeValType();
            v.bar = 1;

            // Hits static constructor
            SomeValType.foo = 3;
        }
    }

A note in this link specifies that static constructors are not called when simply accessing instances:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

于 2010-07-14T12:41:40.213 に答える
1

別の興味深いサンプル:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }
于 2010-07-14T12:54:49.807 に答える
0

これは、MSILの「beforefieldinit」属性の設計による狂気の動作です。これはC++/ CLIにも影響します。バグレポートを提出し、Microsoftがその動作がそのままである理由を非常にうまく説明し、実際の動作を説明するために同意しない/更新する必要がある言語標準の複数のセクションを指摘しました。 。しかし、それは公に見ることはできません。とにかく、これがマイクロソフトからの最後の言葉です(C ++ / CLIでの同様の状況について説明しています):

ここで標準を呼び出しているので、パーティションI、8.9.5の行は次のように述べています。

BeforeFieldInitとマークされている場合、その型の初期化メソッドは、その型に定義されている静的フィールドへの最初のアクセス時またはその前に実行されます。

このセクションでは、実際には、言語の実装が、説明している動作を防ぐためにどのように選択できるかについて詳しく説明します。C ++ / CLIはそうしないことを選択しますが、プログラマーが望む場合はそうすることができます。

基本的に、以下のコードには静的フィールドがまったくないため、静的クラスコンストラクターを呼び出さないという点でJITは完全に正しいです。

言語は異なりますが、同じ動作が表示されます。

于 2010-07-14T12:45:44.313 に答える
0

I would guess that you're creating an ARRAY of your value type. So the new keyword would be used to initialize memory for the array.

Its valid to say

SomeValType i;
i._x = 5;

with no new keyword anywhere, which is essentially what you're doing here. Were SomeValType a reference type, you'd have to initialize each element of your array with

array[i] = new SomeRefType();
于 2010-07-14T12:38:40.583 に答える