デフォルトの構造とコンストラクタ

デフォルトのコンストラクターは、パラメーターを持たないコンストラクター型の作成に要約されるかなり単純な構造です。 したがって、たとえば、非静的クラスを宣言するときにカスタムコンストラクターを宣言しない場合(パラメーターの有無に関係なく)、コンパイラーはパラメーターなしのコンストラクターを生成します。 ただし、構造体(意味のある型)の既定のコンストラクターになると、すべてがそれほど単純ではなくなります。



これは、次の質問に答える方法の簡単な例です: いくつの重要なタイプが由来するのか。 NET Frameworkに はデフォルトのコンストラクターが含まれていますか?すべて 」は直感的な答えのように見えますが、実際には重要なタイプないため、間違っています。 NET Frameworkに は、デフォルトのコンストラクターが含まれていません





しかし、順番に物事を取り、デフォルトのコンストラクターやデフォルト値などの概念を比較することから始めましょう。



クラスに関しては、すべてが単純です。コンストラクターが明示的に宣言されていない場合、C#言語コンパイラーは、パラメーターなしでデフォルトのコンストラクターと呼ばれるコンストラクターを生成します。 参照型の変数(またはフィールド)のデフォルト値はnullです。



しかし、構造に関しては、すべてが少し複雑になります。 重要なタイプのインスタンスのデフォルト値は、すべてのフィールドの「ゼロ」表現です。つまり、 すべての数値フィールドは0で、すべての参照フィールドはnullです。 C#の観点から見ると、重要な型のデフォルトコンストラクターは同じことを行います。デフォルト値を持つ構造体のインスタンスを返します。 これは、次のコードが同等であることを意味します。



Size size1 = new Size(); Size size2 = default(Size);
      
      







この場合、両方のコード行に対して、コンパイラーは同じiniobj命令を生成します。これにより、両方のケースで同じ結果が得られます-Size構造体のすべてのフィールドがゼロになります。



C#言語仕様では、ユーザーが明示的にデフォルトコンストラクターを作成することは許可されていません。これは、構造体に暗黙的に含まれているためです。 ただし、これは完全に真実ではありません。Size型のコンストラクターのリストを取得した場合、そこにパラメーターのないコンストラクターは表示されません。 C#のデフォルトコンストラクターと呼ばれるものは、実際にはオブジェクトを「無効にする」ことであり、通常の意味でのコンストラクターではなく、 Size型の特別なコードは含まれていません。



同様の概念の混合は、 new演算子が呼び出されたときだけでなく、コンストラクターで構造体フィールドを初期化するときにも存在します。 そのため、構造体のコンストラクターには、ローカル変数の規則(明確な割り当て規則)と同様に、構造体のすべてのフィールドの必須初期化の同じ規則が適用されます。 これは、コンストラクターの本体が完了する前に、構造体のすべてのフィールドを明示的または暗黙的に初期化する必要があることを意味します。



 struct SomeStruct { private int _i; private double _d; public SomeStruct(int i) : this() //  “  ” { _i = i; //  _d  ! } }
      
      







this ()の呼び出しは、デフォルトコンストラクターの呼び出しとまったく同じように見え、構造体のすべてのフィールドをリセットする(したがって初期化する)ことによるコンパイルエラーを防ぎます。 実際、 this ()の呼び出しは、構造インスタンスのデフォルト値を取得するために以前に使用されたのと同じinitobjステートメントに変わります。



このようなデフォルトコンストラクターの概念と意味のある型のデフォルト値の取得の混合は、一般に.NETプラットフォームで受け入れられますが、必須ではありません。 ベアILやManaged C ++などの一部の言語は、意味のある型の本格的なカスタムデフォルトコンストラクターをサポートしています。これにより、デフォルト値だけでなく、任意の方法で構造体の状態を初期化できます。

C#では、構造体の既定のコンストラクターを作成できませんが、使用することはできます。 C#言語コンパイラが「 new SomeType 」命令を検出すると、生成されるコードは、指定された型がクラスまたは構造であるかどうか、および構造に本格的なデフォルトコンストラクターが含まれているかどうかによって異なります。



 StringBuilder sb = new StringBuilder(); // 1 Size size = new Size(); // 2 CustomValueType cvt = new CustomValueType(); // 3
      
      







最初の場合、 StringBuiilderクラスのデフォルトコンストラクターはnewobj命令を使用して呼び出されますが、重要な型のインスタンスを作成した結果はその実装に依存します。



SizeCustomValueTypeの両方のタイプは重要なタイプですが、 CustomValueTypeのタイプにはデフォルトのコンストラクターが含まれます(これを実現する方法については後述します)。 2行目では、 initobjステートメントを使用してサイズ変数をデフォルト値で初期化し、3行目では、 呼び出し命令CustomValueType ..ctorを使用してCustomValueTypeタイプのコンストラクターを呼び出します。



デフォルトのコンストラクターで構造を作成する



構造を生成するには、必要なすべての機能をサポートするSystem.Reflection.Emitモジュールを使用します。 新しい型を作成するプロセスは、 DynamicModuleBuilderのインスタンスが作成されるAssemblyBuilderオブジェクトの作成を開始します。このインスタンスには、型が既に作成されています。 この順序は、実際には、アセンブリにはアセンブリのメタデータ(依存関係など)とモジュールのみが含まれ、モジュールには既にカスタム型が含まれているという事実によって説明されます。



 public static Type GenerateValueTypeWithDefaultConstructor(string name, string outputString = "Hello, value type's default ctor!") { //    var assemblyName = new AssemblyName("StructEmitter"); var appDomain = AppDomain.CurrentDomain; var assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); var moduleBuilder = assemblyBuilder.DefineDynamicModule( assemblyName.Name, Path.ChangeExtension(assemblyName.Name, "dll")); var typeBuilder = moduleBuilder.DefineType(name, TypeAttributes.Public, typeof(ValueType)); //     var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { }); var ilGenerator = constructorBuilder.GetILGenerator(); //   Console.WriteLine    ilGenerator.EmitWriteLine(outputString); ilGenerator.Emit(OpCodes.Ret); //assemblyBuilder.Save(); // ""      return typeBuilder.CreateType(); }
      
      







GenerateValueTypeWithDefaultConstructorメソッドは、型名とコンソールへの文字列出力を受け入れます(文字列は、このメソッドの動作を確認する単体テストを記述するために必要です)。 その後、指定された文字列でConsole .WriteLineを呼び出すコンストラクターを使用して、単純で意味のある型を生成します。

その後、 Activator.CreateInstanceを使用してこの型を作成するか、 AssemblyBuilder.Saveを使用して生成されたアセンブリを保存し、通常の方法で使用できます。 その後、生成された型を通常の方法で使用し、重要な型のデフォルトコンストラクターを冷静に呼び出すことができます。



 //        Activtor.CreateInstance var type = CustomStructEmitter .GenerateValueTypeWithDefaultConstructor("CustomValueType"); var cvt1 = Activator.CreateInstance(type); //      var cvt2 = new CustomValueType();
      
      







デフォルトのコンストラクターを呼び出すためのルール



C#の構造体を実際のデフォルトコンストラクターで使用するいくつかのポイントがあります。



構造のカスタムデフォルトコンストラクターがない主な理由の1つは、配列を操作するときのパフォーマンスの低下です。



 var array = new CustomValueType[5];
      
      







配列を作成するには、 newarr命令を使用します。この場合、配列のすべての要素はデフォルト値で初期化されますが、これは重要な型の場合、配列のすべてのインスタンスのすべてのフィールドを「ゼロ化」することを意味します。 使用される型に実際のデフォルトコンストラクターが含まれている場合でも(この場合のように)、パフォーマンスを向上させるために呼び出されません。



すべての要素のコンストラクターを呼び出すには、これを明示的に行う必要があります。



 //        array.Initialize();
      
      







ただし、CLRおよび.NETプラットフォーム言語の開発者が多数のカスタムコンストラクターを呼び出してパフォーマンスを低下させたとしても、これですべての問題が解決されるわけではありません。

次のコードを見てみましょう。



 var list = new List<CustomValueType>(50);
      
      







このコンストラクターはリストの容量を受け入れます。つまり、適切なサイズの配列がリスト内に作成され、現在のリストのサイズはまだ0ですが、少なくとも50個のデフォルトコンストラクターが呼び出されます。配列にメモリを割り当てるプロセスを、その中に重要なタイプのインスタンスを作成するプロセスから分離するため。 この手法はC ++ 新しいロケーティング演算子を使用して使用されますが、これは明らかに問題よりも多くの問題を追加するため、.NET開発者がしなかったので十分合理的です。





.NETプラットフォームでは、リスト、配列、および列挙の問題が依然として存在します。 詳細については、 「列挙のリストを転送する際の問題または抽象化が流れる理由」の記事を参照してください。



重要な型の既存のコンストラクターが呼び出されない場合、配列の操作が唯一の場所ではありません。 次の表は、そのようなコンストラクターが呼び出される場合と呼び出されない場合を明確にします。

 //  var cvt = new CustomValueType(); //  var cvt = Activator.CreateInstance(typeof(CustomValueType)); //   var cvt = default(CustomValueType); static T CreateAsDefault<T>() { return default(T); } //   CustomValueType cvt = CreateAsDefault<CustomValueType>(); static T CreateWithNew<T>() where T : new() { return new T(); } //  !! CustomValueType cvt = CreateWithNew<CustomValueType>(); //   var array = new CustomValueType[5];
      
      







次の場合の動作の不一致に注意を喚起したい: Activator.CreateInstanceを使用してジェネリックパラメーターのインスタンスが作成されることがわかっているため、コンストラクターで例外が発生した場合、ジェネリックメソッドのユーザーは純粋な形式ではなくTargetInvocationExceptionの形式で例外を受け取ります。 ただし、 Activator .CreateInstanceを使用してCustomValueType型のインスタンスを作成する場合、デフォルトのコンストラクターが呼び出されますが、 CreateWithNewメソッドを呼び出し、 new T ()を使用して重要な型のインスタンスを作成する場合は呼び出されません。



おわりに



そこで、次のことがわかりました。

  1. C#のデフォルトコンストラクターは、オブジェクトの値をリセットする命令です。
  2. CLRの観点から見ると、コンストラクターはデフォルトで存在し、C#はそれらを呼び出す方法も知っています。
  3. C#言語では、構造のカスタムデフォルトコンストラクターを作成できません。これは、配列の操作時のパフォーマンスの低下と大きな混乱につながるためです。
  4. デフォルトコンストラクターの作成をサポートする言語で作業する場合、.NETプラットフォームのほとんどの言語で禁止されているのと同じ理由で、コンストラクターを宣言しないでください。
  5. 重要な型は、見た目ほど単純ではありません。重要な型の可変性(可変性)の問題に加えて、デフォルトのコンストラクターであっても、すべてが単純ではありません。



All Articles