참조로 인수 전달 Ref

메서드의 매개 변수 목록에 사용되는 경우 ref 키워드는 인수가 값이 아니라 참조로 전달됨을 나타냅니다. 인수를 참조로 전달하는 경우 호출된 메서드의 인수 변경 내용이 호출 메서드에 반영됩니다. 예를 들어 호출자가 지역 변수 식 또는 배열 요소 액세스 식을 전달하는 경우 호출된 메서드에서 ref 매개 변수가 참조하는 개체를 바꾸면 메서드 반환 시 호출자의 지역 변수 또는 배열 요소가 새 개체를 참조합니다.



참고

참조로 전달의 개념과 참조 형식의 개념을 혼동해서는 안 됩니다. 이 두 개념은 서로 다릅니다. 메서드 매개 변수는 값 형식이든 참조 형식이든 관계없이 ref를 통해 수정할 수 있으며, 참조로 전달되는 경우 값 형식은 boxing되지 않습니다.

ref 매개 변수를 사용하려면 다음 예제에 나와 있는 것처럼 메서드 정의와 호출 메서드가 모두 ref 키워드를 명시적으로 사용해야 합니다.


void Method(ref int refArgument) { refArgument = refArgument + 44; } int number = 1; Method(ref number); Console.WriteLine(number); // Output: 45



ref 또는 in 매개 변수로 전달하는 인수는 전달 전에 초기화해야 합니다. 이러한 방식은 인수를 전달하기 전에 명시적으로 초기화할 필요가 없는 out 매개 변수와는 다릅니다.

클래스의 멤버는 refin 또는 out만 다른 서명을 포함할 수 없습니다. 특정 형식의 두 멤버가 하나는 ref 매개 변수를 포함하고 다른 하나는 out 또는 in 매개 변수를 포함한다는 것 외에는 차이가 없으면 컴파일러 오류가 발생합니다. 예를 들어 다음 코드는 컴파일되지 않습니다.

class CS0663_Example { // Compiler error CS0663: "Cannot define overloaded // methods that differ only on ref and out". public void SampleMethod(out int i) { } public void SampleMethod(ref int i) { } }



그러나 다음 예제에 나와 있는 것처럼 메서드 하나에는 refin 또는 out 매개 변수가 포함되어 있고 다른 하나에는 값 매개 변수가 포함되어 있으면 메서드를 오버로드할 수 있습니다.

class RefOverloadExample { public void SampleMethod(int i) { } public void SampleMethod(ref int i) { } }


참조로 인수 전달: 예제

앞의 예제에서는 값 형식을 참조로 전달합니다. ref 키워드를 사용하여 참조 형식을 참조로 전달할 수도 있습니다. 참조 형식을 참조로 전달하는 경우 호출된 메서드는 참조 매개 변수가 호출자에서 참조하는 개체를 바꿀 수 있습니다. 개체의 저장 위치는 참조 매개 변수의 값으로 메서드에 전달됩니다. 매개 변수의 저장 위치에서 값을 변경하여 새 개체를 가리키도록 하면 호출자가 참조하는 저장 위치도 변경됩니다. 다음 예제에서는 참조 형식 인스턴스를 ref 매개 변수로 전달합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
 
public class MyClass
{
 
    class Product
    {
        public Product(string name, int newID)
        {
            ItemName = name;
            ItemID = newID;
        }
 
        public string ItemName { get; set; }
        public int ItemID { get; set; }
    }
 
    private static void ModifyProductsByReference()
    {
        // Declare an instance of Product and display its initial values.
        Product item = new Product("Fasteners"54321);
        System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
            item.ItemName, item.ItemID);
 
        // Pass the product instance to ChangeByReference.
        ChangeByReference(ref item);
        System.Console.WriteLine("Back in Main.  Name: {0}, ID: {1}\n",
            item.ItemName, item.ItemID);
    }
 
    private static void ChangeByReference(ref Product itemRef)
    {
        // Change the address that is stored in the itemRef parameter.   
        itemRef = new Product("Stapler"99999);
 
        // You can change the value of one of the properties of
        // itemRef. The change happens to item in Main as well.
        itemRef.ItemID = 12345;
    }
 
    static void Main(string[] args)
    {
        ModifyProductsByReference();
    }
}
 



result : 

Original values in Main.  Name: Fasteners, ID: 54321

Back in Main.  Name: Stapler, ID: 12345



참조 반환 값

참조 반환 값(또는 ref return)은 메서드가 호출자에게 참조로 반환하는 값입니다. 즉, 호출자가 메서드에서 반환된 값을 수정할 수 있으며 해당 변경 내용이 메서드를 포함하는 개체의 상태에 반영됩니다.

참조 반환 값은 ref 키워드를 사용하여 정의됩니다.

  • 메서드 시그니처에서. 예를 들어 다음 메서드 시그니처는 GetCurrentPrice 메서드가 Decimal 값을 참조로 반환함을 나타냅니다.

public ref decimal GetCurrentValue()


  • 메서드의 return 문에서 반환된 return 토큰과 변수 간에. 예:

return ref DecimalArray[0];


호출자가 개체 상태를 수정하려면 참조 반환 값을 참조 로컬로 명시적으로 정의된 변수에 저장해야 합니다.


참조 로컬

참조 지역 변수는 return ref을 사용하여 반환된 값을 참조하는 데 사용됩니다. 참조 지역 변수를 초기화하고 참조 반환 값에 할당해야 합니다. 참조 로컬 값의 수정 내용은 메서드가 값을 참조로 반환하는 개체 상태에 반영됩니다.

변수 선언 앞, 값을 참조로 반환하는 메서드 호출 직전에 ref 키워드를 사용하여 참조 로컬을 정의합니다.

예를 들어 다음 문은 GetEstimatedValue 메서드에서 반환되는 참조 로컬 값을 정의합니다.


ref decimal estValue = ref Building.GetEstimatedValue();


동일한 방법으로 참조로 값에 액세스할 수 있습니다. 경우에 따라 참조로 값에 액세스하면 비용이 많이 들 수 있는 복사 작업을 피함으로써 성능이 향상됩니다. 예를 들어, 다음 명령문은 값을 참조하는 데 사용되는 참조 로컬 값을 정의하는 방법을 보여줍니다.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

두 예에서 ref 키워드는 두 위치에 모두 사용해야 합니다. 그렇지 않으면 컴파일러 오류 CS8172, "값을 사용하여 참조 형식 변수를 초기화할 수 없습니다."가 생성됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
 
public class MyClass
{
 
    public class Book
    {
        public string Author;
        public string Title;
    }
 
    public class BookCollection
    {
        private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
                        new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
                       };
        private Book nobook = null;
 
        public ref Book GetBookByTitle(string title)
        {
            for (int ctr = 0; ctr < books.Length; ctr++)
            {
                if (title == books[ctr].Title)
                    return ref books[ctr];
            }
            return ref nobook;
        }
 
        public void ListBooks()
        {
            foreach (var book in books)
            {
                Console.WriteLine($"{book.Title}, by {book.Author}");
            }
            Console.WriteLine();
        }
    }
 
    static void Main(string[] args)
    {
        var bc = new BookCollection();
        bc.ListBooks();
 
        ref Book book = ref  bc.GetBookByTitle("Call of the Wild, The");
        //Book book = bc.GetBookByTitle("Call of the Wild, The");
        if (book != null)
            book = new Book { Title = "Republic, The", Author = "Plato" };
        bc.ListBooks();
    }
}
 


호출자가 GetBookByTitle 메서드에서 참조 로컬로 반환된 값을 저장하는 경우 호출자가 반환 값을 변경하면 다음 예제와 같이 BookCollection 개체에 변경 내용이 반영됩니다.


위의 예제 결과는 

Call of the Wild, The, by Jack London

Tale of Two Cities, A, by Charles Dickens


Republic, The, by Plato

Tale of Two Cities, A, by Charles Dickens


이긴 하지만  2017 community 버전에 default 에선 아래 주석 을 지운다고하여 에러가 나진않는다(기존 위에 한줄은 주석처리)

//ref Book book = ref  bc.GetBookByTitle("Call of the Wild, The");
Book book = bc.GetBookByTitle("Call of the Wild, The");

대신 결과는 값 형식 복사가 일어나게 됨으로 다음처럼 나타나게 된다(컴파일 에러가 나진 않음)

Call of the Wild, The, by Jack London

Tale of Two Cities, A, by Charles Dickens


Call of the Wild, The, by Jack London

Tale of Two Cities, A, by Charles Dickens






가변인자 params


params 키워드를 사용하면 가변 개수의 인수를 사용하는 메서드 매개 변수를 지정할 수 있습니다.

매개 변수 선언이나 지정된 형식의 인수 배열에 지정된 형식의 쉼표로 구분된 인수 목록을 보낼 수 있습니다. 인수를 보내지 않을 수도 있습니다. 인수를 보내지 않는 경우 params 목록의 길이는 0입니다.

메서드 선언에서 params 키워드 뒤에는 추가 매개 변수가 허용되지 않으며, params 키워드 하나만 메서드 선언에 사용할 수 있습니다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using System;
 
public class MyClass
{
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }
 
    public static void UseParams2(params object[] list)
    {
        Console.WriteLine(list.GetType() );
        if(list.Length >= 1)
        {
            Console.WriteLine(list[0].GetType());
 
 
            var test = list[0as int[];
            if(test!=null)
            {
                Console.WriteLine("\t" + test[0].GetType());
            }
            
        }
        
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }
 
 
 
    static void Main()
    {
        
 
 
        // You can send a comma-separated list of arguments of the 
        // specified type.
        UseParams(1234);
        UseParams2(1'a'"test");
 
        // A params parameter accepts zero or more arguments.
        // The following calling statement displays only a blank line.
        UseParams2();
 
        // An array argument can be passed, as long as the array
        // type matches the parameter type of the method being called.
        int[] myIntArray = { 56789 };
        UseParams(myIntArray);
        Console.WriteLine(typeof(int[]));
        
 
        object[] myObjArray = { 2'b'"test""again" };
        UseParams2(myObjArray);
 
        Console.WriteLine(typeof(object[]));
 
        // The following call causes a compiler error because the object
        // array cannot be converted into an integer array.
        //UseParams(myObjArray);
 
        // The following call does not cause an error, but the entire 
        // integer array becomes the first element of the params array.
        // int[] 전체가 object 가 되기 때문에 에러가 나지 않는다
        UseParams2(myIntArray);
    }
}
/*
Output:
1 2 3 4
System.Object[]
System.Int32
1 a test
System.Object[]
5 6 7 8 9
System.Int32[]
System.Object[]
System.Int32
2 b test again
System.Object[]
System.Object[]
System.Int32[]
        System.Int32
*/
 

.







out


out 키워드를 사용하면 참조를 통해 인수를 전달할 수 있습니다. 이러한 방식은 ref 키워드와 비슷합니다. 단, ref의 경우에는 변수를 전달하기 전에 초기화해야 합니다. in이 호출된 메서드에서 인수 값 수정을 허용하지 않는 것을 제외하고 in 키워드와도 같습니다. out 매개 변수를 사용하려면 메서드 정의와 호출 메서드가 모두 명시적으로 out 키워드를 사용해야 합니다. 예:



int initializeInMethod; OutArgExample(out initializeInMethod); Console.WriteLine(initializeInMethod); // value is now 44 void OutArgExample(out int number) { number = 44; }


out 인수로 전달되는 변수는 메서드 호출에서 전달되기 전에 초기화할 필요가 없지만 호출된 메서드는 메서드가 반환되기 전에 값을 할당해야 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
 
public class MyClass
{
 
    public void SampleMethod(out int i)
    {
        //아래 주석을 풀면 에러 안남
        //오류 CS0177  제어가 현재 메서드를 벗어나기 전에 'i' out 매개 변수를 할당해야 합니다.
        //i = 3;
    }
 
    //이 전체 주석을 풀면 컴파일 에러
    //'MyClass'은(는) 매개 변수 한정자 'ref' 및 'out'만 다른 오버로드된 메서드을(를) 정의할 수 없습니다.
    /*
    public void SampleMethod(ref int i)
    {
    }*/
    static void Main()
    {
 
 
    }
}
 





out 인수 선언

메서드가 여러 값을 반환하도록 하려는 경우 out 인수를 사용하여 메서드를 선언하면 유용합니다. 다음 예제에서는 out을 사용하여 단일 메서드 호출로 3개 변수를 반환합니다. 세 번째 인수는 null에 할당됩니다. 따라서 메서드가 값을 선택적으로 반환할 수 있습니다.


void Method(out int answer, out string message, out string stillNull) { answer = 44; message = "I've been returned"; stillNull = null; } int argNumber; string argMessage, argDefault; Method(out argNumber, out argMessage, out argDefault); Console.WriteLine(argNumber); Console.WriteLine(argMessage); Console.WriteLine(argDefault == null);




C# 7.0부터 별도 변수 선언이 아니라 메서드 호출의 인수 목록에서 out 변수를 선언할 수 있습니다. 이렇게 하면 보다 간결하고 읽기 쉬운 코드가 생성되며 메서드 호출 전에 실수로 변수에 값이 할당되는 경우를 방지할 수 있습니다. 다음 예제는 Int32.TryParse 메서드 호출에서 number 변수를 정의한다는 점을 제외하고 이전 예제와 비슷합니다.


string numberAsString = "1640"; if (Int32.TryParse(numberAsString, out int number)) Console.WriteLine($"Converted '{numberAsString}' to {number}"); else Console.WriteLine($"Unable to convert '{numberAsString}'"); // The example displays the following output: // Converted '1640' to 1640



앞의 예제에서 number 변수는 int로 강력하게 형식화됩니다. 다음 예제와 같이 암시적 형식 지역 변수를 선언할 수도 있습니다.


string numberAsString = "1640"; if (Int32.TryParse(numberAsString, out var number)) Console.WriteLine($"Converted '{numberAsString}' to {number}"); else Console.WriteLine($"Unable to convert '{numberAsString}'"); // The example displays the following output: // Converted '1640' to 1640







(호출된 메서드에서 인수를 수정하지 못하는) in




in 키워드를 사용하면 참조를 통해 인수를 전달할 수 있습니다. 이 키워드는 호출된 메서드에서 in 인수를 수정할 수 없다는 점을 제외하고 ref 또는 out 키워드와 유사합니다. ref 인수는 수정할 수 있지만 out 인수는 호출자가 수정해야 하며, 해당 수정 사항은 호출 컨텍스트에서 식별 가능합니다.


int readonlyArgument = 44; InArgExample(readonlyArgument); Console.WriteLine(readonlyArgument); // value is still 44 void InArgExample(in int number) { // Uncomment the following line to see error CS8331 //number = 19; }



in 인수로 전달되는 변수는 메서드 호출에서 전달되기 전에 초기화되어야 합니다. 그러나 호출된 메서드는 값을 할당하거나 인수를 수정하지 않을 수 있습니다.

inref 및 out 키워드는 서로 다른 런타임 동작을 수행하지만 컴파일 시간에 메서드 시그니처의 일부로 간주되지는 않습니다. 따라서 메서드 하나는 ref 또는 in 인수를 사용하고 다른 하나는 out 인수를 사용한다는 것 외에는 차이점이 없으면 메서드를 오버로드할 수 없습니다. 예를 들어 다음 코드는 컴파일되지 않습니다.


class CS0663_Example { // Compiler error CS0663: "Cannot define overloaded // methods that differ only on in, ref and out". public void SampleMethod(in int i) { } public void SampleMethod(ref int i) { } }


in의 존재에 기반한 오버로딩이 허용됩니다.

class InOverloads { public void SampleMethod(in int i) { } public void SampleMethod(int i) { } }



in 매개 변수를 사용하여 메서드를 정의하면 잠재적인 성능 최적화가 이루어집니다. 일부 struct 형식 인수는 크기가 클 수 있으며 긴밀한 루프 또는 중요한 코드 경로에서 메서드를 호출할 때 해당 구조를 복사하는 비용이 중요합니다. 메서드는 호출된 메서드가 인수의 상태를 수정하지 않으므로 in 매개 변수를 선언하여 해당 인수가 참조로 안전하게 전달될 수 있음을 지정합니다. 이러한 인수를 참조로 전달하면 (잠재적으로) 비용이 많이 드는 복사본을 방지할 수 있습니다.





오버로드 해결 규칙

in 인수에 대한 동기를 이해하여 값과 in 인수로 메서드의 오버로드 해결 규칙을 이해할 수 있습니다. in 매개 변수를 사용하여 메서드를 정의하면 잠재적인 성능 최적화가 이루어집니다. 일부 struct 형식 인수는 크기가 클 수 있으며 긴밀한 루프 또는 중요한 코드 경로에서 메서드를 호출할 때 해당 구조를 복사하는 비용이 중요합니다. 메서드는 호출된 메서드가 인수의 상태를 수정하지 않으므로 in 매개 변수를 선언하여 해당 인수가 참조로 안전하게 전달될 수 있음을 지정합니다. 이러한 인수를 참조로 전달하면 (잠재적으로) 비용이 많이 드는 복사본을 방지할 수 있습니다.

호출 사이트의 인수에 in을 지정하는 것은 일반적으로 선택 사항입니다. 값으로 인수를 전달하고 in 한정자를 사용하여 인수를 전달하는 것 사이에는 의미 체계상 차이가 없습니다. 호출 사이트의 in 한정자는 인수 값이 변경될 수 있음을 나타내지 않아도 되므로 선택 사항입니다. 호출 사이트에서 in 한정자를 명시적으로 추가하여 인수가 값이 아닌 참조로 전달되도록 합니다. 명시적으로 in을 사용하는 경우 다음과 같은 두 가지 효과가 있습니다.

먼저 호출 사이트에서 in을 지정하면 컴파일러가 일치하는 in 매개 변수로 정의된 메서드를 선택하게 됩니다. 그렇지 않으면 두 메서드가 in이 있을 때만 다른 경우 by 값 오버로드가 더 적합합니다.

둘째, in을 지정하면 참조로 인수를 전달할 의사가 있음을 선언하는 것입니다. in에 사용된 인수는 직접 참조할 수 있는 위치를 나타내야 합니다. out 및 ref 인수에는 동일한 일반 규칙이 적용됩니다. 상수, 일반 속성 또는 값을 생성하는 다른 식은 사용할 수 없습니다.그렇지 않으면 호출 사이트에서 in을 생략할 경우 메서드에 대한 읽기 전용 참조로 전달할 임시 변수를 만들 수 있도록 컴파일러에 알립니다. 컴파일러는 in 인수를 사용하여 몇 가지 제한 사항을 해결하기 위해 임시 변수를 만듭니다.

  • 임시 변수는 컴파일 시간 상수를 in 매개 변수로 허용합니다.
  • 임시 변수는 속성 또는 in 매개 변수에 대한 다른 식을 허용합니다.
  • 임시 변수는 인수 형식에서 매개 변수 형식으로의 암시적 변환이 있는 경우 인수를 허용합니다.

앞의 모든 인스턴스에서 컴파일러는 상수, 속성 또는 다른 식의 값을 저장하는 임시 변수를 만듭니다.

다음 코드에서는 이러한 규칙을 보여줍니다.


static void Method(in int argument) { // implementation removed } Method(5); // OK, temporary variable created. Method(5L); // CS1503: no implicit conversion from long to int short s = 0; Method(s); // OK, temporary int created with the value 0 Method(in s); // CS1503: cannot convert from in short to in int int i = 42; Method(i); // passed by readonly reference Method(in i); // passed by readonly reference, explicitly using `in`




이제 by 값 인수를 사용하는 다른 메서드를 사용할 수 있다고 가정하겠습니다. 결과는 다음 코드와 같이 변경됩니다.



static void Method(int argument) { // implementation removed } static void Method(in int argument) { // implementation removed } Method(5); // Calls overload passed by value Method(5L); // CS1503: no implicit conversion from long to int short s = 0; Method(s); // Calls overload passed by value. Method(in s); // CS1503: cannot convert from in short to in int int i = 42; Method(i); // Calls overload passed by value Method(in i); // passed by readonly reference, explicitly using `in`









ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/params

ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/out-parameter-modifier

ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/in-parameter-modifier

ref : https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/ref



이미지컷



+ Recent posts