프로그래밍 언어/C#

C# out 키워드

게임 개발 2024. 7. 27. 20:17

 
 
 out 키워드는 다음 두 가지 상황에서 out 키워드를 사용할 수 있습니다.
 

  • 값이 아닌 참조로 메서드에 인수를 전달할 수 있도록 하는 매개 변수 한정자로 사용
  • 형식 매개 변수를 공변(covariant) 으로 지정하는 인터페이스 및 대리자에 대한
  • 제너릭 형식 매개  변수 선언에서 사용

 
 
 

out의 매개 변수 한정자 사용 경우

 
out의 매개 변수 한정자의 사용을 말하기전에,
매개 변수 한정자가 무엇인지 간단히 짚고 넘어가겠습니다.
 

매개 변수 한정자란?


기본적으로 C#의 인수는 call by value로 함수에 전달됩니다.
이는 변수의 복사본이 메서드에 전달된다는 의미와 같습니다. (= 깊은 복사)
 
값 형식의 경우 값의 복사본이 메서드에 전달되며, ( = 깊은 복사 )
참조 형식의 경우 참조의 복사본이 메서드에 전달됩니다. ( = 얕은 복사 )
 

  • 값으로 전달은 메서드에 변수의 복사본을 전달한다는 의미
  • 참조로 전달은 메서드에 변수에 대한 엑세스를 전달한다는 의미
  • 참조 형식 변수에는 해당 데이터에 대한 참조가 포함된다.
  • 값 형식 변수에는 해당 데이터가 직접 포함된다.

 
구조체는 값 형식(C# 참조)이므로 메서드에 구조체를 값으로 전달하는 경우
메서드가 구조체 인수의 복사본을 받아서 작동합니다.
 
메서드가 호출 메서드의 원래 구조체에 액세스할 수 없으므로
어떤 방식으로든 변경할 수 없습니다.
메서드는 복사본만 변경할 수 있습니다.
 
클래스 인스턴스는 값 형식이 아니라 참조 형식입니다.
메서드에 참조 형식을 값으로 전달하는 경우
메서드가 클래스 인스턴스에 대한 참조의 복사본(= 얕은 복사)을 받습니다.
두 변수 모두 동일한 개체를 참조하고 있다는 뜻이 됩니다.
매개 변수는 참조의 복사본을 ( = 즉 같은 주소지를 가리키고 있는 것)
사용하여 인스턴스 멤버에 액세스할 수 있습니다.
 
즉 호출된 메서드가 인스턴스 멤버를 변경하는 경우
호출 메서드도 동일한 인스턴스를 참조하므로
해당 호출 메서드가 끝나더라도 변경된 내용을 확인할 수 있습니다.
 
다음 예제의 출력에서 차이점을 보여줍니다.
ClassTaker메서드는 매개 변수의 주소를 사용하여
클래스 인스턴스의 지정된 필드를 찾기 때문에 willIChange 필드의 값을 변경합니다.
호출 메서드에 있는 구조체의 willIChange 필드는 StructTaker 호출에서 변경되지 않습니다.
왜냐하면 인수 값은 구조체 주소의 복사본이 아니라 구조체 자체의 복사본이기 때문입니다.
StructTaker는 복사본을 변경하고,
StructTaker 호출이 완료되면 복사본이 손실됩니다.
 

class TheClass
{
    public string? willIChange;
}

struct TheStruct
{
    public string willIChange;
}

class TestClassAndStruct
{
    static void ClassTaker(TheClass c)
    {
        c.willIChange = "Changed";
    }
    static void StructTaker(TheStruct s)
    {
        s.willIChange = "Changed";
    }

    public static void Main()
    {
        TheClass testClass = new TheClass();
        TheStruct testStruct = new TheStruct();

        testClass.willIChange = "Not Changed";
        testStruct.willIChange = "Not Changed";

        ClassTaker(testClass);
        StructTaker(testStruct);

        Console.WriteLine("Class field = {0}", testClass.willIChange); 
        Console.WriteLine("Struct field = {0}", testStruct.willIChange);
    }
}

 


인수가 전달되는 방법 및 인수가 참조 형식 또는 값 형식인지 여부는

호출자로부터 표시되는 인수 수정 사항을 확인할 수 있습니다.

 

 

  • value를 값으로의 전달의 경우 (When you pass a value type by value) :
    - 만약 메서드가 매개 변수를 다른 개체를 참조하도록 할당하면, 
    이런 변경사항은 호출자에게 표시되지 않습니다.
    - 매개 변수에 의해 참조되는 객체의 상태를 변경하는 경우,
    변경 사항은 호출자로 부터 보이지 않습니다.
  • reference를 값으로 전달한 경우 (When you pass reference type by value) :
    - 만약 메서드가 매개 변수를 다른 개체를 참조하도록 할당하면,
    해당 변경 사항은 호출자에게서 볼 수 없습니다.
    - 메소드가 파라미터에 의해 참조되는 객체의 상태를 수정하면,
    그 변경들은 호줄자로부터 볼 수 있습니다.
  • value를 참조 형식으로 전달한 경우 (When you pass a value type by reference) :
    - 메소드가 매개 변수를 다른 개체를 참조하도록 할당하면,
    변경 사항은 호출자에게서 볼 수 없습니다.
    - 메소드가 파라미터에 의해 참조하는 개체의 상태를 수정하면,
    호출자에게 해당 변경 내용이 표시됩니다.
  • reference를 참조 형식으로 전달한 경우 (When you pass a reference type by reference) : 
    - 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우,
    해당 변경 내용은 호출자에게 표시됩니다.
    - 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면,
    호출자에게 해당 변경 내용이 표시됩니다.

 

참조 형식으로 참조로 전달하는 경우 호출된 메서드는 참조 매개 변수가 호출자에서

참조하는 개체를 바꿀 수 있습니다.

개체의 스토리지 위치는 참조 매개 변수의 값으로 메서드에 전달됩니다.

매개 변수의 스토리지 위치에서 값을 변경하여 새 개체를 가리키도록 하면

호출자가 참조하는 스토리지 위치도 변경됩니다.

다음 예제에서는 참조 형식 인스턴스를 ref 매개 변수로 전달합니다.

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 ChangeByReference(ref Product itemRef)
{
    // itemRef 파라미터에 저장된 주소를 변경합니다.
    itemRef = new Product("Stapler", 12345);
}

private static void ModifyProductsByReference()
{
    // 제품의 인스턴스를 선언하고 초기 값을 표시합니다.
    Product item = new Product("Fasteners", 54321);
    System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);

    // 제품 인스턴스를 ChangeByReference에 전달합니다.
    ChangeByReference(ref item);
    System.Console.WriteLine("Calling method.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);
}


 

참조 및 값의 안전한 컨텍스트

 

메서드는 매개 변수 값을 필드에 저장할 수 있습니다.

매개 변수가 값으로 전달되는 경우 일반적으로 안전합니다.

값이 복사되며 필드에 저장된 경우 참조 형식에 연결할 수 있습니다.

매개 변수를 안전하게 참조로 전달하려면

컴파일러가 새 변수에 참조흘 할당하는 것이 안전한 시기를 정의해야 합니다.

 

 

모든 식에 대해 컴파일러는 식이나 변수에 대한 액세스를 제한하는 안전한 컨텍스트를 정의합니다.

  • safe-context는 모든 식에 안전하게 액세스할 수 있는 범위를 정의합니다.
  • ref-safe-context는 식에 대한 참조가 안전하게 액세스되거나 수정될 수 있는 범위를 정의합니다.

 

비공식적으로 이러한 범위를 코드가 더 이상 유효하지 않은 참조를 액세스하거나

수정하지 않도록 하는 메커니즘으로 생각할 수 있습니다.

참조는 유효한 개체 또는 구조체를 참조하는 한 유효합니다.

safe-context는 변수가 할당되거나 다시 할당될 수 있는 시기를 정의합니다.

ref-safe-context는 변수가 참조 할당됨 또는 참조 재할당됨될 수 있는 시기를 정의합니다.

할당은 변수를 새 값에 할당합니다. 참조 할당은 다른 스토리지 위치를 참조할 변수를 할당합니다.

 

참조 매개 변수

 

값 대신 참조로 인수를 전달하려면 매개 변수 선언에 다음 한정자 중 하나를 적용합니다.

 

  • ref : 메서드를 호출하기 전에 인수를 초기화해야 합니다.
    메서드는 매개 변수에 새 값을 할당할 수 있지만 반드시 그렇게 할 필요는 없습니다.
  • out : 호출 메서드는 메서드를 호출하기 전에 인수를 초기화할 필요가 없습니다.
    메서드는 매개 변수에 값을 할당해야 합니다.
  • readonly ref : 메서드를 호출하기 전에 인수를 초기화해야 합니다.
    메서드는 매개 변수에 새 값을 할당할 수 없습니다.
  • in : 메서드를 호출하기 전에 인수를 초기화해야 합니다.
    메서드는 매개 변수에 새 값을 할당할 수 없습니다.
    컴파일러는 in 매개변수에 대한 인수 복사본을 보관하기 위해 임시 변수를 만들 수 있습니다.

클래스의 멤버는 ref, ref readonly, in 또는 out만을 가지며, 다른 서명을 포함할 수 없습니다.

특정 형식의 두 멤버가 하나는 ref 매개 변수를 포함하고 다른 하나는 out, ref readonly 또는

in 매개 변수를 포함한다는 것 외에는 차이가 없으면

컴파일러 오루가 발생합니다.

그러나 다음 예제에 나와 있는 것처럼 메서드 하나에는 ref, ref readonly, in 또는 out 매개 변수가 포함되어 있고

다른 하나에는 ref, ref readonly, in 또는 out 매개 변수가 포함되어 있고

다른 하나에는 값으로 전달되는 매개 변수가 포함되어 있으면 메서드를 오버로드할 수 있습니다.

숨기거나 재정의와 같이 서명이 일치해야 하는 다른 상황에서는

in,ref,ref eadonly 및 out이 서명의 일부가 되며 서로 일치하지 않습니다.

 

매개 변수에 이전 한정자 중 하나가 있는 경우 해당 인수는 호환 가능한 한정자를 가질 수 있습니다.

 

  • ref 매개 변수의 인수에는 ref 한정자가 포함되어야 합니다.
  • out 매개 변수의 인수에는 out 한정자가 포함되어야 합니다.
  • in 매개 변수의 인수는 선택적으로 in 한정자를 포함할 수 있습니다.
    대신 ref 한정자를 인수에 사용하면 컴파일러가 경고를 발급합니다.
  • ref readonly 매개 변수의 인수에는
    in 또는 ref 한정자가 포함되어야 하지만 둘 다 포함되어서는 안됩니다.
    두 한정자가 모두 포함되어 있지 않으면 컴파일러는 경고를 발급합니다.

 

이러한 한정자를 사용하면 인수가 사용되는 방법을 설명합니다.

 

  • ref는 메서드가 인수 값을 읽거나 쓸 수 있음을 의미합니다.
  • out은 메서드가 인수 값을 설정함을 의미합니다.
  • ref readonly는 메서드가 읽기는 하지만 인수 값을 쓸 수 없음을 의미합니다.
    인수는 참조로 전달되어야 합니다.
  • in는 메서드가 읽기는 하지만 인수 값을 쓸 수 없음을 의미합니다.
    인수는 참조로 전달되거나 임시 변수를 통해 전달됩니다.

 

다음 종류의 메서드에서는 이전 매개 변수 한정자를 사용할 수 없습니다.

 

  • async 한정자를 사용하여 정의하는 비동기 메서드
  • yield return 또는 yield break 문을 포함하는 반복기 메서드

 

확장 메서드에는 다음 인수 키워드 사용에 대한 제한 사항도 있습니다.

 

  • 확장 메서드의 첫 번째 인수에는 out 키워드를 사용할 수 없습니다.
  • 인수가 struct가 아니거나 구조체로 제한되지 않는 제네릭 형식인 경우
    확장 메서드의 첫 번째 인수에 ref 키워드를 사용할 수 없습니다.
  • 첫 번째 인수가 struct가 아니면 ref readonly 및 in 키워드를 사용할 수 없습니다.
  • ref readonly 및 in 키워드는
    구조체로 제한되는 경우에도 제네릭 형식에 사용할 수 없습니다.

속성은 변수가 아닙니다. 메서드입니다. 속성은 ref 매개 변수에 대한 인수일 수 없습니다.

 

 

ref 매개 변수 한정자

 

ref 매개 변수를 사용하려면 다음 예제에 나와 있는 것처럼

메서드 정의와 호출 메서드가 모두 ref 키워드를 명시적으로 사용해야 합니다.

(COM을 호출할 때 호출 메서드가 ref을 생략할 수 있다는 사실은 제외입니다.)
out 매개 변수를 사용하려면 메서드 

 

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

 

out 매개 변수 한정자

 

 

out 매개 변수를 사용하려면 메서드 정의와 호출 메서드가

모두 명시적으로 out 키워드를 사용해야 합니다

예시는 다음 코드와 같습니다.

int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod);     // value is now 44

void OutArgExample(out int number)
{
    number = 44;
}

 

out 인수로 전달되는 변수는 메서드 호출에서 전달되기 전에 초기화할 필요가 없지만

호출된 메서드는 메서드가 반환도기 전에 값을 할당해야 합니다.

 

Deconstruct 메서드는 out 한정자를 사용하여 매개 변수를 선언하여 여러 값을 반환합니다.

다른 메서드는 여러 반환 값에 대해 값 튜플을 반환할 수 있습니다.

 

변수를 out 인수로 전달하기 전에 별도의 문에서 변수를 선언할 수 있습니다.

별도의 변수 선언이 아닌 메서드 호출의 인수 목록에서 out 변수를 선언할 수도 있습니다.

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

 

암시적 형식 지역 변수를 선언할 수도 있습니다.

 

 

ref readonly 한정자

 

ref readonly 한정자는 메서드 선언에 있어야 합니다.

호출 사이트의 한정자는 선택 사항입니다. in 또는 ref 한정자를 사용할 수 있습니다.

ref readonly 한정자는 호출 사이트에서 유효하지 않습니다.

호출 사이트에서 사용하는 한정자는 인수의 특성을 설명하는 데 도움이 될 수 있습니다.

인수가 변수이고 쓰기가 가능한 경우에만 ref를 사용할 수 있습니다.

 

인수가 변수인 경우에만 in을 사용할 수 있습니다.

쓰기 가능하거나 읽기 전용일 수 있습니다.

인수가 변수가 아니고 식인 경우 한정자를 추가할 수 없습니다.

 

다음 예에서는 이러한 조건을 보여줍니다.

다음 메서드는 ref readonly 한정자를 사용하여

성능상의 이유로 큰 구조체가 참조로 전달되어야 함을 나타냅니다.

 

public static void ForceByRef(ref readonly OptionStruct thing)
{
    // elided
}

 

 

ref 또는 in 한정자를 사용하여 메서드를 호출할 수 있습니다.

한정자를 생략하면 컴파일러가 경고를 발급합니다.

인수가 변수가 아닌 식인 경우 in 또는 ref 한정자를 추가할 수 없으므로 경고를 표시하지 않아야 합니다.

 

in 매개 변수 한정자

 

in 한정자는 메서드 선언에 필요하지만 호출 사이트에서는 필요하지 않습니다.

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;
}

 

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/method-parameters#out-parameter-modifier

 

메서드 매개 변수 - C# reference

메서드 매개 변수는 값으로 전달됩니다. 한정자는 읽기 전용 및 'out' 매개 변수와 같은 구분을 포함하여 참조별 전달 의미 체계를 사용하도록 설정합니다. params 한정자는 일련의 선택적 인수를

learn.microsoft.com

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/out

 

out 키워드 - C# reference

out 키워드 - C# 참조

learn.microsoft.com

 

'프로그래밍 언어 > C#' 카테고리의 다른 글

프로퍼티, 델리게이트, 이벤트 간단 정리  (0) 2024.01.08
C#  (0) 2023.12.17