메서드 매개 변수
기본적으로 C#의 인수는 값으로 함수에 전달됩니다. 즉, 변수의 복사본이 메서드에 전달됩니다. 값(struct) 형식의 경우 값의 복사본이 메서드에 전달됩니다. 참조(class) 형식의 경우 참조의 복사본이 메서드에 전달됩니다. 매개 변수 한정자를 사용하면 참조로 인수 를 전달할 수 있습니다. 다음 개념은 이러한 차이점과 매개 변수 한정자를 사용하는 방법을 이해하는 데 도움이 됩니다.
- 값으로 전달은 메서드에 변수의 복사본을 전달한다는 의미입니다.
- 참조로 전달은 메서드에 변수에 대한 액세스를 전달한다는 의미입니다.
- 참조 형식 변수에는 해당 데이터에 대한 참조가 포함됩니다.
- 값 형식 변수에는 해당 데이터가 직접 포함됩니다.
구조체는 값 형식이므로 메서드에 값으로 구조체를 전달할 때 메서드는 구조체 인수의 복사본을 받고 작동합니다. 메서드가 호출 메서드의 원래 구조체에 액세스할 수 없으므로 어떤 방식으로든 변경할 수 없습니다. 메서드는 복사본만 변경할 수 있습니다.
클래스 인스턴스는 값 형식이 아닌 참조 형식입니다. 메서드에 참조 형식을 값으로 전달하는 경우 메서드가 클래스 인스턴스에 대한 참조의 복사본을 받습니다. 두 변수 모두 동일한 개체를 참조합니다. 매개 변수는 참조의 복사본입니다. 호출된 메서드는 호출 메서드에서 인스턴스를 다시 할당할 수 없습니다. 그러나 호출된 메서드는 참조의 복사본을 사용하여 인스턴스 멤버에 액세스할 수 있습니다. 호출된 메서드가 인스턴스 멤버를 변경하는 경우 호출 메서드는 동일한 인스턴스를 참조하므로 이러한 변경 내용도 확인합니다.
다음 예제의 출력에서 차이점을 보여 줍니다. 메서드 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);
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
인수가 전달되는 방법 및 인수가 참조 형식인지 값 형식인지 여부는 호출자에서 표시되는 인수 수정 내용을 제어합니다.
- 값 형식을 값으로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 값으로 참조 형식을 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
- 참조로 값 형식을 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
- 참조 형식을 참조로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시됩니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
참조 형식을 참조로 전달하는 경우 호출된 메서드는 참조 매개 변수가 호출자에서 참조하는 개체를 바꿀 수 있습니다. 개체의 스토리지 위치는 참조 매개 변수의 값으로 메서드에 전달됩니다. 매개 변수의 스토리지 위치에서 값을 변경하여 새 개체를 가리키도록 하면 호출자가 참조하는 스토리지 위치도 변경됩니다. 다음 예제에서는 참조 형식 인스턴스를 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)
{
// Change the address that is stored in the itemRef parameter.
itemRef = new Product("Stapler", 12345);
}
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("Calling method. Name: {0}, ID: {1}\n",
item.ItemName, item.ItemID);
}
// This method displays the following output:
// Original values in Main. Name: Fasteners, ID: 54321
// Calling method. Name: Stapler, ID: 12345
참조 및 값의 컨텍스트 금고
메서드는 매개 변수 값을 필드에 저장할 수 있습니다. 매개 변수가 값으로 전달되는 경우 일반적으로 안전합니다. 값이 복사되며 필드에 저장된 경우 참조 형식에 연결할 수 있습니다. 매개 변수를 안전하게 참조로 전달하려면 컴파일러가 새 변수에 참조를 할당하는 것이 안전한 시기를 정의해야 합니다. 모든 식에 대해 컴파일러는 식 또는 변수에 대한 액세스를 경계하는 안전한 컨텍스트 를 정의합니다. 컴파일러는 안전 컨텍스트 및 ref-safe-context의 두 가지 범위를 사용합니다.
- 안전 컨텍스트는 식에 안전하게 액세스할 수 있는 범위를 정의합니다.
- ref-safe-context는 식에 대한 참조를 안전하게 액세스하거나 수정할 수 있는 범위를 정의합니다.
비공식적으로 이러한 범위를 코드가 더 이상 유효하지 않은 참조를 액세스하거나 수정하지 않도록 하는 메커니즘으로 생각할 수 있습니다. 참조는 유효한 개체 또는 구조체를 참조하는 한 유효합니다. 안전 컨텍스트는 변수를 할당하거나 다시 할당할 수 있는 시기를 정의합니다. ref-safe-context는 변수를 ref 할당하거나 ref를 다시 할당할 수 있는 시기를 정의합니다. 할당은 변수를 새 값에 할당합니다. 참조 할당은 다른 스토리지 위치를 참조할 변수를 할당합니다.
참조 매개 변수
매개 변수 선언에 다음 한정자 중 하나를 적용하여 값 대신 참조로 인수를 전달합니다.
ref: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 있지만 그렇게 할 필요는 없습니다.out: 메서드를 호출하기 전에 호출 메서드가 인수를 초기화할 필요가 없습니다. 메서드는 매개 변수에 값을 할당해야 합니다.readonly ref: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 없습니다.in: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 없습니다. 컴파일러는 매개 변수에 대한 인수의 복사본을 보관하는 임시 변수를in만들 수 있습니다.
클래스의 멤버는 , ref readonly또는 inout.와만 ref다른 서명을 가질 수 없습니다. 컴파일러 오류는 형식의 두 멤버 간의 유일한 차이점은 그 ref 중 하나에 매개 변수가 있고 다른 하나는 매개 변수 또는 in 매개 변수가 outref readonly있다는 것입니다. 그러나 다음 예제와 같이 한 메서드에 , ref readonly또는 inout 매개 변수가 있고 다른 메서드ref에 값으로 전달되는 매개 변수가 있는 경우 메서드를 오버로드할 수 있습니다. 숨기거나 재정 inrefref readonlyout 의하는 것과 같이 서명 일치가 필요한 다른 상황에서는 서명의 일부이며 서로 일치하지 않습니다.
매개 변수에 이전 한정자 중 하나가 있는 경우 해당 인수에 호환되는 한정자가 있을 수 있습니다.
- 매개 변수에 대한 인수에는 한
ref정자가ref포함되어야 합니다. - 매개 변수에 대한 인수는 한
out정자를out포함해야 합니다. - 매개 변수에 대한 인수는
in선택적으로 한정자를 포함할in수 있습니다.ref대신 인수에 한정자를 사용하면 컴파일러에서 경고를 발생합니다. - 매개 변수에 대한 인수에는
ref readonly한정자 또는ref한정자가 포함되어야 하지만 둘 다 포함in할 수는 없습니다. 두 한정자가 모두 포함되지 않으면 컴파일러에서 경고를 발생합니다.
이러한 한정자를 사용하면 인수가 사용되는 방법을 설명합니다.
ref는 메서드가 인수의 값을 읽거나 쓸 수 있는 것을 의미합니다.out는 메서드가 인수의 값을 설정하는 것을 의미합니다.ref readonly는 메서드가 읽지만 인수 값을 쓸 수 없음을 의미합니다. 인수 는 참조로 전달되어야 합니다 .in는 메서드가 읽지만 인수 값을 쓸 수 없음을 의미합니다. 인수는 참조 또는 임시 변수를 통해 전달됩니다.
속성은 변수가 아닙니다. 메서드이며 매개 변수에 ref 전달할 수 없습니다. 다음 종류의 메서드에서는 이전 매개 변수 한정자를 사용할 수 없습니다.
- async 한정자를 사용하여 정의하는 비동기 메서드
- yield return 또는
yield break문을 포함하는 반복기 메서드
확장 메서드에는 다음과 같은 인수 키워드(keyword) 사용에 대한 제한 사항도 있습니다.
out확장 메서드의 첫 번째 인수에는 키워드(keyword) 사용할 수 없습니다.- 인수가
ref아니거나 구조체로 제한되지 않은 제네릭 형식인 경우 확장 메서드의struct첫 번째 인수에서 키워드(keyword) 사용할 수 없습니다. - 첫 번째 인수
struct가ref readonly아닌 경우 및in키워드(keyword) 사용할 수 없습니다. ref readonly구조체로 제한되는 경우에도 제네릭 형식과in키워드(keyword) 사용할 수 없습니다.
ref 매개 변수 한정자
ref 매개 변수를 사용하려면 다음 예제에 나와 있는 것처럼 메서드 정의와 호출 메서드가 모두 ref 키워드를 명시적으로 사용해야 합니다. (COM을 호출할 때 호출 메서드가 ref를 생략할 수 있다는 사실은 제외입니다.)
void Method(ref int refArgument)
{
refArgument = refArgument + 44;
}
int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45
매개 변수에 ref 전달되는 인수는 전달되기 전에 초기화해야 합니다.
out 매개 변수 한정자
out 매개 변수를 사용하려면 메서드 정의와 호출 메서드가 모두 명시적으로 out 키워드를 사용해야 합니다. 예시:
int initializeInMethod;
OutArgExample(out initializeInMethod);
Console.WriteLine(initializeInMethod); // value is now 44
void OutArgExample(out int number)
{
number = 44;
}
인수로 out 전달된 변수는 메서드 호출에서 전달되기 전에 초기화할 필요가 없습니다. 호출된 메서드는 메서드가 반환되기 전에 값을 할당해야 합니다.
분해 메서드 는 여러 값을 반환하기 위해 한정자를 사용하여 해당 매개 변수 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 한정자는 메서드 선언에 있어야 합니다. 호출 사이트의 한정자는 선택 사항입니다. 또는 ref 한 in 정자를 사용할 수 있습니다. ref readonly 한정자는 호출 사이트에서 유효하지 않습니다. 호출 사이트에서 사용하는 한정자는 인수의 특성을 설명하는 데 도움이 될 수 있습니다. 인수가 변수이고 쓰기 가능한 경우에만 사용할 ref 수 있습니다. 인수가 변수인 경우에만 사용할 in 수 있습니다. 쓰기 가능하거나 읽기 전용일 수 있습니다. 인수가 변수가 아니라 식인 경우 한정자를 추가할 수 없습니다. 다음 예제에서는 이러한 조건을 보여 줍니다. 다음 메서드는 한정자를 사용하여 ref readonly 성능상의 이유로 큰 구조체를 참조로 전달해야 함을 나타냅니다.
public static void ForceByRef(ref readonly OptionStruct thing)
{
// elided
}
또는 in 한정자를 사용하여 메서드를 호출할 ref 수 있습니다. 한정자를 생략하면 컴파일러에서 경고를 발생합니다. 인수가 변수가 아닌 식인 경우 추가하거나 ref 한정자를 추가할 in 수 없으므로 경고를 표시하지 않아야 합니다.
ForceByRef(in options);
ForceByRef(ref options);
ForceByRef(options); // Warning! variable should be passed with `ref` or `in`
ForceByRef(new OptionStruct()); // Warning, but an expression, so no variable to reference
변수가 변수인 readonly 경우 한정자를 in 사용해야 합니다. 대신 한정자를 사용하는 경우 컴파일러에서 ref 오류를 발생합니다.
ref readonly 한정자는 메서드가 인수가 변수가 아닌 식이 아닌 변수가 될 것으로 예상한다는 것을 나타냅니다. 변수가 아닌 식의 예는 상수, 메서드 반환 값 및 속성입니다. 인수가 변수가 아니면 컴파일러에서 경고를 발생합니다.
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;
}
in 한정자를 사용하면 컴파일러가 인수에 대한 임시 변수를 만들고 해당 인수에 대한 읽기 전용 참조를 전달할 수 있습니다. 컴파일러는 인수를 변환해야 하거나, 인수 형식에서 암시적 변환이 있거나, 인수가 변수가 아닌 값인 경우 항상 임시 변수를 만듭니다. 예를 들어 인수가 리터럴 값이거나 속성 접근자에서 반환된 값인 경우입니다. API에서 인수를 참조로 전달해야 하는 경우 한정자 대신 in 한정자를 선택합니다ref readonly.
매개 변수를 사용하여 in 정의된 메서드는 성능 최적화를 얻을 수 있습니다. 일부 struct 형식 인수의 크기는 클 수 있으며, 꽉 닫힌 루프 또는 중요한 코드 경로에서 메서드를 호출하는 경우 해당 구조를 복사하는 데 드는 비용이 상당합니다. 메서드는 호출된 메서드가 해당 인수의 상태를 수정하지 않으므로 인수를 참조로 안전하게 전달할 수 있도록 지정하는 매개 변수를 선언 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`
이제 값별 인수를 사용하는 다른 메서드를 사용할 수 있다고 가정합니다. 결과는 다음 코드와 같이 변경됩니다.
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`
인수가 참조로 전달되는 유일한 메서드 호출이 마지막입니다.
참고 항목
앞의 코드는 단순화를 위해 인수 형식으로 int를 사용합니다. int는 대부분의 최신 컴퓨터에서 참조보다 크지 않기 때문에 단일 int를 읽기 전용 참조로 전달하면 아무런 이점이 없습니다.
params 한정자
메서드 선언에서 키워드(keyword) 이후에 params 는 다른 매개 변수가 허용되지 않으며 메서드 선언에는 키워드(keyword) 하나 params 만 허용됩니다.
선언된 매개 변수 형식이 params 1차원 배열이 아니면 컴파일러 오류 CS0225 가 발생합니다.
params 매개 변수를 사용하여 메서드를 호출하면 다음을 전달할 수 있습니다.
- 배열 요소 형식의 쉼표로 구분된 인수 목록입니다.
- 지정된 형식의 인수 배열입니다.
- 인수가 없습니다. 인수를 보내지 않는 경우
params목록의 길이는 0입니다.
다음 예제에서는 params 매개 변수에 인수를 보낼 수 있는 다양한 방법을 보여 줍니다.
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)
{
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(1, 2, 3, 4);
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 = { 5, 6, 7, 8, 9 };
UseParams(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
UseParams2(myObjArray);
// 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.
UseParams2(myIntArray);
}
}
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
.NET feedback
피드백
다음에 대한 사용자 의견 제출 및 보기