値型 vs 参照型

# ref キーワードを使用した参照渡し

ドキュメントから:

C# では、値または参照によって引数をパラメーターに渡すことができます。参照渡しにより、関数メンバー、メソッド、プロパティ、インデクサー、演算子、およびコンストラクターでパラメーターの値を変更し、その変更を呼び出し元の環境に保持できます。パラメータを参照渡しするには、`ref` または `out` キーワードを使用します。

ref の違い と out それは out です 関数が終了する前に、渡されたパラメーターを割り当てる必要があることを意味します。対照的に、ref で渡されたパラメーター 変更することも変更しないこともできます。

using System;

class Program
{
    static void Main(string[] args)
    {
        int a = 20;
        Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
        Callee(a);
        Console.WriteLine("Inside Main - After Callee: a = {0}", a);
        
        Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
        CalleeRef(ref a);
        Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
     
        Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
        CalleeOut(out a);
        Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
        
        Console.ReadLine();
    }

    static void Callee(int a)
    {
        a = 5;
        Console.WriteLine("Inside Callee a : {0}", a);
    }

    static void CalleeRef(ref int a)
    {
        a = 6;
        Console.WriteLine("Inside CalleeRef a : {0}", a);
    }
    
    static void CalleeOut(out int a)
    {
        a = 7;
        Console.WriteLine("Inside CalleeOut a : {0}", a);
    }
}

出力 :

Inside Main - Before Callee: a = 20
Inside Callee a : 5
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 6
Inside Main - After CalleeRef: a = 6
Inside Main - Before CalleeOut: a = 6
Inside CalleeOut a : 7
Inside Main - After CalleeOut: a = 7

# 他の場所で値を変更する

public static void Main(string[] args)
{
    var studentList = new List<Student>();
    studentList.Add(new Student("Scott", "Nuke"));
    studentList.Add(new Student("Vincent", "King"));
    studentList.Add(new Student("Craig", "Bertt"));

    // make a separate list to print out later
    var printingList = studentList; // this is a new list object, but holding the same student objects inside it

    // oops, we've noticed typos in the names, so we fix those
    studentList[0].LastName = "Duke";
    studentList[1].LastName = "Kong";
    studentList[2].LastName = "Brett";

    // okay, we now print the list
    PrintPrintingList(printingList);
}

private static void PrintPrintingList(List<Student> students)
{
    foreach (Student student in students)
    {
        Console.WriteLine(string.Format("{0} {1}", student.FirstName, student.LastName));
    }
}

タイプミス後に学生の名前を修正する前にprintingListリストが作成されたにもかかわらず、PrintPrintingListメソッドは修正された名前をまだ出力していることに気付くでしょう:

Scott Duke
Vincent Kong
Craig Brett

これは、両方のリストが同じ学生への参照のリストを保持しているためです。 SO 基礎となる学生オブジェクトを変更すると、いずれかのリストによる用途に伝播します。

学生クラスは次のようになります。

public class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Student(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}

# 参照渡し

メソッド内の値の型と参照型の例が適切に機能するようにするには、参照渡しするパラメーターのメソッド シグネチャと、メソッドを呼び出すときに ref キーワードを使用します。

public static void Main(string[] args)
{
    ...
    DoubleNumber(ref number); // calling code
    Console.WriteLine(number); // outputs 8
    ...
}

public void DoubleNumber(ref int number)
{
    number += number;
}

これらの変更を行うと、数値が期待どおりに更新されます。つまり、数値のコンソール出力は 8 になります。

課題数

var a = new List<int>();
var b = a;
a.Add(5);
Console.WriteLine(a.Count); // prints 1 
Console.WriteLine(b.Count); // prints 1 as well

List<int> の変数への代入 List<int> のコピーを作成しません .代わりに、参照を List<int> にコピーします。 .このように動作する型を 参照型 と呼びます .

# メソッドのパラメータ ref と out との違い

参照によって値の型を渡す方法は 2 つあります:refout .違いは ref で渡すことです 値は初期化する必要がありますが、out で渡す場合はそうではありません . out の使用 メソッド呼び出し後に変数が値を持つことを保証します:

public void ByRef(ref int value)
{
    Console.WriteLine(nameof(ByRef) + value);
    value += 4;
    Console.WriteLine(nameof(ByRef) + value);
}

public void ByOut(out int value)
{
    value += 4 // CS0269: Use of unassigned out parameter `value'  
    Console.WriteLine(nameof(ByOut) + value); // CS0269: Use of unassigned out parameter `value'  

    value = 4;
    Console.WriteLine(nameof(ByOut) + value);
}

public void TestOut()
{
    int outValue1;
    ByOut(out outValue1); // prints 4

    int outValue2 = 10;   // does not make any sense for out
    ByOut(out outValue2); // prints 4
}

public void TestRef()
{
    int refValue1;
    ByRef(ref refValue1); // S0165  Use of unassigned local variable 'refValue'

    int refValue2 = 0;
    ByRef(ref refValue2); // prints 0 and 4

    int refValue3 = 10;
    ByRef(ref refValue3); // prints 10 and 14
}

キャッチは、 out を使用することです パラメータ must メソッドを終了する前に初期化されるため、次のメソッドは ref で可能です out ではありません :

public void EmtyRef(bool condition, ref int value)
{
    if (condition)
    {
        value += 10;
    }
}

public void EmtyOut(bool condition, out int value)
{
    if (condition)
    {
        value = 10;
    }
} //CS0177: The out parameter 'value' must be assigned before control leaves the current method

これは、condition の場合です。 成り立たない、value 未割り当てになります。

# ref vs out パラメータ

コード

class Program
{
    static void Main(string[] args)
    {
        int a = 20;
        Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
        Callee(a);
        Console.WriteLine("Inside Main - After Callee: a = {0}", a);
        Console.WriteLine();

        Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
        CalleeRef(ref a);
        Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
        Console.WriteLine();

        Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
        CalleeOut(out a);
        Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
        Console.ReadLine();
    }

    static void Callee(int a)
    {
        a += 5;
        Console.WriteLine("Inside Callee a : {0}", a);
    }

    static void CalleeRef(ref int a)
    {
        a += 10;
        Console.WriteLine("Inside CalleeRef a : {0}", a);
    }

    static void CalleeOut(out int a)
    {
        // can't use a+=15 since for this method 'a' is not intialized only declared in the method declaration
        a = 25; //has to be initialized
        Console.WriteLine("Inside CalleeOut a : {0}", a);
    }
}

出力

Inside Main - Before Callee: a = 20
Inside Callee a : 25
Inside Main - After Callee: a = 20

Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 30
Inside Main - After CalleeRef: a = 30

Inside Main - Before CalleeOut: a = 30
Inside CalleeOut a : 25
Inside Main - After CalleeOut: a = 25

# 構文

  • 参照渡し:public void Double(ref int numberToDouble) { }

# コメント

# イントロダクション

# 値の種類

値の型は、2 つのうち単純な方です。値型は、データ自体を表すためによく使用されます。整数、ブール値、または 3D 空間の点はすべて、適切な値の型の例です。

値型 (構造体) は、struct キーワードを使用して宣言されます。新しい構造体を宣言する方法の例については、構文セクションを参照してください。

一般的に言えば、値の型を宣言するために使用される 2 つのキーワードがあります。

  • 構造体
  • 列挙

# 参照タイプ

参照型はもう少し複雑です。参照型は、オブジェクト指向プログラミングの意味での伝統的なオブジェクトです。そのため、継承 (およびその利点) をサポートし、ファイナライザーもサポートします。

C# では、通常、次の参照型があります:

  • クラス
  • 代議員
  • インターフェース

新しい参照型 (クラス) は、class キーワードを使用して宣言されます。例については、新しい参照型を宣言する方法についての構文セクションを参照してください。

# 主な違い

参照型と値型の主な違いを以下に示します。

# 値型はスタックに存在し、参照型はヒープに存在します

これは 2 つの違いとしてよく言及されますが、要するに、C# で int などの値の型を使用すると、プログラムはその変数を使用してその値を直接参照するということです。 int mine =0 とすれば、変数 mine は直接 0 を参照するので効率的です。ただし、参照型は実際には (名前が示すように) 基になるオブジェクトへの参照を保持します。これは、C++ などの他の言語のポインターに似ています。

この効果にすぐには気付かないかもしれませんが、効果はあり、強力で微妙です。例については、他の場所で参照型を変更する例を参照してください。

この違いは、次の他の違いの主な理由であり、知っておく価値があります。

# メソッド内で値型を変更しても値型は変わらない、参照型は

値の型がパラメーターとしてメソッドに渡されると、メソッドが何らかの方法で値を変更しても、値は変更されません。対照的に、参照型を同じメソッドに渡して変更すると、基になるオブジェクトが変更されるため、同じオブジェクトを使用する他のものは、元の値ではなく、新しく変更されたオブジェクトを持ちます。

詳細については、メソッドの値型と参照型の例を参照してください。

「ref」キーワードを使用してそれらをメソッドに渡すだけで、このオブジェクトを参照によって渡すことができます。つまり、メモリ内の同じオブジェクトです。したがって、あなたが行った変更は尊重されます。例については、参照渡しの例を参照してください。

# 値型は null にできませんが、参照型はできます

それが言うように、参照型に null を割り当てることができます。つまり、割り当てた変数に実際のオブジェクトを割り当てることはできません。ただし、値型の場合、これは不可能です。ただし、Nullable を使用して、値の型を null 許容できるようにすることができます。これが要件である場合は、検討している場合は、クラスがここで最善のアプローチではないかどうかを強く考えてください。タイプしてください。