EveryDay.DevUp

[Unity] 메서드 매개 변수 ( 값 형식, 참조 형식, in, out, ref, params ) 본문

C#

[Unity] 메서드 매개 변수 ( 값 형식, 참조 형식, in, out, ref, params )

EveryDay.DevUp 2021. 12. 28. 22:40

매개 변수의 전달

1. 값 형식 매개 변수

- 값 형식의 매개 변수를 전달 할 경우 값 형식의 복사본이 만들어져 메서드에 전달되게 됨

public class ValueTypeSample
{
    public int value = 0;

    public void AddValue( int value )
    {
        value++;
        Debug.Log(" AddValue : " + value);
    }
}

public class MethodParameter : MonoBehaviour
{
    private void Start()
    {
        ValueTypeSample valueTypeSample = new ValueTypeSample();

        // 현재 value 값은 0
        Debug.Log("value 1 : " + valueTypeSample.value);

        // AddValue에서 매개변수로 전달받은 value에 +1을 하여 value가 1로 나옴
        valueTypeSample.AddValue(valueTypeSample.value);

        // 다시 value를 출력했을 때 값은 0, 값으로 전달한 매개변수는 값의 복사본이 전달되어 
        // 해당 함수에서만 값의 변경이 생김 
        Debug.Log("value 2 : " + valueTypeSample.value);
    }
}

2. 참조 형식 매개 변수

- 참조형식의 매개 변수는 참조 중인 데이터를 가리키는 참조 값이 전달 됨

public class IntArraySample
{
    int[] intArray = new int[3];

    public void Start()
    {
        // 초기 값 0이 출력됨
        Debug.Log("intArray index zero before : " + intArray[0]);

        AddValueIndexZero(intArray);

        // 1이 출력됨
        // AddValueIndexZero의 참조 형식의 array를 매개변수로 전달했기 때문에 실제 값이 변경 됨
        Debug.Log("intArray index zero after : " + intArray[0]);
    }

    void AddValueIndexZero( int[] arr )
    {
        arr[0]++;
    }
}

3. in

- 참조 매개 변수, 매개 변수가 참조로 전달 되지만 수정은 할 수 없도록 전달 됨

- in 인수로 전달되기전 초기화되어야 함

- C# 7.2 이상에서 사용 가능

- 호출 메소드에 명시적으로 in을 사용하지 않아도 됨

- 매개 변수 한정자

1) 값 형식의 경우 값이 참조로 전달 되지만 값을 수정할 수는 없음 

public struct ValueTypeSample2
{
    public int value;
}

public class InSample
{
    public void Start()
    {
        ValueTypeSample2 valueType = new ValueTypeSample2();
        AddValueInValueType(in valueType);

        ValueTypeSample2 valueType2;
        // valueType2를 초기화 하지 않았기 때문에 에러가 발생함
        AddValueInValueType(in valueType2);
    }

    void AddValueInValueType(in ValueTypeSample2 valueType)
    {
        // 값 형식 in의 경우 값을 변경할 수 없기 때문에 에러가 발생함 
        valueType.value = 1;
    }
}

2) 참조 형식의 경우

public class InSample2
{
    public void Start()
    {
        ReferenceTypeSample referenceTypeSample = new ReferenceTypeSample();
        // 0 출력
        Debug.Log(" referenceTypeSample value before: " + referenceTypeSample.value);
        AddReferenceInValueType(in referenceTypeSample);
        // 1 출력
        Debug.Log(" referenceTypeSample value after: " + referenceTypeSample.value);

        ReferenceTypeSample referenceTypeSample2;
        // referenceTypeSample2를 초기화 하지 않았기 때문에 에러가 발생함
        AddReferenceInValueType(in referenceTypeSample2);
    }

    void AddReferenceInValueType(in ReferenceTypeSample referenceTypeSample)
    {
        // 참조 형식의 in의 경우 내부의 값을 변경할 수 있음
        referenceTypeSample.value = 1;

        // 새로운 할당을 할 경우 에러가 발생함
        referenceTypeSample = new ReferenceTypeSample();
    }
}

4. out

- 출력 매개 변수, 매개 변수가 참조로 전달되며 메소드 안에서 작업이 수행되어 할당이 되어야 함

- out으로 전달되기 전 초기화하지 않아도 되며, 초기화 해도 해당 값은 무시 된다.

- 메서드 정의와 호출 메서드 모두 명시적으로 out 키워드를 사용해야 함

- 매개 변수 한정자

public class ReferenceTypeSample
{
    public int value;
}

public class OutSample
{
    public int value = 100;

    public void Start()
    {
        // 출력 100
        Debug.Log("before Value : " + value);
        SetOutValue(out value);
        // 출력 1
        Debug.Log("after Value : " + value);

        ReferenceTypeSample referenceTypeSample;
        SetOutReferenceValue(out referenceTypeSample);
        // 출력 10
        Debug.Log("referenceTypeSample value : " + referenceTypeSample.value);

        // 호출 함수에서 변수를 정의해서 사용할 수 있음
        // C#7 이전에는 out 사용하기전 선언이 필요
        SetOutReferenceValue(out ReferenceTypeSample referenceTypeSample2);
        // 출력 10
        Debug.Log("referenceTypeSample2 value : " + referenceTypeSample2.value);

        // 필요 없는 경우 _로 생략이 가능함
        SetTwoValue(out int a, out _);
    }

    void SetOutValue(out int outValue)
    {
        outValue = 1;
    }

    void SetOutReferenceValue( out ReferenceTypeSample referenceTypeSample)
    {
        referenceTypeSample = new ReferenceTypeSample();
        referenceTypeSample.value = 10;
    }

    void SetTwoValue(out int a, out int b)
    {
        a = 1;
        b = 2;
    }
}

5. ref

- 참조 형식으로 값을 전달

1) 참조로 메소드 매개 변수 전달

- 메서드 정의와 호출 메서드 모두 명시적으로 ref를 사용해야 함

- 인수를 전달하기 전 초기화가 필요함

ㄱ. 값 형식에 사용할 경우

: 값 형식의 복사 대신 참조로 전달하여 메소드안에 값 변경이 값에 반영 됨

public class RefSampleValue
{
    public int value = 0;

    public void Start()
    {
        // 0 출력
        Debug.Log(" value before : " + value);
        AddValue(ref value);
        // 1출력, 값 형식 value 값의 변경이 반영 됨
        Debug.Log(" value before : " + value);
    }

    void AddValue( ref int value)
    {
        value++;
    }
}

ㄴ. 참조 형식에 사용할 경우

: 참조 매개 변수가 참조하는 개체를 바꿀 수 있음 

public class RefSampleReference
{
    public void Start()
    {
        ReferenceTypeSample referenceTypeSample = new ReferenceTypeSample();

        // 0 출력
        Debug.Log(" referenceTypeSample value : " + referenceTypeSample.value);
     
        SetValueNotRef(referenceTypeSample);
        // 0 출력, 참조 형식의 매개 변수를 전달해도 참조하는 개체를 바꿀 수는 없음 
        Debug.Log(" referenceTypeSample value not ref : " + referenceTypeSample.value);

        SetValueRef(ref referenceTypeSample);
        // 5출력, ref를 사용할 경우 전달한 참조 형식의 참조 개체를 바꿀 수 있음
        Debug.Log(" referenceTypeSample value ref : " + referenceTypeSample.value);
    }

    void SetValueNotRef(ReferenceTypeSample referenceTypeSample)
    {
        referenceTypeSample = new ReferenceTypeSample();
        referenceTypeSample.value = 3;
    }

    void SetValueRef(ref ReferenceTypeSample referenceTypeSample)
    {
        referenceTypeSample = new ReferenceTypeSample();
        referenceTypeSample.value = 5;
    }
}

2) 참조 반환 값 ( ref return )

- 메소드가 호출자에게 참조로 반환

- C#7 이상 사용 가능

struct MassiveStruct
{
    public int massive;
}

struct MassiveData
{
    public MassiveStruct[] massiveDataArray;

    public MassiveData(int size)
    {
        massiveDataArray = new MassiveStruct[size];
    }

    public MassiveStruct GetMassiveStructByIndex(int index)
    {
        return massiveDataArray[index];
    }

    public ref MassiveStruct GetRefMassiveStructByIndex( int index)
    {
        return ref massiveDataArray[index];
    }
}

public class RefReturnSample
{
    public void Start()
    {
        MassiveData massiveData = new MassiveData(1);
        
        MassiveStruct massiveStruct = massiveData.GetMassiveStructByIndex(0);
        massiveStruct.massive = 10;

        // 0 이 출력됨, 값의 복사 값이 return 되었기 때문에 massiveData에 영향을 주지 않음
        Debug.Log("massiveData Index " + massiveData.massiveDataArray[0].massive);

        ref MassiveStruct refMassiveStruct = ref massiveData.GetRefMassiveStructByIndex(0);
        refMassiveStruct.massive = 10;

        // 10이 출력 됨, 참조 값이 return 되었기 때문에 massiveData에 영향을 줌
        // 큰 구조체 데이터 값을 return할 경우의 성능 상 이점이 있을 수 있음
        Debug.Log("ref massiveData Index  " + massiveData.massiveDataArray[0].massive);
    }
}

3) 참조 로컬 

- 참조 로컬은 return ref를 사용하여 반환된 값을 참조하는 데 사용 

- 비 참조 반환값으로 초기화할 수 없음 

- 변수 선언 앞과 값을 참조로 반환하는 메소드 호출 바로 앞에 ref를 명시함으로써 사용

public class RefReturmSample2
{
    public void Start()
    {
        ReferenceTypeSample sample1 = new ReferenceTypeSample();
        ReferenceTypeSample sample2 = GetReferenceSampe(ref sample1);
        // 100, 100 출력
        Debug.Log(" sample value :" + sample1.value + " sample2 value :" + sample2.value);

        sample2 = new ReferenceTypeSample();
        sample2.value = 200;
        // 100, 200 출력, sample2가 가리키는 참조 개체가 새로 할당됬기 때문에 sample1과 sample2가 달라짐
        Debug.Log(" sample value :" + sample1.value + " sample2 value :" + sample2.value);

        ref ReferenceTypeSample sample3 = ref GetRefReferenceSampe(ref sample1);
        // 100, 100 출력
        Debug.Log(" sample value :" + sample1.value + " sample3 value :" + sample3.value);

        sample3 = new ReferenceTypeSample();
        sample3.value = 200;
        // 200, 200 출력, sample3과 sample1은 동일한 참조를 가리키고 있기 때문에 같은 값을 출력함 
        Debug.Log(" sample value :" + sample1.value + " sample3 value :" + sample3.value);
    }

    public ReferenceTypeSample GetReferenceSampe(ref ReferenceTypeSample sample)
    {
        sample = new ReferenceTypeSample();
        sample.value = 100;
        return sample;
    }

    public ref ReferenceTypeSample GetRefReferenceSampe(ref ReferenceTypeSample sample)
    {
        sample = new ReferenceTypeSample();
        sample.value = 100;
        return ref sample;
    }
}

5) 참조 읽기 전용 로컬 readonly 참조 로컬

- readonly를 통해 참조 로컬을 수정할 수 없도록 함

- 값 형식의 복사로 인한 오버헤드 문제를 해결하기 위함

- 2)의 행위로 반환 된 값 참조를 변경하지 않도록 하기 위해 사용 

public struct ReadOnlySampleStruct
{
    public int value;

    public void AddValue()
    {
        value++;
    }
}

public class ReadOnlySample2
{
    ReadOnlySampleStruct readOnlySampleStruct = new ReadOnlySampleStruct();

    public void Start()
    {
        ref readonly ReadOnlySampleStruct rost = ref GetSampleStruct(ref readOnlySampleStruct);
        
        // ref readonly 이기 때문에 값을 수정하려고할 경우 에러가 발생함
        rost.value = 2;

        // defensive copy가 발생함
        rost.AddValue();
    }

    ref readonly ReadOnlySampleStruct GetSampleStruct( ref ReadOnlySampleStruct readOnlySampleStruct)
    {
        return ref readOnlySampleStruct;
    }
}

ㄱ) defensive copy

: readonly struct에서 struct 멤버의 값을 내부 함수로 변경하려고할 경우 컴파일러에서 이를 방지하기 위해 복사본으로 함수를 진행

public struct ReadOnlySampleStruct
{
    public int value;

    public void AddValue()
    {
        //아래와 같이 함수가 실행됨
    	//ReadOnlySampleStruct temp = new ReadOnlySampleStruct();
        //temp.value++;
        value++;
    }
}

public class ReadOnlySample
{
    readonly ReadOnlySampleStruct readOnlySampleStruct = new ReadOnlySampleStruct();
    
    public void Start()
    {
        // error - readonly 구조체로 value에 직접적인 수정이 불가능함
        readOnlySampleStruct.value = 1;

        // 0 출력
        Debug.Log(" value 1: " + readOnlySampleStruct.value);
        readOnlySampleStruct.AddValue();

        // 1이 출력될 것 같지만, 0이 출력됨. readonly struct로 정의되어 있기 때문에 
        // 컴파일러에서 struct.value를 수정하지 않도록 하기 위해 복사본을 만들어서 value++을 함
        // readOnlySampleStruct의 값은 변경되지 않음 이를 defensvie copy 라함 
        Debug.Log(" value 2: " + readOnlySampleStruct.value);
    }
}

ㄴ) readonly struct

: defensive copy문제를 해결하기 위해 struct를 readonly로 정의

// readonly struct는 멤버도 reaonly로 정의되기 때문에 값 변경이 명시적으로 불가능함을 알 수 있음
// defensive copy는 코드에서 에러가 나지 않기 때문에 프로그래머가 해당 로직을 알지 못하면 원인 파악이 힘들 수 있음 
public readonly struct ReadOnlySampleStruct2
{
    public readonly int value;

    public int AddValue()
    {
        int addValue = value;
        addValue++;

        return addValue;
    }
}

* ref, in, out 사용 시 이슈

ㄱ) 메서드 오버로딩 제한

public class MethodOverloading
{
    public void Add(int i) { }
    public void Add(ref int i) { }

    // error - ref, in, out 중 하나만을 사용해서 함수 오버로딩을 사용할 수 있음.
    // ref 로 Add의 인수가 하나인 함수를 이미 정의 했기 때문에 in을 사용해서 함수 오버로딩을 할 수 없음
    public void Add(in int i) { }

    // 매개변수의 개수가 다르면 오버로딩할 수 있음
    public void Add(ref int i, ref int j) { }

    // error - 매개 변수의 개수가 동일하고 ref, in, out으로 정의가 된 함수가 있기 때문에 오버로딩을 할 수 없음
    public void Add(out int i, ref int j) { }
}

ㄴ) async 한정자를 사용하여 정의하는 비동기 메소드에는 사용할 수 없음

ㄷ) yield return 또는 yield break문을 포함하는 반복기 메소드에는 사용할 수 없음

ㄹ) 확장 메서드의 첫번째 인수에는 out을 사용할 수 없음

ㅁ) 인수가 구조체가 아니거나, 구조체로 제한되지 않는 제네릭 형식의 경우 확장 메소드 첫번째 인수에 ref 키워드를 사용할 수 없음

ㅂ) 첫번째 인수가 구조체인 경우 이외에는 in키워드를 사용할 수 없고, 구조체로 제한되는 경우에는 제네릭 형식에 in 키워드를 사용할 수 없음 

6. params

- 가변 개수의 인수를 사용할 수 있도록 함 

- params 키워드 뒤에는 추가 매개 변수를 사용할 수 없음 

- 1차원 배열이 아닐 경우 컴파일 오류가 발생

public class ParamsSample
{
    void Start()
    {
        // 매개 변수가 가변적이기 때문에 동일 함수를 사용할 수 있음 
        Add(1);
        Add(2, 3);
    }

    public void Add( params int[] values) {}
}

 

* 출처 및 참고 블로그

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

 

메서드 매개 변수 - C# 참조

메서드 매개 변수(C# 참조) 이 문서의 내용 --> in, ref 또는 out 없이 메서드에 대해 선언된 매개 변수는 값으로 호출된 메서드에 전달됩니다. 메서드에서 해당 값을 변경할 수 있지만, 제어가 호출

docs.microsoft.com

https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/passing-parameters

 

매개 변수 전달 - C# 프로그래밍 가이드

값 또는 참조로 C#의 매개 변수에 인수를 전달할 수 있습니다. 참조로 전달되는 인수에 대한 변경 내용이 유지됩니다. Ref 또는 out을 사용하여 참조로 전달합니다.

docs.microsoft.com

https://www.csharpstudy.com/Latest/CS7-outvar.aspx

 

C# 7.0 out 파라미터 - C# 프로그래밍 배우기 (Learn C# Programming)

C# 7 에서 편리해진 out 파라미터 이전 C# 버전에서는 메서드 호출시 out 파라미터를 사용할 때 미리 out 파리미터의 타입을 앞에 선언해야 했다. 이는 함수를 호출하는 문장 밖에 변수를 선언함으로

www.csharpstudy.com

https://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0&detail=1&wid=11526 

 

.NET Framework: 752. C# 7.2 - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가

글쓴 사람 정성태 (techsharer at outlook.com) 홈페이지 첨부 파일 [ref_readonly_return.zip]     부모글 보이기/감추기 C# 7.2 - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가 C# 7.2 (1) - readonly 구조체 ;

www.sysnet.pe.kr

https://www.sysnet.pe.kr/2/0/11524

 

.NET Framework: 750. C# 7.2 - readonly 구조체

.NET Framework: 750. C# 7.2 - readonly 구조체 [링크 복사], [링크+제목 복사] 조회: 6362 글쓴 사람 정성태 (techsharer at outlook.com) 홈페이지 첨부 파일 부모글 보이기/감추기 C# 7.2 - readonly 구조체 C# 7.2 (1) - read

www.sysnet.pe.kr

https://www.sysnet.pe.kr/2/0/11525

 

.NET Framework: 751. C# 7.2 - 메서드의 매개 변수에 in 변경자 추가

.NET Framework: 751. C# 7.2 - 메서드의 매개 변수에 in 변경자 추가 [링크 복사], [링크+제목 복사] 조회: 6180 글쓴 사람 정성태 (techsharer at outlook.com) 홈페이지 첨부 파일 부모글 보이기/감추기 C# 7.2 - 메

www.sysnet.pe.kr