메서드 매개 변수
기본적으로 C#의 인수는 값으로 함수에 전달됩니다. 이는 변수의 복사본이 메서드에 전달된다는 의미입니다. 값(struct
) 형식의 경우 값의 복사본이 메서드에 전달됩니다. 참조(class
) 형식의 경우 참조의 복사본이 메서드에 전달됩니다. 매개 변수 한정자를 사용하면 참조로 인수를 전달할 수 있습니다. 다음 개념은 이러한 차이점과 매개 변수 한정자를 사용하는 방법을 이해하는 데 도움이 됩니다.
- 값으로 전달은 메서드에 변수의 복사본을 전달한다는 의미입니다.
- 참조로 전달은 메서드에 변수에 대한 액세스를 전달한다는 의미입니다.
- 참조 형식 변수에는 해당 데이터에 대한 참조가 포함됩니다.
- 값 형식 변수에는 해당 데이터가 직접 포함됩니다.
구조체는 값 형식(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);
}
}
/* Output:
Class field = Changed
Struct field = Not Changed
*/
매개 변수 형식 및 인수 모드의 조합
인수가 전달되는 방법 및 인수가 참조 형식 또는 값 형식인지 여부는 호출자에게 표시되는 인수 수정 내용을 제어합니다.
- 값 형식을 값으로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 참조 형식을 값으로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시되지 않습니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
- 값 형식을 참조로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조
ref =
하는 경우 해당 변경 내용은 호출자에서 표시되지 않습니다 . - 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
- 메서드가 매개 변수를 할당하여 다른 개체를 참조
- 참조 형식을 참조로 전달하는 경우:
- 메서드가 매개 변수를 할당하여 다른 개체를 참조하는 경우 해당 변경 내용은 호출자에게 표시됩니다.
- 메서드가 매개 변수에서 참조하는 개체의 상태를 수정하면 호출자에게 해당 변경 내용이 표시됩니다.
참조 형식을 참조로 전달하는 경우 호출된 메서드는 참조 매개 변수가 호출자에서 참조하는 개체를 바꿀 수 있습니다. 개체의 스토리지 위치는 참조 매개 변수의 값으로 메서드에 전달됩니다. 매개 변수의 스토리지 위치에서 값을 변경하여 새 개체를 가리키도록 하면 호출자가 참조하는 스토리지 위치도 변경됩니다. 다음 예제에서는 참조 형식 인스턴스를 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
참조 및 값의 안전한 컨텍스트
메서드는 매개 변수 값을 필드에 저장할 수 있습니다. 매개 변수가 값으로 전달되는 경우 일반적으로 안전합니다. 값이 복사되며 필드에 저장된 경우 참조 형식에 연결할 수 있습니다. 매개 변수를 안전하게 참조로 전달하려면 컴파일러가 새 변수에 참조를 할당하는 것이 안전한 시기를 정의해야 합니다. 모든 식에 대해 컴파일러는 식이나 변수에 대한 액세스를 제한하는 안전한 컨텍스트를 정의합니다. 컴파일러는 safe-context와 ref-safe-context라는 두 가지 범위를 사용합니다.
- safe-context는 모든 식에 안전하게 액세스할 수 있는 범위를 정의합니다.
- ref-safe-context는 식에 대한 참조가 안전하게 액세스되거나 수정될 수 있는 범위를 정의합니다.
비공식적으로 이러한 범위를 코드가 더 이상 유효하지 않은 참조를 액세스하거나 수정하지 않도록 하는 메커니즘으로 생각할 수 있습니다. 참조는 유효한 개체 또는 구조체를 참조하는 한 유효합니다. safe-context는 변수가 할당되거나 다시 할당될 수 있는 시기를 정의합니다. ref-safe-context는 변수가 참조 할당됨 또는 참조 재할당됨될 수 있는 시기를 정의합니다. 할당은 변수를 새 값에 할당합니다. 참조 할당은 다른 스토리지 위치를 참조할 변수를 할당합니다.
참조 매개 변수
값 대신 참조로 인수를 전달하려면 매개 변수 선언에 다음 한정자 중 하나를 적용합니다.
ref
: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 있지만 반드시 그렇게 할 필요는 없습니다.out
: 호출 메서드는 메서드를 호출하기 전에 인수를 초기화할 필요가 없습니다. 메서드는 매개 변수에 값을 할당해야 합니다.ref readonly
: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 없습니다.in
: 메서드를 호출하기 전에 인수를 초기화해야 합니다. 메서드는 매개 변수에 새 값을 할당할 수 없습니다. 컴파일러는in
매개 변수에 대한 인수 복사본을 보관하기 위해 임시 변수를 만들 수 있습니다.
클래스의 멤버는 ref
, ref readonly
, in
또는 out
만 다른 서명을 포함할 수 없습니다. 특정 형식의 두 멤버가 하나는 ref
매개 변수를 포함하고 다른 하나는 out
, ref readonly
또는 in
매개 변수를 포함한다는 것 외에는 차이가 없으면 컴파일러 오류가 발생합니다. 그러나 다음 예제에 나와 있는 것처럼 메서드 하나에는 ref
, ref readonly
, in
또는 out
매개 변수가 포함되어 있고 다른 하나에는 값으로 전달되는 매개 변수가 포함되어 있으면 메서드를 오버로드할 수 있습니다. 숨기기나 재정의와 같이 서명이 일치해야 하는 다른 상황에서는 in
, ref
, ref readonly
및 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
를 생략할 수 있다는 사실은 제외입니다.)
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
인수로 전달되는 변수는 메서드 호출에서 전달되기 전에 초기화할 필요가 없지만 호출된 메서드는 메서드가 반환되기 전에 값을 할당해야 합니다.
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
한정자를 추가할 수 없으므로 경고를 표시하지 않아야 합니다.
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`
이제 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`
인수가 참조로 전달되는 유일한 메서드 호출이 마지막입니다.
참고 항목
앞의 코드는 단순화를 위해 인수 형식으로 int
를 사용합니다. int
는 대부분의 최신 컴퓨터에서 참조보다 크지 않기 때문에 단일 int
를 읽기 전용 참조로 전달하면 아무런 이점이 없습니다.
params
한정자
메서드 선언에서 params
키워드 뒤에는 추가 매개 변수가 허용되지 않으며, params
키워드 하나만 메서드 선언에 사용할 수 있습니다.
params
매개 변수의 선언된 형식은 컬렉션 형식이어야 합니다. 인식된 컬렉션 형식은 다음과 같습니다.
- 단일 차원 배열 형식
T[]
이면 요소 형식은T
입니다. - 범위 형식:
System.Span<T>
System.ReadOnlySpan<T>
여기서는 요소 형식이T
입니다.
- 해당 요소 형식이 있는 액세스 가능한 메서드를 만드는 형식입니다. 메서드 만들기는 컬렉션 식에 사용되는 것과 동일한 특성을 사용하여 식별됩니다.
- System.Collections.Generic.IEnumerable<T>을(를) 구현하는 구조체 또는 클래스 형식입니다. 여기에서:
- 형식에는 인수 없이 호출할 수 있는 생성자가 있으며, 생성자는 적어도 선언 멤버만큼 액세스할 수 있습니다.
- 형식에는 인스턴스(확장명 아님) 메서드
Add
이(가) 있습니다. 여기에서:- 단일 값 인수를 사용하여 메서드를 호출할 수 있습니다.
- 메서드가 제네릭이면 인수에서 형식 인수를 유추할 수 있습니다.
- 메서드는 적어도 선언 멤버만큼 액세스할 수 있습니다. 여기서 요소 형식은 형식의 반복 형식입니다.
- 인터페이스 형식:
C# 13 이전에는 매개 변수가 단일 차원 배열이어야 합니다.
params
매개 변수를 사용하여 메서드를 호출하면 다음을 전달할 수 있습니다.
- 배열 요소 형식의 쉼표로 구분된 인수 목록입니다.
- 지정된 형식의 인수 컬렉션입니다.
- 인수가 없습니다. 인수를 보내지 않는 경우
params
목록의 길이는 0입니다.
다음 예제에서는 params
매개 변수에 인수를 보낼 수 있는 다양한 방법을 보여 줍니다.
public static void ParamsModifierExample(params int[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void ParamsModifierObjectExample(params object[] list)
{
for (int i = 0; i < list.Length; i++)
{
System.Console.Write(list[i] + " ");
}
System.Console.WriteLine();
}
public static void TryParamsCalls()
{
// You can send a comma-separated list of arguments of the
// specified type.
ParamsModifierExample(1, 2, 3, 4);
ParamsModifierObjectExample(1, 'a', "test");
// A params parameter accepts zero or more arguments.
// The following calling statement displays only a blank line.
ParamsModifierObjectExample();
// 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 };
ParamsModifierExample(myIntArray);
object[] myObjArray = { 2, 'b', "test", "again" };
ParamsModifierObjectExample(myObjArray);
// The following call causes a compiler error because the object
// array cannot be converted into an integer array.
//ParamsModifierExample(myObjArray);
// The following call does not cause an error, but the entire
// integer array becomes the first element of the params array.
ParamsModifierObjectExample(myIntArray);
}
/*
Output:
1 2 3 4
1 a test
5 6 7 8 9
2 b test again
System.Int32[]
*/
오버로드 확인은 매개 변수에 대한 params
인수가 컬렉션 형식인 경우 모호성을 유발할 수 있습니다. 인수의 컬렉션 형식은 매개 변수의 컬렉션 형식으로 변환할 수 있어야 합니다. 다른 오버로드가 해당 매개 변수에 대해 더 나은 변환을 제공하는 경우 해당 메서드가 더 좋을 수 있습니다. 그러나 매개 변수에 대한 params
인수가 불연속 요소이거나 누락된 경우 매개 변수 형식이 다른 params
모든 오버로드는 해당 매개 변수에 대해 동일합니다.
자세한 내용은 C# 언어 사양의 인수 목록 섹션을 참조하세요. 언어 사양은 C# 구문 및 사용법에 대 한 신뢰할 수 있는 소스 됩니다.
.NET