web-dev-qa-db-ja.com

構造体は「値渡し」ですか?

最近、Vector2フィールドのプロパティを作成しようとしましたが、意図したとおりに機能しないことに気づきました。

public Vector2 Position { get; set; }

これにより、メンバーの値を変更できなくなります(XY

これに関する情報を調べたところ、Vector2構造体にプロパティを作成すると、元のオブジェクトのコピーのみが返され、参照は返されないことがわかりました。

Java開発者として、これは私を混乱させます。

C#のオブジェクトはいつ値によって渡され、いつ参照によって渡されるのですか?
すべての構造体オブジェクトは値で渡されますか?

34
Acidic

シグネチャで ref または out を指定しない限り、C#の everythingはvalue によって渡されることを理解することが重要です。

値型(およびstructs)が参照型と異なる点は、値型に直接アクセスするのに対し、参照型にはその参照を介してアクセスすることです。参照型をメソッドに渡すと、値自体ではなく、その参照が値で渡されます。

説明のために、classPointClassstructPointStructが同じように定義されていると考えてください(無関係な詳細は省略):

struct PointStruct { public int x, y; }

class PointClass { public int x, y; }

そして、これらの2つの型を値で取得するメソッドSomeMethodがあります。

static void ExampleMethod(PointClass apc, PointStruct aps) { … }

ここで2つのオブジェクトを作成し、メソッドを呼び出す場合:

var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);

ExampleMethod(pc, ps);

…次の図でこれを視覚化できます。

 diagram 

pcは参照であるため、値自体は含まれていません。むしろ、メモリ内の別の場所にある(名前のない)値を参照します。これは、破線の境界線と矢印で視覚化されています。

ただし、pcpsの両方について、メソッドを呼び出すと、実際の変数がコピーされます

ExampleMethodが引数変数を内部的に再割り当てするとどうなりますか?確認しよう:

static void ExampleMethod(PointClass apc, PointStruct aps); {
    apc = new PointClass(2, 2);
    aps = new PointStruct(2, 2);
}

メソッドを呼び出した後のpcおよびpsの出力:

pc: {x: 1, y: 1}
ps: {x: 1, y: 1}

ExampleMethodが値のコピーを変更し、元の値は影響を受けません。

これは、基本的に、「値渡し」が意味するものです。

参照型と値型にはまだ違いがあり、それは変数自体ではなく値のメンバーを変更するときに関係します。これは、参照型が値によって渡されるという事実に直面したときに人々をつまずかせる部分です。別のExampleMethodを検討してください。

static void ExampleMethod(PointClass apc, PointStruct aps) {
    apc.x = 2;
    aps.x = 2;
}

メソッドを呼び出した後、次の結果を確認します。

pc: {x: 2, y: 1}
ps: {x: 1, y: 1}

→参照オブジェクトは変更されましたが、値オブジェクトは変更されませんでした。上の図はその理由を示しています。参照オブジェクトの場合、pcがコピーされても、pcapcの両方の参照の実際の値は同じままであり、apcを使用して変更できます。 psについては、実際の値自体をapsにコピーしました。元の値をExampleMethodで変更することはできません。

75
Konrad Rudolph

structは値の型であるため、常に値として渡されます。

値は、参照型(オブジェクト)または値型(構造体)のいずれかです。渡されるのは常に値です。参照タイプの場合は参照の値を渡し、値タイプの場合は値自体を渡します。

by referenceという用語は、refまたはoutキーワードを使用してパラメーターを渡すときに使用されます。次に、値を渡す代わりに、値を含む変数への参照を渡します。通常、パラメーターは常に渡されます値による

22
Guffa

Foreward:管理されているC#でも、Cによって作成されたコアメモリイディオムが残っています。メモリは、配列内のインデックスに「メモリアドレス」というラベルが付けられた巨大な配列と見なすことができます。 pointerは、この配列(メモリaddress)の数値インデックスです。この配列の値は、データまたは別のメモリアドレスへのポインタのいずれかです。 constポインタは、この配列の変更できないのインデックスに格納された値です。メモリアドレスは本質的に存在し、変更することはできませんが、そのアドレスにある値は、notconstの場合、常に変更できます。

クラスを渡す

クラス/参照型(文字列を含む)は、constポインター参照によって渡されます。変異は、このインスタンスのすべての使用に影響します。オブジェクトのアドレスは変更できません。割り当てまたはnewを使用してアドレスを変更しようとすると、現在のスコープのパラメーターと同じ名前を共有するローカル変数が実際に作成されます。

コピーで渡す

プリミティブ/ ValueTypes /構造体(文字列は不誠実なポーズをとっても、どちらもストリングではありません)は、メソッド、プロパティから返されるか、パラメーターとして受け取ったときに完全にコピーされます。構造体の変異は共有されません。構造体にクラスメンバーが含まれている場合、コピーされるのはポインター参照です。このメンバーオブジェクトは変更可能です。

プリミティブは変更できません。 1を2に変更することはできません。現在1を参照しているメモリアドレスを、現在のスコープの2のメモリアドレスに変更できます。

渡すtrue参照

outまたはrefキーワードの使用が必要です。

refを使用すると、newオブジェクトのポインターを変更したり、既存のオブジェクトを割り当てたりできます。 refを使用すると、プリミティブ/ ValueType /構造体をメモリポインタで渡して、オブジェクトのコピーを回避することもできます。また、別のプリミティブに割り当てた場合、それを別のプリミティブに置き換えることもできます。

outは、意味的にはrefと同一ですが、1つの小さな違いがあります。 refパラメータは初期化する必要があります。outパラメータは、パラメータを受け入れるメソッドで初期化する必要があるため、初期化解除できます。これはTryParseメソッドで一般的に示され、xの初期値が役に立たないときにint x = 0; int.TryParse("5", out x)を用意する必要がなくなります。

5
Chris Marisic

.NETデータ型はvalueおよびreferenceタイプに分けられます。値のタイプには、intbyte、およびstructsがあります。参照タイプには、stringおよびクラスが含まれます。

構造体は、値の型が1つまたは2つだけ含​​まれている場合は、クラスの代わりに適切です(ただし、意図しない副作用が発生する場合もあります)。

したがって、構造体は実際に値で渡され、表示されているものが期待されます。

5
Jonathan Wood

メソッドを介して構造体とクラスを渡すことのさまざまな効果を説明するためだけに:

(注: LINQPad 4 でテスト済み)

/// via http://stackoverflow.com/questions/9251608/are-structs-pass-by-value
void Main() {

    // just confirming with delegates
    Action<StructTransport> delegateTryUpdateValueType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    Action<ClassTransport> delegateTryUpdateRefType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    // initial state
    var structObject = new StructTransport { i = 1, s = "one" };
    var classObject = new ClassTransport { i = 2, s = "two" };

    structObject.Dump("Value Type - initial");
    classObject.Dump("Reference Type - initial");

    // make some changes!
    delegateTryUpdateValueType(structObject);
    delegateTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after delegate");
    classObject.Dump("Reference Type - after delegate");

    methodTryUpdateValueType(structObject);
    methodTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after method");
    classObject.Dump("Reference Type - after method");

    methodTryUpdateValueTypePassByRef(ref structObject);
    methodTryUpdateRefTypePassByRef(ref classObject);

    structObject.Dump("Value Type - after method passed-by-ref");
    classObject.Dump("Reference Type - after method passed-by-ref");
}

// the constructs
public struct StructTransport {
    public int i { get; set; }
    public string s { get; set; }
}
public class ClassTransport {
    public int i { get; set; }
    public string s { get; set; }
}

// the methods
public void methodTryUpdateValueType(StructTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateRefType(ClassTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateValueTypePassByRef(ref StructTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

結果

(LINQPadダンプから)

Value Type - initial 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - initial 
ClassTransport 
UserQuery+ClassTransport 
i 2 
s two 

//------------------------

Value Type - after delegate 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after delegate 
ClassTransport 
UserQuery+ClassTransport 
i 12 
s two, appended delegate 

//------------------------

Value Type - after method 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after method 
ClassTransport 
UserQuery+ClassTransport 
i 112 
s two, appended delegate, appended method 

//------------------------

Value Type - after method passed-by-ref 
StructTransport 
UserQuery+StructTransport 
i 1001 
s one, appended method by ref 


Reference Type - after method passed-by-ref 
ClassTransport 
UserQuery+ClassTransport 
i 1112 
s two, appended delegate, appended method, appended method by ref 
3
drzaus

問題は、ゲッターがVector2のコピーを返すことです。このように座標を変えると

obj.Position.X = x;
obj.Position.Y = y;

この一時的なコピーの座標のみを変更します。

代わりにこれを行う

obj.Position = new Vector2(x, y);

これは、値または参照とは関係ありません。 Value2は値の型であり、getはこの値を返します。ベクトルに参照型(クラス)がある場合、getはこの参照を返します。 returnは値ごとに値を返します。参照タイプがある場合、これらの参照は値であり、返されます。

オブジェクトは参照によって渡され、構造体は値によって渡されます。ただし、引数には「out」および「ref」修飾子があることに注意してください。

したがって、次のように構造体を参照で渡すことができます。

public void fooBar( ref Vector2 position )
{
}
1
Dan Byström

はい、構造体はValueTypeを継承し、値によって渡されます。これは、int、double、boolなどのプリミティブ型にも当てはまります(文字列は対象外)。文字列、配列、すべてのクラスは参照型であり、参照によって渡されます。

構造体を参照渡しする場合は、refキーワードを使用します。

public void MyMethod (ref Vector2 position)

これはstruct by-refを渡し、そのメンバーを変更できるようにします。

構造体型のすべての格納場所は、その構造体のプライベートおよびパブリックのすべてのフィールドを保持します。構造体型のパラメーターを渡すには、スタック上のその構造体にスペースを割り当て、すべてのフィールドを構造体からスタックにコピーする必要があります。

コレクション内に格納されている構造体の操作に関して、既存のコレクションで可変構造体を使用するには、通常、構造体をローカルコピーに読み取って、そのコピーを変更し、再度格納する必要があります。 MyListがList<Point>であり、ローカル変数zMyList[3].Xに追加するとします。

ポイントtemp = MyList [3]; 
 temp.X + = Z; 
 MyList [3] = temp; 

これは少し厄介ですが、多くの場合、不変の構造体を使用するよりもクリーンで安全であり、効率的です。不変のクラスオブジェクトよりも効率的で、可変クラスオブジェクトを使用するよりも安全です。コレクションが値型の要素を公開するためのより良い方法に対するコンパイラのサポートを本当に望んでいます。このような公開を適切なセマンティクスで処理する効率的なコードを作成する方法があります(たとえば、コレクションオブジェクトは、要素がコレクションにリンクされていなくても、要素が更新されたときに反応する可能性があります)。コードは恐ろしく読みます。クロージャと概念的に同様の方法でコンパイラサポートを追加すると、効率的なコードも読み取り可能になります。

一部の人々が主張することに反して、構造は 根本的に異なる クラスタイプのオブジェクトからですが、すべての構造タイプには対応するタイプがあり、「ボックス構造」と呼ばれることもあります。これは、オブジェクトから派生します( CLI(共通言語インフラストラクチャ)仕様)を参照 、セクション8.2.4および8.9.7)。コンパイラーは、クラス型オブジェクトへの参照を期待するコードに渡す必要がある場合、構造体を対応するボックス化された型に暗黙的に変換しますが、ボックス化された構造体への参照により、その内容を実際の構造体にコピーできます。ボックス化された構造体を直接操作するコード。ボックス化された構造体はクラスオブジェクトのように動作します。

0
supercat