C#での参照または値によるオブジェクトの受け渡し


234

C#では、非プリミティブ変数は参照によって渡され、プリミティブ値は値によって渡されると常に思っていました。

したがって、メソッドにプリミティブ以外のオブジェクトを渡す場合、メソッド内のオブジェクトに対して行われたことは、渡されるオブジェクトに影響します。(C#101のもの)

しかし、System.Drawing.Imageオブジェクトを渡したときに、これは当てはまらないように見えました。system.drawing.imageオブジェクトを別のメソッドに渡し、そのオブジェクトに画像をロードした場合、そのメソッドをスコープ外にして呼び出し元のメソッドに戻ると、その画像は元のオブジェクトにロードされませんか?

どうしてこれなの?


20
C#では、すべての変数はデフォルトで値によって渡されます。参照型の場合は、参照の値を渡します。
Andrew Barber

回答:


503

オブジェクトはまったく渡されません。既定では、引数が評価され、そのは、呼び出すメソッドのパラメーターの初期値としてで渡されます。ここで重要な点は、値が参照型の参照であるということです-オブジェクト(またはnull)に到達する方法。そのオブジェクトへの変更は、呼び出し元から可視になります。ただし、別のオブジェクトを参照するようにパラメーターの値を変更しても、値渡し(すべてのタイプのデフォルト)を使用している場合は表示されません

参照渡しを使用する場合は、パラメータータイプが値タイプか参照タイプかに関係なくoutまたはを使用する必要ありますref。その場合、事実上、変数自体が参照によって渡されるため、パラメーターは引数と同じ格納場所を使用し、パラメーター自体への変更は呼び出し元に表示されます。

そう:

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

これについて詳しく説明している記事があります。基本的に、「参照渡し」は、それが何を意味するかを意味するものではありません。


2
あなたの権利、私はそれを見ませんでした!image = Image.FromFile(..)を読み込んでいて、それは可変画像を置き換えていて、オブジェクトを変更していませんでした!:) もちろん。
マイケル、2012年

1
@Adeem:不正解です。「パラメータオブジェクト」はなく、パラメータの値が参照するオブジェクトがあります。私はあなたが正しい考えを持っていると思いますが、用語は重要です:)
ジョン・スキート2015

2
私たちは、キーワードをドロップした場合refoutのC#から、そのC#のパラメーターにJavaが値によってすなわちいつものと同じように渡すと言ってokです。javaとの違いはありますか?
ブロードバンド2015年

1
@broadband:はい、デフォルトの受け渡しモードは値渡しです。もちろん、C#にはポインターとカスタム値型があるため、Javaよりも少し複雑になります。
Jon Skeet、2015年

3
@Vippy:いいえ、まったくありません。これはリファレンスのコピーです。リンク先の記事を読むことをお勧めします。
Jon Skeet、

18

これを紹介するもう1つのコードサンプル:

void Main()
{


    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

そして出力:

TestPlain:0

TestRef:5

TestObjPlain:test

TestObjRef:TestObjRef


2
したがって、基本的に参照型は、Caller関数の変更を確認する場合、参照として渡す必要があります。
Unbreakable

1
文字列は不変の参照型です。不変とは、作成後に変更できないことを意味します。文字列を変更するたびに、新しい文字列が作成されます。そのため、呼び出しメソッドで変更を取得するには、文字列を「ref」として渡す必要がありました。他のオブジェクト(従業員など)を 'ref'なしで渡して、メソッドの呼び出しで変更を戻すことができます。
Himalaya Garg

1
@ vmg、HimalayaGargによると、これはあまり良い例ではありません。不変ではない別の参照型の例を含める必要があります。
Daniel

11

良い答えがたくさん追加されました。私はまだ貢献したいのですが、もう少し明確にするかもしれません。

メソッドに引数としてインスタンスを渡すと、インスタンスのが渡されcopyます。ここで、渡すインスタンスがvalue type(に常駐するstack)の場合、その値のコピーを渡すため、値を変更しても、呼び出し元には反映されません。インスタンスが参照タイプの場合は、参照のコピー(これもにあるstack)をオブジェクトに渡します。したがって、同じオブジェクトへの参照が2つあります。どちらもオブジェクトを変更できます。ただし、メソッド本体内で新しいオブジェクトをインスタンス化する場合、参照のコピーは元のオブジェクトを参照しなくなり、作成した新しいオブジェクトを参照します。したがって、2つの参照と2つのオブジェクトを持つことになります。


これが選択された答えになるはずです!
JAN

完全に同意します!:)
JOSEFtw

8

このようにすると、より明確になると思います。このようなことをテストするには、LinqPadをダウンロードすることをお勧めします。

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

そしてそれは出力するはずです

WontUpdate

名:Egli、姓:Becerra

UpdateImplicitly

名:ファビオ、姓:ベセラ

UpdateExplicitly

名:ファビオ、姓:ベセラ


そして、public static void WhatAbout(Person p){p = new Person(){FirstName = "First"、LastName = "Last"}; } :)
マリンポポフ

4

あなたが渡すとSystem.Drawing.Imageメソッドに型オブジェクトを、あなたが実際にそのオブジェクトへの参照のコピーを渡しています。

したがって、そのメソッド内で新しい画像をロードしている場合は、new / copyed referenceを使用してロードしています。オリジナルを変更していません。

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}


-1

参照による受け渡し関数パラメーターに「ref」を追加するだけで、mainがstatic(#public void main(String[] args))であるため、関数「static」を宣言する必要があるもう1つのことです!

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.