대리자 20명
20.1 일반
대리자 선언은 클래스에서 파생된 클래스 System.Delegate
를 정의합니다. 대리자 인스턴스는 호출 목록을 캡슐화합니다. 이 목록은 각각 호출 가능한 엔터티라고 하는 하나 이상의 메서드 목록입니다. 인스턴스 메서드의 경우 호출 가능한 엔터티는 인스턴스와 해당 인스턴스의 메서드로 구성됩니다. 정적 메서드의 경우 호출 가능한 엔터티는 메서드로만 구성됩니다. 적절한 인수 집합을 사용하여 대리자 인스턴스를 호출하면 각 대리자의 호출 가능한 엔터티가 지정된 인수 집합으로 호출됩니다.
참고: 대리자 인스턴스의 흥미롭고 유용한 속성은 캡슐화하는 메서드의 클래스를 모르거나 신경 쓰지 않는다는 것입니다. 중요한 것은 해당 메서드가 대리자의 형식과 호환된다는 것입니다(§20.4). 이렇게 하면 대리자가 "익명" 호출에 완벽하게 적합합니다. 끝 메모
20.2 대리자 선언
delegate_declaration 새 대리자 형식을 선언하는 type_declaration(§14.7)입니다.
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier §23.2에 정의되어 있습니다.
동일한 한정자가 대리자 선언에 여러 번 표시되는 것은 컴파일 시간 오류입니다.
variant_type_parameter_list 제공하는 대리자 선언은 제네릭 대리자 선언입니다. 또한 제네릭 클래스 선언 또는 제네릭 구조체 선언 내에 중첩된 대리자는 제네릭 대리자 선언입니다. 이는 포함된 형식에 대한 형식 인수를 제공하여 생성된 형식(§8.4)을 만들어야 하기 때문에 제네릭 대리자 선언입니다.
new
한정자는 다른 형식 내에서 선언된 대리자에만 허용되며, 이 경우 §15.3.5에 설명된 대로 이러한 대리자가 상속된 멤버를 동일한 이름으로 숨기게 지정합니다.
public
, protected
, internal
및 private
한정자는 대리자 형식의 접근성을 제어합니다. 대리자 선언이 발생하는 컨텍스트에 따라 이러한 한정자 중 일부는 허용되지 않을 수 있습니다(§7.5.2).
대리자의 형식 이름은 식별자입니다.
메서드(§15.6.1)와 마찬가지로 대리자가 있는 경우 ref
참조 단위로 반환하고, 그렇지 않으면 return_type void
대리자는 값을 반환하고, 그렇지 않으면 대리자는 값을 반환합니다.
선택적 parameter_list 대리자의 매개 변수를 지정합니다.
값별 반환 또는 반환 없음 대리자 선언의 return_type 대리자가 반환하는 결과의 형식(있는 경우)을 지정합니다.
return-by-ref 대리자 선언의 ref_return_type 대리자가 반환한 variable_reference(§9.5)에서 참조하는 변수의 형식을 지정합니다.
선택적 variant_type_parameter_list (§18.2.3)는 대리자 자체에 대한 형식 매개 변수를 지정합니다.
대리자 형식의 반환 형식은 출력 안전(§18.2.3.2)이어야 void
합니다.
대리자 형식의 모든 매개 변수 형식은 입력이 안전해야 합니다(§18.2.3.2). 또한 출력 또는 참조 매개 변수 형식도 출력에 안전해야 합니다.
참고: 출력 매개 변수는 일반적인 구현 제한으로 인해 입력이 안전해야 합니다. 끝 메모
또한 대리자의 모든 형식 매개 변수에 대한 각 클래스 형식 제약 조건, 인터페이스 형식 제약 조건 및 형식 매개 변수 제약 조건은 입력로부터 안전해야 합니다.
C#의 대리자 형식은 구조적으로 동일하지 않고 이름이 동일합니다.
예제:
delegate int D1(int i, double d); delegate int D2(int c, double d);
대리자 형식
D1
은D2
서로 다른 두 형식이므로 동일한 서명에도 불구하고 서로 교환할 수 없습니다.끝 예제
다른 제네릭 형식 선언과 마찬가지로 생성된 대리자 형식을 만들려면 형식 인수를 지정해야 합니다. 생성된 대리자 형식의 매개 변수 형식 및 반환 형식은 대리자 선언의 각 형식 매개 변수에 대해 생성된 대리자 형식의 해당 형식 인수를 대체하여 생성됩니다.
대리자 형식을 선언하는 유일한 방법은 delegate_declaration 통해서입니다. 모든 대리자 형식은 .에서 System.Delegate
파생된 참조 형식입니다. 모든 대리자 유형에 필요한 멤버는 §20.3에 자세히 설명되어 있습니다. 대리자 형식은 암시적으로 sealed
사용되므로 대리자 형식에서 형식을 파생하는 것은 허용되지 않습니다. 에서 파생되는 대리자가 아닌 클래스 형식을 선언하는 System.Delegate
것도 허용되지 않습니다. System.Delegate
는 대리자 형식이 아닙니다. 모든 대리자 형식이 파생되는 클래스 형식입니다.
20.3 대리자 구성원
모든 대리자 형식은 §15.3.4에 설명된 대로 클래스의 Delegate
멤버를 상속합니다. 또한 모든 대리자 형식은 매개 변수 목록이 대리자 선언의 parameter_list 일치하는 제네릭 Invoke
이 아닌 메서드를 제공해야 하며, 해당 반환 형식은 대리자 선언의 return_type 또는 ref_return_type 일치하고, ref_kind 대리자 선언에서 일치하는 return-by-ref 대리자에 대해 제공해야 합니다. 메서드는 Invoke
최소한 포함하는 대리자 형식만큼 액세스할 수 있어야 합니다. 대리자 Invoke
형식에서 메서드를 호출하는 것은 대리자 호출 구문(§20.6)을 사용하는 것과 의미상 동일합니다.
구현은 대리자 형식에서 추가 멤버를 정의할 수 있습니다.
인스턴스화를 제외하고 클래스 또는 클래스 인스턴스에 적용할 수 있는 모든 작업을 대리자 클래스 또는 인스턴스에 각각 적용할 수도 있습니다. 특히 일반적인 멤버 액세스 구문을 통해 형식의 System.Delegate
멤버에 액세스할 수 있습니다.
20.4 대리자 호환성
다음 중 모두 true인 경우 메서드 또는 대리자 형식 M
이 대리자 형식 D
과 호환됩니다.
D
에는M
매개 변수 수가 같고 각 매개 변수의 매개 변수D
는 에 있는 해당 매개 변수와 동일한 참조 매개 변수 한정자를 갖습니다M
.- 각 값 매개 변수에 대해 매개 변수 형식에서 해당 매개 변수 형식
M
D
으로의 ID 변환(§10.2.2) 또는 암시적 참조 변환(§10.2.8)이 있습니다. - 각 참조 매개 변수에 대해 매개 변수 형식
D
은 .의 매개 변수 형식M
과 동일합니다. - 다음 중 하나는 true입니다.
이 호환성 정의는 반환 형식의 공변성과 매개 변수 형식의 반공변성을 허용합니다.
예제:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }
메서드
A.M1
는B.M1
동일한 반환 형식 및 매개 변수 목록을 가지므로 대리자 형식D1
과D2
둘 다와 호환됩니다. 메서드B.M2
B.M3
는 대리자 형식과B.M4
D2
호환되지 않으며 반환 형식D1
또는 매개 변수 목록이 다르기 때문에 호환되지 않습니다. 메서드B.M5
이며B.M6
둘 다 대리자 형식D3
과 호환됩니다.끝 예제
예제:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
메서드
X.F
는 대리자 형식Predicate<int>
과 호환되며 메서드X.G
는 대리자 형식Predicate<string>
과 호환됩니다.끝 예제
참고: 대리자 호환성의 직관적인 의미는 대리자의 모든 호출을 형식 안전성을 위반하지 않고 메서드 호출로 바꿀 수 있는 경우 메서드가 대리자 형식과 호환된다는 것입니다. 선택적 매개 변수 및 매개 변수 배열을 명시적 매개 변수로 처리합니다. 예를 들어 다음 코드에서 다음을 수행합니다.
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }
Action<string>
Action<string>
유효한 호출위의 메서드의 시그니처가
Print(object value, bool prependTimestamp = false)
된 경우 메서드는Action<string>
호환되지 않습니다.끝 메모
20.5 대리자 인스턴스화
대리자의 인스턴스는 대리자 형식, 대리자 조합 또는 대리자 제거로 변환되는 delegate_creation_expression(§12.8.16.6)에 의해 만들어집니다. 새로 만든 대리자 인스턴스는 다음 중 하나 이상을 참조합니다.
- delegate_creation_expression 참조되는 정적 메서드 또는
- 대상 개체(사용할 수 없음
null
) 및 delegate_creation_expression 참조되는 인스턴스 메서드 또는 - 다른 대리자(§12.8.16.6).
예제:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }
끝 예제
대리자 인스턴스에 의해 캡슐화된 메서드 집합을 호출 목록이라고 합니다. 대리자 인스턴스가 단일 메서드에서 만들어지면 해당 메서드를 캡슐화하고 해당 호출 목록에는 하나의 항목만 포함됩니다. 그러나 대리자가 아닌null
두 인스턴스가 결합되면 두 개 이상의 항목이 포함된 새 호출 목록을 형성하기 위해 호출 목록이 연결됩니다(왼쪽 피연산자 순서, 오른쪽 피연산자).
단일 대리자에서 새 대리자를 만들면 결과 호출 목록에 원본 대리자(§12.8.16.6)인 항목이 하나만 있습니다.
대리자는 이진 +
(§12.10.5) 및 +=
연산자(§12.21.4)를 사용하여 결합됩니다. 대리자는 이진 -
(§12.10.6) 및 -=
연산자(§12.21.4)를 사용하여 대리자의 조합에서 제거할 수 있습니다. 대리자를 같음(§12.12.9)으로 비교할 수 있습니다.
예제: 다음 예제에서는 여러 대리자의 인스턴스화와 해당 호출 목록을 보여 줍니다.
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }
cd2
인스턴스화될 때cd1
각각 하나의 메서드를 캡슐화합니다.cd3
인스턴스화되면 두 메서드M1
의 호출 목록과M2
해당 순서대로 나열됩니다.cd4
'의 호출 목록에는 해당 순서대로M1
,M2
및M1
가 포함됩니다. 의 경우cd5
호출 목록에는 해당 순서대로M1
,M2
M1
,M1
및M2
등이 포함됩니다.delegate_creation_expression 사용하여 다른 대리자에서 대리자를 만들 때 결과에는 원래와 다른 구조의 호출 목록이 있지만 동일한 메서드가 동일한 순서로 호출됩니다. 호출 목록에서 만든 경우
td3
멤버가 하나뿐이지만 해당 멤버는 메서드M1
목록이며M2
해당 메서드는 호출되는 순서와 동일한 순서로 호출td3
cd3
cd3
됩니다. 마찬가지로 인스턴스화될 때td4
호출 목록에는 두 개의 항목만 있지만 세 가지 메서드M1
를M2
호출하고M1
그 순서대로cd4
호출합니다.호출 목록의 구조는 대리자 빼기에 영향을 줍니다. 대리자
cd6
- (호출, 및 ) 호출 및M1
M1
에서cd4
빼기cd2
(호출M2
M2
M1
)로 생성됩니다.M1
그러나 대리td6
자 - 목록의 단일 항목이 아니라 중첩된 목록의 멤버와 마찬가지로 (호출, 및 )에서td4
빼cd2
기(호출M2
M1
) 및M1
) 순서대로M2
계속 호출M1
M2
합니다M1
.M2
대리자를 결합하고 제거하는 더 많은 예제는 §20.6을 참조하세요.끝 예제
인스턴스화되면 대리자 인스턴스는 항상 동일한 호출 목록을 참조합니다.
참고: 두 대리자가 결합되거나 한 대리자가 다른 대리자에서 제거되면 새 대리자가 자체 호출 목록으로 생성됩니다. 결합되거나 제거된 대리자의 호출 목록은 변경되지 않은 상태로 유지됩니다. 끝 메모
20.6 대리자 호출
C#은 대리자를 호출하기 위한 특수 구문을 제공합니다. 호출 목록에 하나의 항목이 포함된 대리null
자가 아닌 인스턴스가 호출되면 지정된 인수와 동일한 인수를 사용하여 하나의 메서드를 호출하고 참조된 메서드와 동일한 값을 반환합니다. 대리자 호출에 대한 자세한 내용은 §12.8.9.4를 참조하세요.) 이러한 대리자를 호출하는 동안 예외가 발생하고 호출된 메서드 내에서 예외가 catch되지 않으면 해당 메서드가 해당 대리자가 참조한 메서드를 직접 호출한 것처럼 대리자를 호출하는 메서드에서 예외 catch 절에 대한 검색이 계속됩니다.
호출 목록에 여러 항목이 포함된 대리자 인스턴스의 호출은 호출 목록의 각 메서드를 동기적으로 순서대로 호출하여 진행합니다. 호출된 각 메서드는 대리자 인스턴스에 지정된 것과 동일한 인수 집합을 전달합니다. 이러한 대리자 호출에 참조 매개 변수(§15.6.2.3.3)가 포함된 경우 각 메서드 호출은 동일한 변수에 대한 참조와 함께 발생합니다. 호출 목록의 한 메서드에 의해 해당 변수에 대한 변경 내용은 호출 목록 아래의 메서드에 표시됩니다. 대리자 호출에 출력 매개 변수 또는 반환 값이 포함된 경우 최종 값은 목록의 마지막 대리자 호출에서 가져옵니다. 이러한 대리자의 호출을 처리하는 동안 예외가 발생하고 해당 예외가 호출된 메서드 내에서 catch되지 않으면 대리자를 호출한 메서드에서 예외 catch 절에 대한 검색이 계속되며 호출 목록의 추가 메서드는 호출되지 않습니다.
값 null
이 형식 System.NullReferenceException
예외인 대리자 인스턴스를 호출하려고 합니다.
예제: 다음 예제에서는 대리자를 인스턴스화, 결합, 제거 및 호출하는 방법을 보여 줍니다.
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }
문
cd3 += cd1;
에 표시된 것처럼 대리자는 호출 목록에 여러 번 있을 수 있습니다. 이 경우 발생당 한 번만 호출됩니다. 이와 같은 호출 목록에서 해당 대리자가 제거되면 호출 목록의 마지막 항목은 실제로 제거된 대리자입니다.최종 문을
cd3 -= cd1
실행하기 직전에 대리cd3
자는 빈 호출 목록을 참조합니다. 빈 목록에서 대리자를 제거하거나 비어 있지 않은 목록에서 존재하지 않는 대리자를 제거하려는 시도는 오류가 아닙니다.생성된 출력은 다음과 같습니다.
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60
끝 예제
ECMA C# draft specification