C#では、構造体は値の観点から管理され、オブジェクトは参照されます。私の理解では、クラスのインスタンスを作成するときに、キーワードnew
を使用すると、C#はクラス情報を使用してインスタンスを作成します(以下を参照)。
_class MyClass
{
...
}
MyClass mc = new MyClass();
_
Structの場合、オブジェクトを作成するのではなく、変数に値を設定するだけです:
_struct MyStruct
{
public string name;
}
MyStruct ms;
//MyStruct ms = new MyStruct();
ms.name = "donkey";
_
私が理解していないのは、MyStruct ms = new MyStruct()
で変数を宣言する場合、キーワードnew
はステートメントに対して何をしているのですか? 。構造体をオブジェクトにできない場合、new
はここでインスタンス化されますか?
MSDNの struct (C# Reference)
から:
New演算子を使用してstructオブジェクトを作成すると、そのオブジェクトが作成され、適切なコンストラクターが呼び出されます。クラスとは異なり、構造体はnew演算子を使用せずにインスタンス化できます。 newを使用しない場合、フィールドは未割り当てのままになり、すべてのフィールドが初期化されるまでオブジェクトを使用できません。
私の理解では、実際にすべてのフィールドを手動で初期化しない限り、newを使用せずに構造体を適切に使用することはできません。 new演算子を使用する場合、コンストラクターがこれを行います。
それが解決することを願っています。これに関する説明が必要な場合はお知らせください。
編集
かなり長いコメントスレッドがあるので、ここにもう少し追加すると思いました。私はそれを理解する最良の方法はそれを試してみることだと思います。 Visual Studioで「StructTest」というコンソールプロジェクトを作成し、次のコードをコピーします。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace struct_test
{
class Program
{
public struct Point
{
public int x, y;
public Point(int x)
{
this.x = x;
this.y = 5;
}
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
// It will break with this constructor. If uncommenting this one
// comment out the other one with only one integer, otherwise it
// will fail because you are overloading with duplicate parameter
// types, rather than what I'm trying to demonstrate.
/*public Point(int y)
{
this.y = y;
}*/
}
static void Main(string[] args)
{
// Declare an object:
Point myPoint;
//Point myPoint = new Point(10, 20);
//Point myPoint = new Point(15);
//Point myPoint = new Point();
// Initialize:
// Try not using any constructor but comment out one of these
// and see what happens. (It should fail when you compile it)
myPoint.x = 10;
myPoint.y = 20;
// Display results:
Console.WriteLine("My Point:");
Console.WriteLine("x = {0}, y = {1}", myPoint.x, myPoint.y);
Console.ReadKey(true);
}
}
}
それで遊んでください。コンストラクターを削除して、何が起こるかを確認してください。 1つの変数のみを初期化するコンストラクターを使用してみてください(1つはコメントアウトしていますが、コンパイルされません)。 newキーワードを使用して、または使用せずに試してください(いくつかの例をコメントアウトし、コメントを外して試してみてください)。
キャッチ このスレッドからのEric Lippertの優れた答え。 彼を引用するには:
値型を「新規」にすると、3つのことが起こります。まず、メモリマネージャは短期ストレージからスペースを割り当てます。第二に、コンストラクタには短期の保存場所への参照が渡されます。コンストラクターの実行後、短期保管場所にあった値は、それがどこにあっても、その値の保管場所にコピーされます。値型の変数には実際の値が格納されることに注意してください。
(コンパイラーは、そうすることで部分的に構築された構造体がユーザーコードに決して公開されないと判断できる場合、コンパイラーはこれら3つのステップを1つのステップに最適化できることに注意してください。コンストラクターへの保存場所。これにより、1つの割り当てと1つのコピーが保存されます。)
(本当に答えだからこの答えを作る)
「new MyStuct()」を使用すると、すべてのフィールドが何らかの値に設定されます。上記の場合、何も違いはありません。 ms.nameを設定する代わりに、読み取ろうとすると、VSで「使用可能な未割り当てフィールド 'name'の使用」エラーが発生します。
オブジェクトまたは構造体が存在するときはいつでも、そのすべてのフィールドも存在します。これらのフィールドのいずれかが構造体タイプである場合、ネストされたフィールドもすべて存在します。配列が作成されると、すべての要素が存在します(上記のように、これらの要素のいずれかが構造体である場合、それらの構造体のフィールドも存在します)。これらはすべて、コンストラクターコードが実行される前に発生します。
.netでは、構造体コンストラクターは実質的に、構造体を「出力」パラメーターとしてとるメソッドにすぎません。 C#では、構造体コンストラクターを呼び出す式は一時的な構造体インスタンスを割り当て、そのコンストラクターを呼び出してから、その一時的なインスタンスを式の値として使用します。これはvb.netとは異なります。vb.netでは、コンストラクター用に生成されたコードはすべてのフィールドをゼロにすることから始まりますが、呼び出し元のコードはコンストラクターを宛先で直接操作しようとします。たとえば、vb.netのmyStruct = new myStructType(whatever)
は、コンストラクターの最初のステートメントが実行される前にmyStruct
をクリアします。コンストラクター内では、構築中のオブジェクトへの書き込みはすべて、myStruct
で即座に処理されます。
構造体では、new
キーワードは不必要に混乱を招きます。何もしません。コンストラクタを使用する場合にのみ必要です。 notnew
を実行します。
new
の通常の意味は、(ヒープ上の)永続ストレージを割り当てることです。C++のような言語では、new myObject()
またはmyObject()
のみが許可されます。どちらも同じコンストラクターを呼び出します。ただし、前者は新しいオブジェクトを作成し、ポインターを返します。後者は単にtempを作成します。構造体またはクラスはいずれも使用できます。 new
は選択肢であり、何かを意味します。
C#には選択肢がありません。クラスは常にヒープ内にあり、構造体は常にスタック上にあります。構造体で実際のnew
を実行することはできません。経験豊富なC#プログラマーはこれに慣れています。 ms = new MyStruct();
を見ると、new
を単なる構文として無視することを知っています。彼らはms = MyStruct()
のように振る舞うことを知っています。これは単に既存のオブジェクトに割り当てるだけです。
奇妙な(?)、クラスにはnew
が必要です。 c=myClass();
は許可されません(コンストラクタを使用して既存のオブジェクトc
の値を設定します)。c.init();
のようなものを作成する必要があります。そのため、コンストラクターは常にクラスに割り当てられ、構造体には割り当てられません。 new
は常に単なる装飾です。
構造体で偽のnew
を必要とする理由は、構造体をクラスに簡単に変更できるためだと思います(最初に宣言するときに常にmyStruct=new myStruct();
を使用することをお勧めします)。
ValueType
と構造体は、C#で特別なものです。ここでは、new何かをしたときに何が起こるかを示しています。
ここには次のものがあります
コード
partial class TestClass {
public static void NewLong() {
var i=new long();
}
public static void NewMyLong() {
var i=new MyLong();
}
public static void NewMyLongWithValue() {
var i=new MyLong(1234);
}
public static void NewThatLong() {
var i=new ThatLong();
}
}
[StructLayout(LayoutKind.Sequential)]
public partial struct MyLong {
const int bits=8*sizeof(int);
public static implicit operator int(MyLong x) {
return (int)x.m_Low;
}
public static implicit operator long(MyLong x) {
long y=x.m_Hi;
return (y<<bits)|x.m_Low;
}
public static implicit operator MyLong(long x) {
var y=default(MyLong);
y.m_Low=(uint)x;
y.m_Hi=(int)(x>>bits);
return y;
}
public MyLong(long x) {
this=x;
}
uint m_Low;
int m_Hi;
}
public partial class ThatLong {
const int bits=8*sizeof(int);
public static implicit operator int(ThatLong x) {
return (int)x.m_Low;
}
public static implicit operator long(ThatLong x) {
long y=x.m_Hi;
return (y<<bits)|x.m_Low;
}
public static implicit operator ThatLong(long x) {
return new ThatLong(x);
}
public ThatLong(long x) {
this.m_Low=(uint)x;
this.m_Hi=(int)(x>>bits);
}
public ThatLong() {
int i=0;
var b=i is ValueType;
}
uint m_Low;
int m_Hi;
}
そして、テストクラスのメソッドの生成されたILは
IL
// NewLong
.method public hidebysig static
void NewLong () cil managed
{
.maxstack 1
.locals init (
[0] int64 i
)
IL_0000: nop
IL_0001: ldc.i4.0 // Push 0 as int
IL_0002: conv.i8 // convert the pushed value to long
IL_0003: stloc.0 // pop it to the first local variable, that is, i
IL_0004: ret
}
// NewMyLong
.method public hidebysig static
void NewMyLong () cil managed
{
.maxstack 1
.locals init (
[0] valuetype MyLong i
)
IL_0000: nop
IL_0001: ldloca.s i // Push address of i
IL_0003: initobj MyLong // pop address of i and initialze as MyLong
IL_0009: ret
}
// NewMyLongWithValue
.method public hidebysig static
void NewMyLongWithValue () cil managed
{
.maxstack 2
.locals init (
[0] valuetype MyLong i
)
IL_0000: nop
IL_0001: ldloca.s i // Push address of i
IL_0003: ldc.i4 1234 // Push 1234 as int
IL_0008: conv.i8 // convert the pushed value to long
// call the constructor
IL_0009: call instance void MyLong::.ctor(int64)
IL_000e: nop
IL_000f: ret
}
// NewThatLong
.method public hidebysig static
void NewThatLong () cil managed
{
// Method begins at RVA 0x33c8
// Code size 8 (0x8)
.maxstack 1
.locals init (
[0] class ThatLong i
)
IL_0000: nop
// new by calling the constructor and Push it's reference
IL_0001: newobj instance void ThatLong::.ctor()
// pop it to the first local variable, that is, i
IL_0006: stloc.0
IL_0007: ret
}
メソッドの動作は、ILコードでコメント化されています。そして、あなたは OpCodes.Initobj と OpCodes.Newobj を見てみたいかもしれません。通常、値のタイプは OpCodes.Initobj で初期化されますが、MSDNの説明では OpCodes.Newobj も使用されます。
OpCodes.Newobj の説明
値の型は、通常ではなく、newobjを使用して作成されます。それらは通常、引数またはローカル変数としてnewarr(ゼロベースの1次元配列の場合)として、またはオブジェクトのフィールドとして割り当てられます。割り当てられると、Initobjを使用して初期化されます。 ただし、、newobj命令を使用して、スタック上に値型の新しいインスタンスを作成し、引数として渡すことができます。ローカルなど。
byte
からdouble
までの数値である各値タイプには、定義されたop-codeがあります。それらはstruct
として宣言されていますが、示されているように生成されたILにはいくつかの違いがあります。
言及すべきもう2つのことを次に示します。
ValueType
自体は抽象クラスとして宣言されます
つまり、直接newすることはできません。
struct
sには、明示的なパラメーターなしのコンストラクターを含めることはできません
つまり、struct
をnewすると、NewMyLong
またはNewMyLongWithValue
のいずれかの上記のケースに該当します。
要約するために、値のタイプと構造のnewは言語概念の一貫性のためです。