15개 클래스
15.1 일반
클래스는 데이터 멤버(상수 및 필드), 함수 멤버(메서드, 속성, 이벤트, 인덱서, 연산자, 인스턴스 생성자, 종료자 및 정적 생성자) 및 중첩된 형식을 포함할 수 있는 데이터 구조입니다. 클래스 형식은 파생 클래스가 기본 클래스를 확장하고 특수화할 수 있는 메커니즘인 상속을 지원합니다.
15.2 클래스 선언
15.2.1 일반
class_declaration 새 클래스를 선언하는 type_declaration(§14.7)입니다.
class_declaration
: attributes? class_modifier* 'partial'? 'class' identifier
type_parameter_list? class_base? type_parameter_constraints_clause*
class_body ';'?
;
class_declaration 선택적 특성 집합(§22)과 선택적 class_modifier집합(§15.2.2), 선택적 partial
한정자(§15.2.7), 키워드 class
및 클래스 이름을 지정하는 식별자로 구성됩니다. 선택적 type_parameter_list(§15.2.3) 뒤에 선택적 class_base 사양(§15.2.4)이 잇습니다.) 뒤에 선택적 type_parameter_constraints_clause 집합(§15.2.5), class_body(§15.2.6) 뒤에 세미콜론이 옵니다.
클래스 선언은 type_parameter_list 제공하지 않는 한 type_parameter_constraints_clause 제공하지 않습니다.
type_parameter_list 제공하는 클래스 선언은 제네릭 클래스 선언입니다. 또한 제네릭 클래스 선언 또는 제네릭 구조체 선언 내에 중첩된 모든 클래스는 제네릭 클래스 선언 자체이며, 포함된 형식에 대한 형식 인수는 생성된 형식(§8.4)을 만들기 위해 제공되어야 하기 때문에 제네릭 클래스 선언입니다.
15.2.2 클래스 한정자
15.2.2.1 일반
class_declaration 필요에 따라 클래스 한정자 시퀀스를 포함할 수 있습니다.
class_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'abstract'
| 'sealed'
| 'static'
| unsafe_modifier // unsafe code support
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
동일한 한정자가 클래스 선언에 여러 번 표시되는 것은 컴파일 시간 오류입니다.
한 new
정자는 중첩 클래스에서 허용됩니다. 이 클래스는 §15.3.5에 설명된 대로 동일한 이름으로 상속된 멤버를 숨기게 지정합니다. 한정자가 중첩 클래스 선언이 아닌 클래스 선언에 표시되는 것은 컴파일 시간 오류 new
입니다.
public
, protected
, internal
및 private
한정자는 클래스의 접근성을 제어합니다. 클래스 선언이 발생하는 컨텍스트에 따라 이러한 한정자 중 일부는 허용되지 않을 수 있습니다(§7.5.2).
부분 형식 선언(§15.2.7)에 접근성 사양(, public
, protected
및 internal
한정자를 통해private
)이 포함된 경우 해당 사양은 접근성 사양을 포함하는 다른 모든 부분에 동의해야 합니다. 부분 형식의 일부가 접근성 사양을 포함하지 않는 경우 형식에 적절한 기본 접근성(§7.5.2)이 제공됩니다.
abstract
, sealed
및 static
한정자는 다음 하위클래스에 설명되어 있습니다.
15.2.2.2 추상 클래스
abstract
한정자는 클래스가 불완전하고 기본 클래스로만 사용되도록 함을 나타내는 데 사용됩니다. 추상 클래스는 다음과 같은 방법으로 비 추상 클래스와 다릅니다.
- 추상 클래스는 직접 인스턴스화할 수 없으며 추상 클래스에서 연산자를 사용하는
new
것은 컴파일 시간 오류입니다. 컴파일 시간 형식이 추상인 변수와 값을 가질 수 있지만 이러한 변수 및 값은 추상 형식에서 파생된 비 추상 클래스의 인스턴스에 대한 참조이거나 반드시 포함됩니다null
. - 추상 클래스는 추상 멤버를 포함할 수 있지만 필수는 아닙니다.
- 추상 클래스는 봉인할 수 없습니다.
비 추상 클래스가 추상 클래스에서 파생되는 경우 비 추상 클래스는 상속된 모든 추상 멤버의 실제 구현을 포함하므로 이러한 추상 멤버를 재정의해야 합니다.
예제: 다음 코드에서
abstract class A { public abstract void F(); } abstract class B : A { public void G() {} } class C : B { public override void F() { // Actual implementation of F } }
추상 클래스
A
는 추상 메서드F
를 소개합니다. 클래스B
는 추가 메서드G
를 도입하지만 구현F
B
을 제공하지 않으므로 추상으로 선언되어야 합니다. 클래스C
는 재정의F
하고 실제 구현을 제공합니다. 추상 멤버C
C
가 없으므로 추상이 아닌 것으로 허용됩니다(필수는 아님).끝 예제
클래스의 부분 형식 선언(§15.2.7)에 있는 하나 이상의 부분이 한정자를 포함하는 abstract
경우 클래스는 추상입니다. 그렇지 않으면 클래스가 추상이 아닌 경우
15.2.2.3 봉인된 클래스
한 sealed
정자는 클래스에서 파생되지 않도록 방지하는 데 사용됩니다. 봉인된 클래스를 다른 클래스의 기본 클래스로 지정하면 컴파일 시간 오류가 발생합니다.
봉인된 클래스는 추상 클래스일 수도 없습니다.
참고: 한
sealed
정자는 주로 의도하지 않은 파생을 방지하는 데 사용되지만 특정 런타임 최적화를 사용하도록 설정합니다. 특히 봉인된 클래스에는 파생 클래스가 없는 것으로 알려져 있으므로 봉인된 클래스 인스턴스의 가상 함수 멤버 호출을 가상이 아닌 호출로 변환할 수 있습니다. 끝 메모
클래스의 부분 형식 선언(§15.2.7)에 한정자가 포함된 하나 이상의 부분이 있으면 sealed
클래스가 봉인됩니다. 그렇지 않으면 클래스가 봉인되지 않습니다.
15.2.2.4 정적 클래스
15.2.2.4.1 일반
한 static
정자는 정적 클래스로 선언되는 클래스를 표시하는 데 사용됩니다. 정적 클래스는 인스턴스화되지 않아야 하며 형식으로 사용해서는 안 되며 정적 멤버만 포함해야 합니다. 정적 클래스만 확장 메서드 선언(§15.6.10)을 포함할 수 있습니다.
정적 클래스 선언에는 다음과 같은 제한 사항이 적용됩니다.
- 정적 클래스에는 한
sealed
abstract
정자가 포함되지 않습니다. 그러나 정적 클래스는 인스턴스화하거나 파생할 수 없으므로 봉인된 클래스와 추상 클래스 모두인 것처럼 동작합니다. - 정적 클래스는 class_base 사양(§15.2.4)을 포함하지 않으며 기본 클래스 또는 구현된 인터페이스 목록을 명시적으로 지정할 수 없습니다. 정적 클래스는 암시적으로 형식
object
에서 상속됩니다. - 정적 클래스는 정적 멤버(§15.3.8)만 포함해야 합니다.
참고: 모든 상수 및 중첩된 형식은 정적 멤버로 분류됩니다. 끝 메모
- 정적 클래스에는 접근성이 선언된 멤버 또는
protected
선언된 멤버private protected
protected internal
가 있어야 합니다.
이러한 제한 사항을 위반하는 것은 컴파일 시간 오류입니다.
정적 클래스에는 인스턴스 생성자가 없습니다. 정적 클래스에서 인스턴스 생성자를 선언할 수 없으며 정적 클래스에 대해 기본 인스턴스 생성자(§15.11.5)가 제공되지 않습니다.
정적 클래스의 멤버는 자동으로 정적이지 않으며 멤버 선언에는 상수 및 중첩 형식을 제외하고 한정자가 명시적으로 포함되어 static
야 합니다. 클래스가 정적 외부 클래스 내에 중첩된 경우 한정자를 명시적으로 포함하지 않는 한 중첩 클래스는 정적 클래스가 static
아닙니다.
클래스의 부분 형식 선언(§15.2.7)에 있는 하나 이상의 부분이 한정자를 포함하는 static
경우 클래스는 정적입니다. 그렇지 않으면 클래스가 정적이지 않습니다.
15.2.2.4.2 정적 클래스 형식 참조
정적 클래스를 참조할 수 있는 경우 namespace_or_type_name(§7.8)가 허용됩니다.
-
namespace_or_type_name 폼
T
의T.I
또는 -
namespace_or_type 이름은 양식
T
의 typeof_expression(typeof(T)
)입니다.
정적 클래스를 참조할 수 있는 경우 primary_expression(§12.8)가 허용됩니다.
-
primary_expression 양식
E
의 member_access(E.I
)에 있습니다.
다른 컨텍스트에서 정적 클래스를 참조하는 것은 컴파일 시간 오류입니다.
참고: 예를 들어 정적 클래스를 기본 클래스, 멤버의 구성 요소 형식(§15.3.7), 제네릭 형식 인수 또는 형식 매개 변수 제약 조건으로 사용하는 것은 오류입니다. 마찬가지로 배열 형식, 새 식, 캐스트 식, 식 또는 기본값 식에서 정적 클래스를
sizeof
사용할 수 없습니다. 끝 메모
15.2.3 형식 매개 변수
형식 매개 변수는 생성된 형식을 만들기 위해 제공된 형식 인수의 자리 표시자를 나타내는 간단한 식별자입니다. constrast로 형식 인수(§8.4.2)는 생성된 형식을 만들 때 형식 매개 변수로 대체되는 형식입니다.
type_parameter_list
: '<' type_parameters '>'
;
type_parameters
: attributes? type_parameter
| type_parameters ',' attributes? type_parameter
;
type_parameter §8.5에 정의되어 있습니다.
클래스 선언의 각 형식 매개 변수는 해당 클래스의 선언 공간(§7.3)에 이름을 정의합니다. 따라서 해당 클래스의 다른 형식 매개 변수 또는 해당 클래스에 선언된 멤버와 같은 이름을 가질 수 없습니다. 형식 매개 변수는 형식 자체와 같은 이름을 가질 수 없습니다.
두 개의 부분 제네릭 형식 선언(동일한 프로그램에서)이 동일한 정규화된 이름(형식 매개 변수 수에 대한 generic_dimension_specifier(§12.8.18)을 포함하는 경우(§7.8.3) 동일한 바인딩되지 않은 제네릭 형식에 기여합니다. 이러한 두 부분 형식 선언은 각 형식 매개 변수에 대해 동일한 이름을 순서대로 지정해야 합니다.
15.2.4 클래스 기본 사양
15.2.4.1 일반
클래스 선언에는 클래스의 직접 기본 클래스와 클래스에서 직접 구현하는 인터페이스(§18)를 정의하는 class_base 사양이 포함될 수 있습니다.
class_base
: ':' class_type
| ':' interface_type_list
| ':' class_type ',' interface_type_list
;
interface_type_list
: interface_type (',' interface_type)*
;
15.2.4.2 기본 클래스
class_type class_base 포함된 경우 선언되는 클래스의 직접 기본 클래스를 지정합니다. 부분적이지 않은 클래스 선언에 class_base 없거나 object
partial 클래스 선언에 기본 클래스 사양이 포함된 경우 해당 기본 클래스 사양은 기본 클래스 사양을 포함하는 해당 부분 형식의 다른 모든 부분과 동일한 형식을 참조해야 합니다. 부분 클래스의 일부가 기본 클래스 사양을 포함하지 않는 경우 기본 클래스는 다음과 같습니다 object
. 클래스는 §15.3.4에 설명된 대로 직접 기본 클래스에서 멤버를 상속합니다.
예제: 다음 코드에서
class A {} class B : A {}
클래스
A
는 의 직접 기본 클래스B
라고하며B
에서A
파생되었다고합니다.A
직접 기본 클래스를 명시적으로 지정하지 않으므로 직접 기본 클래스는 암시적으로 지정됩니다object
.끝 예제
제네릭 형식 선언(§15.3.9.7) 내에 선언된 중첩 형식을 포함하여 생성된 클래스 형식의 경우 제네릭 클래스 선언에서 기본 클래스를 지정하면 생성된 형식의 기본 클래스는 기본 클래스 선언의 각 type_parameter 대해 생성된 형식의 해당 type_argument 대체하여 가져옵니다.
예: 제네릭 클래스 선언이 지정된 경우
class B<U,V> {...} class G<T> : B<string,T[]> {...}
생성된 형식
G<int>
의 기본 클래스는 다음과 입니다B<string,int[]>
.끝 예제
클래스 선언에 지정된 기본 클래스는 생성된 클래스 형식(§8.4)일 수 있습니다. 기본 클래스는 범위 내의 형식 매개 변수를 포함할 수 있지만 자체적으로 형식 매개 변수(§8.5)가 될 수 없습니다.
예제:
class Base<T> {} // Valid, non-constructed class with constructed base class class Extend1 : Base<int> {} // Error, type parameter used as base class class Extend2<V> : V {} // Valid, type parameter used as type argument for base class class Extend3<V> : Base<V> {}
끝 예제
클래스 형식의 직접 기본 클래스는 적어도 클래스 형식 자체(§7.5.5)만큼 액세스할 수 있어야 합니다. 예를 들어 public 클래스가 프라이빗 또는 내부 클래스에서 파생되는 컴파일 시간 오류입니다.
클래스 형식의 직접 기본 클래스는 다음 형식, , System.Array
System.Delegate
System.Enum
또는 System.ValueType
형식dynamic
이 아니어야 합니다. 또한 제네릭 클래스 선언은 직접 또는 간접 기본 클래스(System.Attribute
)로 사용하지 않습니다.
클래스A
의 직접 기본 클래스 사양 B
의 의미를 결정할 때의 직접 기본 클래스는 일시적으로 가정B
되므로 기본 클래스 object
사양의 의미는 재귀적으로 자체에 의존할 수 없습니다.
예: 다음
class X<T> { public class Y{} } class Z : X<Z.Y> {}
는 기본 클래스 사양
X<Z.Y>
에서 직접 기본 클래스Z
로 간주되므로object
(§7.8의 규칙에 따라)Z
멤버Y
가 있는 것으로 간주되지 않으므로 오류가 발생합니다.끝 예제
클래스의 기본 클래스는 직접 기본 클래스 및 기본 클래스입니다. 즉, 기본 클래스 집합은 직접 기본 클래스 관계의 전이적 닫기입니다.
예: 다음에서:
class A {...} class B<T> : A {...} class C<T> : B<IComparable<T>> {...} class D<T> : C<T[]> {...}
의 기본 클래스는
D<int>
C<int[]>
,B<IComparable<int[]>>
A
및object
.끝 예제
클래스 object
를 제외하고 모든 클래스에는 정확히 하나의 직접 기본 클래스가 있습니다. 클래스에는 object
직접 기본 클래스가 없으며 다른 모든 클래스의 궁극적인 기본 클래스입니다.
클래스 자체에 따라 달라지는 컴파일 시간 오류입니다. 이 규칙의 목적을 위해 클래스는 직접 기본 클래스(있는 경우)에 따라 달라지며 중첩된 가장 가까운 바깥쪽 클래스(있는 경우)에 직접 의존합니다. 이 정의를 고려할 때 클래스가 의존하는 클래스의 전체 집합은 관계에 따라 직접의 전이적 닫 기 입니다.
예: 예제
class A : A {}
는 클래스가 자체에 따라 달라지므로 잘못되었습니다. 마찬가지로 예제도 마찬가지입니다.
class A : B {} class B : C {} class C : A {}
클래스가 순환적으로 자체에 의존하기 때문에 오류가 발생합니다. 마지막으로, 예제
class A : B.C {} class B : A { public class C {} }
A는 순환적으로 의존하는 (즉시 바깥쪽 클래스)에
B.C
B
의존하는 (직접 기본 클래스)에A
의존하기 때문에 컴파일 시간 오류가 발생합니다.끝 예제
클래스는 클래스 내에 중첩된 클래스에 의존하지 않습니다.
예제: 다음 코드에서
class A { class B : A {} }
B
는 (직접 기본 클래스와 바로 바깥쪽 클래스이기 때문에A
)에A
A
의존하지만B
(B
기본 클래스나 바깥쪽 클래스A
가 아니므로) 의존하지 않습니다. 따라서 이 예제는 유효합니다.끝 예제
봉인된 클래스에서 파생할 수 없습니다.
예제: 다음 코드에서
sealed class A {} class B : A {} // Error, cannot derive from a sealed class
클래스는 봉인된 클래스
B
A
에서 파생하려고 시도하므로 오류가 발생합니다.끝 예제
15.2.4.3 인터페이스 구현
class_base 사양에는 인터페이스 형식 목록이 포함될 수 있으며, 이 경우 클래스는 지정된 인터페이스 형식을 구현했다고 합니다. 제네릭 형식 선언(§15.3.9.7) 내에 선언된 중첩 형식을 포함하여 생성된 클래스 형식의 경우 지정된 인터페이스의 각 type_parameter 대해 생성된 형식의 해당 type_argument 대체하여 구현된 각 인터페이스 형식을 가져옵니다.
여러 부분으로 선언된 형식의 인터페이스 집합(§15.2.7)은 각 파트에 지정된 인터페이스의 합합입니다. 특정 인터페이스는 각 파트에서 한 번만 이름을 지정할 수 있지만 여러 파트는 동일한 기본 인터페이스의 이름을 지정할 수 있습니다. 지정된 인터페이스의 각 멤버에 대해 하나의 구현만 있어야 합니다.
예: 다음에서:
partial class C : IA, IB {...} partial class C : IC {...} partial class C : IA, IB {...}
클래스
C
에 대한 기본 인터페이스 집합은IA
,IB
및IC
.끝 예제
일반적으로 각 파트는 해당 부분에 선언된 인터페이스의 구현을 제공합니다. 그러나 이는 요구 사항이 아닙니다. 파트는 다른 부분에 선언된 인터페이스에 대한 구현을 제공할 수 있습니다.
예제:
partial class X { int IComparable.CompareTo(object o) {...} } partial class X : IComparable { ... }
끝 예제
클래스 선언에 지정된 기본 인터페이스는 인터페이스 형식(§8.4, §18.2)을 생성할 수 있습니다. 기본 인터페이스는 범위에 있는 형식 매개 변수를 포함할 수 있지만 자체적으로 형식 매개 변수가 될 수 없습니다.
예제: 다음 코드는 클래스가 생성된 형식을 구현하고 확장하는 방법을 보여 줍니다.
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
끝 예제
인터페이스 구현은 §18.6에서 자세히 설명합니다.
15.2.5 형식 매개 변수 제약 조건
제네릭 형식 및 메서드 선언은 필요에 따라 type_parameter_constraints_clause포함하여 형식 매개 변수 제약 조건을 지정할 수 있습니다.
type_parameter_constraints_clauses
: type_parameter_constraints_clause
| type_parameter_constraints_clauses type_parameter_constraints_clause
;
type_parameter_constraints_clause
: 'where' type_parameter ':' type_parameter_constraints
;
type_parameter_constraints
: primary_constraint (',' secondary_constraints)? (',' constructor_constraint)?
| secondary_constraints (',' constructor_constraint)?
| constructor_constraint
;
primary_constraint
: class_type nullable_type_annotation?
| 'class' nullable_type_annotation?
| 'struct'
| 'notnull'
| 'unmanaged'
;
secondary_constraint
: interface_type nullable_type_annotation?
| type_parameter nullable_type_annotation?
;
secondary_constraints
: secondary_constraint (',' secondary_constraint)*
;
constructor_constraint
: 'new' '(' ')'
;
각 type_parameter_constraints_clause 토큰 where
과 형식 매개 변수의 이름, 콜론 및 해당 형식 매개 변수에 대한 제약 조건 목록으로 구성됩니다. 각 형식 매개 변수에 대해 최대 하나의 where
절이 있을 수 있으며 where
절은 순서에 따라 나열될 수 있습니다.
get
속성 접근 set
자의 토큰과 where
마찬가지로 토큰은 키워드가 아닙니다.
절에 where
지정된 제약 조건 목록에는 단일 기본 제약 조건, 하나 이상의 보조 제약 조건 및 생성자 제약 new()
조건과 같은 구성 요소가 순서대로 포함될 수 있습니다.
기본 제약 조건은 클래스 형식, 참조 형식 제약 조건, struct
, notnull
또는 관리되지 않는 형식 제약 조건unmanaged
일 수 있습니다. 클래스 형식 및 참조 형식 제약 조건에는 nullable_type_annotation 포함될 수 있습니다.
보조 제약 조건은 interface_type 또는 type_parameter, 필요에 따라 nullable_type_annotation 될 수 있습니다. nullable_type_annotatione*가 있으면 형식 인수가 제약 조건을 충족하는 nullable이 아닌 참조 형식에 해당하는 null 허용 참조 형식이 될 수 있음을 나타냅니다.
참조 형식 제약 조건은 형식 매개 변수에 사용되는 형식 인수가 참조 형식이 되도록 지정합니다. 모든 클래스 형식, 인터페이스 형식, 대리자 형식, 배열 형식 및 참조 형식으로 알려진 형식 매개 변수(아래에 정의된 대로)는 이 제약 조건을 충족합니다.
클래스 형식, 참조 형식 제약 조건 및 보조 제약 조건에는 nullable 형식 주석이 포함될 수 있습니다. 형식 매개 변수에 이 주석이 없거나 없는 경우 형식 인수에 대한 Null 허용 여부 기대치를 나타냅니다.
- 제약 조건에 nullable 형식 주석이 포함되지 않은 경우 형식 인수는 nullable이 아닌 참조 형식이어야 합니다. 형식 인수가 nullable 참조 형식인 경우 컴파일러에서 경고를 표시할 수 있습니다.
- 제약 조건에 nullable 형식 주석이 포함된 경우 nullable이 아닌 참조 형식과 nullable 참조 형식 모두에 의해 제약 조건이 충족됩니다.
형식 인수의 null 허용 여부가 형식 매개 변수의 null 허용 여부와 일치하지 않아도 합니다. 형식 매개 변수의 null 허용 여부가 형식 인수의 null 허용 여부와 일치하지 않는 경우 컴파일러에서 경고를 발생시킬 수 있습니다.
참고: 형식 인수가 nullable 참조 형식임을 지정하려면 nullable 형식 주석을 제약 조건(사용
T : class
또는T : BaseClass
)으로 추가하지 말고 제네릭 선언 전체에서 형식 인수에 해당하는 nullable 참조 형식을 나타내는 데 사용합니다T?
. 끝 메모
nullable 형식 주석 ?
은 제약이 없는 형식 인수에서 사용할 수 없습니다.
형식 인수가 nullable 참조 형식T
인 경우 형식 매개 변수 C?
의 T?
경우 인스턴스는 .이 아닌 C?
것으로 C??
해석됩니다.
예제: 다음 예제에서는 형식 인수의 null 허용 여부가 형식 매개 변수 선언의 null 허용 가능성에 미치는 영향을 보여 줍니다.
public class C { } public static class Extensions { public static void M<T>(this T? arg) where T : notnull { } } public class Test { public void M() { C? mightBeNull = new C(); C notNull = new C(); int number = 5; int? missing = null; mightBeNull.M(); // arg is C? notNull.M(); // arg is C? number.M(); // arg is int? missing.M(); // arg is int? } }
형식 인수가 nullable이 아닌 형식
?
인 경우 형식 주석은 매개 변수가 해당 nullable 형식임을 나타냅니다. 형식 인수가 이미 nullable 참조 형식인 경우 매개 변수는 동일한 null 허용 형식입니다.끝 예제
null이 아닌 제약 조건은 형식 매개 변수에 사용되는 형식 인수가 null을 허용하지 않는 값 형식 또는 nullable이 아닌 참조 형식이어야 임을 지정합니다. 널이 될 수 없는 값 형식이나 널이 될 수 없는 참조 형식이 아닌 형식 인수도 사용할 수 있지만, 컴파일러가 진단 경고를 발생시킬 수 있습니다.
값 형식 제약 조건은 형식 매개 변수에 사용되는 형식 인수가 null을 허용하지 않는 값 형식이 되도록 지정합니다. 값 형식 제약 조건이 있는 nullable이 아닌 모든 구조체 형식, 열거형 형식 및 형식 매개 변수는 이 제약 조건을 충족합니다. 값 형식으로 분류되지만 nullable 값 형식(§8.3.12)은 값 형식 제약 조건을 충족하지 않습니다. 값 형식 제약 조건이 있는 형식 매개 변수는 constructor_constraint 있는 다른 형식 매개 변수 에 대한 형식 인수로 사용될 수 있지만 constructor_constraint 포함하지 않습니다.
참고: 형식은
System.Nullable<T>
nullable이 아닌 값 형식 제약 조건을 지정합니다T
. 따라서 형식을 재귀적으로 생성T??
하여Nullable<Nullable<T>>
금지합니다. 끝 메모
키워드가 아니므로 unmanaged
primary_constraint 관리되지 않는 제약 조건은 항상 class_type 구문적으로 모호합니다. 호환성을 위해 이름의 이름 조회(§12.8.4)가 성공하면 해당 이름이 unmanaged
.로 class_type
처리됩니다. 그렇지 않으면 관리되지 않는 제약 조건으로 처리됩니다.
관리되지 않는 형식 제약 조건은 형식 매개 변수에 사용되는 형식 인수가 nullable이 아닌 관리되지 않는 형식(§8.8)으로 지정합니다.
포인터 형식은 형식 인수가 될 수 없으며 관리되지 않는 형식임에도 불구하고 관리되지 않는 형식 제약 조건을 충족하지 않습니다.
제약 조건이 클래스 형식, 인터페이스 형식 또는 형식 매개 변수인 경우 해당 형식은 해당 형식 매개 변수에 사용되는 모든 형식 인수가 지원하는 최소 "기본 형식"을 지정합니다. 생성된 형식 또는 제네릭 메서드를 사용할 때마다 형식 인수는 컴파일 타임에 형식 매개 변수의 제약 조건에 대해 검사됩니다. 제공된 형식 인수는 §8.4.5에 설명된 조건을 충족해야 합니다.
class_type 제약 조건은 다음 규칙을 충족해야 합니다.
- 형식은 클래스 형식이어야 합니다.
- 형식은 이어야 합니다
sealed
. - 형식은 다음 형식
System.Array
중 하나가 아니어야 합니다. 또는System.ValueType
. - 형식은 이어야 합니다
object
. - 지정된 형식 매개 변수에 대한 최대 하나의 제약 조건은 클래스 형식일 수 있습니다.
interface_type 제약 조건으로 지정된 형식은 다음 규칙을 충족해야 합니다.
- 형식은 인터페이스 형식이어야 합니다.
- 지정된
where
절에서 형식을 두 번 이상 지정해서는 안 됩니다.
두 경우 모두 제약 조건에는 생성된 형식의 일부로 연결된 형식 또는 메서드 선언의 형식 매개 변수가 포함될 수 있으며 선언되는 형식이 포함될 수 있습니다.
형식 매개 변수 제약 조건으로 지정된 모든 클래스 또는 인터페이스 형식은 선언되는 제네릭 형식 또는 메서드만큼 액세스 가능(§7.5.5)이어야 합니다.
type_parameter 제약 조건으로 지정된 형식은 다음 규칙을 충족해야 합니다.
- 형식은 형식 매개 변수여야 합니다.
- 지정된
where
절에서 형식을 두 번 이상 지정해서는 안 됩니다.
또한 형식 매개 변수의 종속성 그래프 주기가 없어야 합니다. 여기서 종속성은 다음에 정의된 전이적 관계입니다.
- 형식 매개 변수가 형식 매개 변수
T
에 대한S
S
제약 조건으로 사용되는 경우 다음에 따라 달라집니다.T
- 형식 매개 변수가 형식 매개 변수
S
에 따라 달라지고T
형식 매개 변수T
U
S
에 따라 달라U
지는 경우
이 관계를 고려할 때 형식 매개 변수가 직접 또는 간접적으로 자체적으로 의존하는 것은 컴파일 시간 오류입니다.
모든 제약 조건은 종속 형식 매개 변수 간에 일치해야 합니다. 형식 매개 변수가 형식 매개 변수 S
T
에 따라 달라지면 다음을 수행합니다.
-
T
에는 값 형식 제약 조건이 없습니다. 그렇지 않으면T
효과적으로 봉인되므로 형식이 같아야 하므로S
두 형식T
매개 변수가 필요하지 않습니다. - 값 형식 제약
S
조건이 있는 경우T
class_type 제약 조건이 없습니다. - class_type 제약 조건이
S
있고A
제약 조건이T
있는 경우 ID 변환 또는 암시적 참조 변환이B
A
B
B
있습니다.A
- 또한 형식 매개 변수에 따라 달라지고
S
U
제약 조건이U
있고A
제약 조건이T
있는 경우 ID 변환 또는 암시적 참조 변환이B
있어야 합니다A
B
B
.A
값 형식 제약 조건이 있고 S
참조 형식 제약 조건이 있는 것이 유효 T
합니다. 실제로 이 제한은 T
형식 System.Object
, System.ValueType
및 System.Enum
모든 인터페이스 형식으로 제한됩니다.
형식 매개 변수의 where
절에 생성자 제약 조건(형식 new()
포함)이 포함된 경우 연산자를 사용하여 new
형식의 인스턴스를 만들 수 있습니다(§12.8.17.2). 생성자 제약 조건이 있는 형식 매개 변수에 사용되는 모든 형식 인수는 값 형식, public 매개 변수가 없는 생성자가 있는 비 추상 클래스 또는 값 형식 제약 조건 또는 생성자 제약 조건이 있는 형식 매개 변수여야 합니다.
primary_constraint 있거나 constructor_constraintunmanaged
입니다.
예: 다음은 제약 조건의 예입니다.
interface IPrintable { void Print(); } interface IComparable<T> { int CompareTo(T value); } interface IKeyProvider<T> { T GetKey(); } class Printer<T> where T : IPrintable {...} class SortedList<T> where T : IComparable<T> {...} class Dictionary<K,V> where K : IComparable<K> where V : IPrintable, IKeyProvider<K>, new() { ... }
다음 예제에서는 형식 매개 변수의 종속성 그래프 순환이 발생하므로 오류가 발생합니다.
class Circular<S,T> where S: T where T: S // Error, circularity in dependency graph { ... }
다음 예제에서는 잘못된 추가 상황을 보여 줍니다.
class Sealed<S,T> where S : T where T : struct // Error, `T` is sealed { ... } class A {...} class B {...} class Incompat<S,T> where S : A, T where T : B // Error, incompatible class-type constraints { ... } class StructWithClass<S,T,U> where S : struct, T where T : U where U : A // Error, A incompatible with struct { ... }
끝 예제
Cₓ
다음과 같이 생성됩니다.
- 중첩 형식
C
Outer.Inner
이면Cₓ
중첩 형식Outerₓ.Innerₓ
입니다. - 형식 인수
C
가 있는 생성된 형식Cₓ
인G<A¹, ..., Aⁿ>
경우A¹, ..., Aⁿ
Cₓ
생성된 형식G<A¹ₓ, ..., Aⁿₓ>
입니다. - 배열 형식이면
C
배열 형식E[]
Cₓ
Eₓ[]
입니다. - 동적이면
C
.입니다Cₓ
.object
- 그렇지 않으면
Cₓ
가C
입니다.
T
는 다음과 같이 정의됩니다.
다음과 같은 형식 집합을 살펴보겠습니다 R
.
- 형식 매개 변수
T
인R
각 제약 조건에는 유효 기본 클래스가 포함됩니다. - 구조체 형식의
T
각 제약 조건에는 .가R
포함됩니다System.ValueType
. - 열거형 형식인
T
각 제약 조건에는 .가R
포함됩니다System.Enum
. - 대리자 형식
T
의R
각 제약 조건에 대해 해당 동적 지우기를 포함합니다. - 배열 형식인
T
각 제약 조건에는 .가R
포함됩니다System.Array
. - 클래스 형식
T
인R
각 제약 조건에 대해 해당 동적 지우기를 포함합니다.
Then
- 값 형식 제약 조건이 있는 경우
T
유효 기본 클래스는 .입니다System.ValueType
. - 그렇지 않으면 비어 있으면
R
유효 기본 클래스는 .입니다object
. - 그렇지 않으면 유효 기본 클래스는 집합
T
의 가장 포괄 형식(R
)입니다. 집합에 포괄 형식이 없으면 유효 기본 클래스T
는 .입니다object
. 일관성 규칙은 가장 포괄적인 형식이 있는지 확인합니다.
형식 매개 변수가 기본 메서드에서 제약 조건이 상속되는 메서드 형식 매개 변수인 경우 유효 기본 클래스는 형식 대체 후에 계산됩니다.
이러한 규칙은 유효 기본 클래스가 항상 class_type 보장합니다.
T
은 다음과 같이 정의됩니다.
- secondary_constraints
T
경우 유효 인터페이스 집합은 비어 있습니다. -
T
제약 조건이 있지만 type_parameter 제약 조건이 없는 경우 해당 유효 인터페이스 집합은 interface_type 제약 조건의 동적 지우기 집합입니다. - interface_type 제약 조건이 없지만
T
type_parameter 제약 조건이 있는 경우 해당 유효 인터페이스 집합은 type_parameter 제약 조건의 유효 인터페이스 집합의 결합입니다. - interface_type 제약 조건과
T
제약 조건이 모두 있는 경우 해당 유효 인터페이스 집합은 interface_type 제약 조건의 동적 지우기 집합과 해당 type_parameter 제약 조건의 유효 인터페이스 집합의 결합입니다.
형식 매개 변수는 참조 형식 제약 조건이 있거나 유효한 기본 클래스가 아닌 object
System.ValueType
경우 참조 형식으로 알려져 있습니다. 형식 매개 변수는 참조 형식으로 알려져 있고 nullable이 아닌 참조 형식 제약 조건이 있는 경우 nullable이 아닌 참조 형식이라고 합니다.
제약 조건이 내포된 인스턴스 멤버에 액세스하는 데 제한된 형식 매개 변수 형식의 값을 사용할 수 있습니다.
예: 다음에서:
interface IPrintable { void Print(); } class Printer<T> where T : IPrintable { void PrintOne(T x) => x.Print(); }
메서드
IPrintable
는 항상 구현x
하도록 제한되므로 직접T
IPrintable
호출할 수 있습니다.끝 예제
부분 제네릭 형식 선언에 제약 조건이 포함된 경우 제약 조건은 제약 조건을 포함하는 다른 모든 부분에 동의해야 합니다. 특히 제약 조건을 포함하는 각 부분에는 동일한 형식 매개 변수 집합에 대한 제약 조건이 있어야 하며 각 형식 매개 변수에 대해 기본, 보조 및 생성자 제약 조건 집합은 동일해야 합니다. 두 제약 조건 집합은 동일한 멤버를 포함하는 경우 동일합니다. 부분 제네릭 형식의 일부가 형식 매개 변수 제약 조건을 지정하지 않는 경우 형식 매개 변수는 제약이 없는 것으로 간주됩니다.
예제:
partial class Map<K,V> where K : IComparable<K> where V : IKeyProvider<K>, new() { ... } partial class Map<K,V> where V : IKeyProvider<K>, new() where K : IComparable<K> { ... } partial class Map<K,V> { ... }
는 제약 조건(처음 두 부분)을 포함하는 파트가 동일한 형식 매개 변수 집합에 대해 동일한 기본, 보조 및 생성자 제약 조건 집합을 효과적으로 지정하기 때문에 정확합니다.
끝 예제
15.2.6 클래스 본문
클래스의 class_body 해당 클래스의 멤버를 정의합니다.
class_body
: '{' class_member_declaration* '}'
;
15.2.7 부분 선언
한정자는 partial
여러 부분에서 클래스, 구조체 또는 인터페이스 형식을 정의할 때 사용됩니다. 한 partial
정자는 상황별 키워드(§6.4.4)이며 키워드 또는 키워드 바로 앞에 특별한 의미만 있습니다class
struct
interface
.
부분 형식 선언의 각 부분에는 한 partial
정자가 포함되어야 하며 동일한 네임스페이스 또는 다른 부분과 형식을 포함하는 것으로 선언되어야 합니다.
partial
한정자는 형식 선언의 추가 부분이 다른 곳에 있을 수 있지만 이러한 추가 파트의 존재는 요구 사항이 아니라 한정자를 포함하는 partial
형식의 유일한 선언에 유효합니다. 부분 형식의 선언 하나만 기본 클래스 또는 구현된 인터페이스를 포함할 수 있습니다. 그러나 지정된 형식 인수의 null 허용 여부를 포함하여 기본 클래스 또는 구현된 인터페이스의 모든 선언이 일치해야 합니다.
부분 형식의 모든 부분은 컴파일 시간에 병합될 수 있도록 함께 컴파일되어야 합니다. 부분 형식은 특히 이미 컴파일된 형식을 확장할 수 없습니다.
중첩 형식은 한정자를 사용하여 partial
여러 부분으로 선언할 수 있습니다. 일반적으로 포함 형식도 사용하여 partial
선언되며 중첩된 형식의 각 부분은 포함하는 형식의 다른 부분에서 선언됩니다.
예: 다음 partial 클래스는 서로 다른 컴파일 단위에 있는 두 부분으로 구현됩니다. 첫 번째 부분은 데이터베이스 매핑 도구에서 생성된 컴퓨터이고 두 번째 부분은 수동으로 작성됩니다.
public partial class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } } // File: Customer2.cs public partial class Customer { public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
위의 두 부분이 함께 컴파일되면 결과 코드는 다음과 같이 클래스가 단일 단위로 작성된 것처럼 동작합니다.
public class Customer { private int id; private string name; private string address; private List<Order> orders; public Customer() { ... } public void SubmitOrder(Order orderSubmitted) => orders.Add(orderSubmitted); public bool HasOutstandingOrders() => orders.Count > 0; }
끝 예제
부분 선언의 여러 부분의 형식 또는 형식 매개 변수에 지정된 특성의 처리는 §22.3에서 설명합니다.
15.3 클래스 멤버
15.3.1 일반
클래스의 멤버는 class_member_declaration도입된 멤버와 직접 기본 클래스에서 상속된 멤버로 구성됩니다.
class_member_declaration
: constant_declaration
| field_declaration
| method_declaration
| property_declaration
| event_declaration
| indexer_declaration
| operator_declaration
| constructor_declaration
| finalizer_declaration
| static_constructor_declaration
| type_declaration
;
클래스의 멤버는 다음 범주로 나뉩니다.
- 클래스와 연결된 상수 값을 나타내는 상수(§15.4)입니다.
- 클래스의 변수인 필드(§15.5)입니다.
- 클래스(§15.6)에서 수행할 수 있는 계산 및 작업을 구현하는 메서드입니다.
- 명명된 특성 및 해당 특성을 읽고 쓰는 것과 관련된 작업을 정의하는 속성(§15.7).
- 클래스(§15.8)에서 생성할 수 있는 알림을 정의하는 이벤트입니다.
- 인덱서- 클래스의 인스턴스를 배열과 동일한 방식으로(구문적으로) 인덱싱할 수 있도록 허용합니다(§15.9).
- 클래스의 인스턴스에 적용할 수 있는 식 연산자를 정의하는 연산자입니다(§15.10).
- 클래스의 인스턴스를 초기화하는 데 필요한 작업을 구현하는 인스턴스 생성자(§15.11)
- 클래스 인스턴스가 영구적으로 삭제되기 전에 수행할 작업을 구현하는 종료자(§15.13).
- 정적 생성자- 클래스 자체를 초기화하는 데 필요한 작업을 구현합니다(§15.12).
- 클래스(§14.7)에 로컬인 형식을 나타내는 형식입니다.
class_declaration 새 선언 공간(§7.3)을 만들고 class_declaration 포함된 type_parameter및 class_member_declaration 이 선언 공간에 새 멤버를 소개합니다. 다음 규칙은 class_member_declaration적용됩니다.
인스턴스 생성자, 종료자 및 정적 생성자는 즉시 바깥쪽 클래스와 동일한 이름을 가져야 합니다. 다른 모든 구성원은 즉시 바깥쪽 클래스의 이름과 다른 이름을 가져야 합니다.
클래스 선언의 type_parameter_list 형식 매개 변수의 이름은 동일한 type_parameter_list 다른 모든 형식 매개 변수의 이름과 달라야 하며 클래스의 이름 및 클래스의 모든 멤버 이름과 다릅니다.
형식의 이름은 동일한 클래스에 선언된 형식이 아닌 모든 멤버의 이름과 다릅니다. 두 개 이상의 형식 선언이 동일한 정규화된 이름을 공유하는 경우 선언에는
partial
한정자(§15.2.7)가 있어야 하며 이러한 선언이 결합되어 단일 형식을 정의합니다.
참고: 형식 선언의 정규화된 이름은 형식 매개 변수 수를 인코딩하므로 형식 매개 변수 수가 다르면 두 개의 고유 형식이 동일한 이름을 공유할 수 있습니다. 끝 메모
상수, 필드, 속성 또는 이벤트의 이름은 동일한 클래스에 선언된 다른 모든 멤버의 이름과 다릅니다.
메서드의 이름은 동일한 클래스에 선언된 다른 모든 비 메서드의 이름과 다릅니다. 또한 메서드의 서명(§7.6)은 동일한 클래스에 선언된 다른 모든 메서드의 서명과 달라야 하며, 동일한 클래스에 선언된 두 메서드에는 단독으로
in
다른 서명이 없으므로 ,out
및ref
.인스턴스 생성자의 서명은 동일한 클래스에 선언된 다른 모든 인스턴스 생성자의 서명과 달라야 하며, 동일한 클래스에 선언된 두 개의 생성자에만 다른
ref
서명이out
있어야 합니다.인덱서의 서명은 동일한 클래스에 선언된 다른 모든 인덱서의 서명과 다릅니다.
연산자의 서명은 동일한 클래스에 선언된 다른 모든 연산자의 서명과 다릅니다.
클래스의 상속된 멤버(§15.3.4)는 클래스의 선언 공간에 속하지 않습니다.
참고: 따라서 파생 클래스는 상속된 멤버와 동일한 이름 또는 시그니처를 가진 멤버를 선언할 수 있습니다(상속된 멤버는 사실상 숨겨짐). 끝 메모
여러 부분으로 선언된 형식의 멤버 집합(§15.2.7)은 각 부분에서 선언된 멤버의 합합입니다. 형식 선언의 모든 부분의 본문은 동일한 선언 공간(§7.3)을 공유하고 각 멤버의 범위(§7.7)는 모든 부분의 본문으로 확장됩니다. 멤버의 접근성 도메인에는 항상 바깥쪽 형식의 모든 부분이 포함됩니다. 한 부분에서 선언된 프라이빗 멤버는 다른 부분에서 자유롭게 액세스할 수 있습니다. 해당 멤버가 한정자가 있는 형식이 아니면 형식의 둘 이상의 부분에서 동일한 멤버를 partial
선언하는 것은 컴파일 시간 오류입니다.
예제:
partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int y; } } partial class A { int x; // Error, cannot declare x more than once partial class Inner // Ok, Inner is a partial type { int z; } }
끝 예제
필드 초기화 순서는 C# 코드 내에서 중요할 수 있으며 § 15.5.6.1에 정의된 대로 일부 보장이 제공됩니다. 그렇지 않으면 형식 내 멤버의 순서는 거의 중요하지 않지만 다른 언어 및 환경과 상호 작용할 때 중요할 수 있습니다. 이러한 경우 여러 부분으로 선언된 형식 내의 멤버 순서는 정의되지 않습니다.
15.3.2 인스턴스 유형
각 클래스 선언에는 연결된 인스턴스 형식이 있습니다. 제네릭 클래스 선언의 경우 형식 선언에서 생성된 형식(§8.4)을 만들어 인스턴스 형식을 구성하고 제공된 각 형식 인수는 해당 형식 매개 변수입니다. 인스턴스 형식은 형식 매개 변수를 사용하므로 형식 매개 변수가 범위에 있는 경우에만 사용할 수 있습니다. 즉, 클래스 선언 내에 있습니다. 인스턴스 형식은 클래스 선언 내에 작성된 코드의 this
형식입니다. 제네릭이 아닌 클래스의 경우 인스턴스 형식은 단순히 선언된 클래스입니다.
예제: 다음은 인스턴스 형식과 함께 여러 클래스 선언을 보여 줍니다.
class A<T> // instance type: A<T> { class B {} // instance type: A<T>.B class C<U> {} // instance type: A<T>.C<U> } class D {} // instance type: D
끝 예제
15.3.3 생성된 형식의 멤버
생성된 형식의 상속되지 않은 멤버는 멤버 선언의 각 type_parameter 대해 생성된 형식의 해당 type_argument 대체하여 가져옵니다. 대체 프로세스는 형식 선언의 의미 체계적 의미를 기반으로 하며 단순히 텍스트 대체가 아닙니다.
예: 제네릭 클래스 선언이 지정된 경우
class Gen<T,U> { public T[,] a; public void G(int i, T t, Gen<U,T> gt) {...} public U Prop { get {...} set {...} } public int H(double d) {...} }
생성된 형식
Gen<int[],IComparable<string>>
에는 다음 멤버가 있습니다.public int[,][] a; public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...} public IComparable<string> Prop { get {...} set {...} } public int H(double d) {...}
제네릭 클래스 선언
a
에 있는 멤버Gen
의 형식은 "2차원 배열T
"이므로 위의 생성된 형식에 있는 멤버a
의 형식은 "1차원 배열의int
2차원 배열 또는 "int[,][]
입니다.끝 예제
인스턴스 함수 멤버 내에서 형식은 포함하는 선언의 this
인스턴스 형식(§15.3.2)입니다.
제네릭 클래스의 모든 멤버는 직접 또는 생성된 형식의 일부로 모든 묶은 클래스의 형식 매개 변수를 사용할 수 있습니다. 런타임에 닫힌 특정 생성 형식(§8.4.3)을 사용하면 형식 매개 변수의 각 사용이 생성된 형식에 제공된 형식 인수로 바뀝니다.
예제:
class C<V> { public V f1; public C<V> f2; public C(V x) { this.f1 = x; this.f2 = this; } } class Application { static void Main() { C<int> x1 = new C<int>(1); Console.WriteLine(x1.f1); // Prints 1 C<double> x2 = new C<double>(3.1415); Console.WriteLine(x2.f1); // Prints 3.1415 } }
끝 예제
15.3.4 상속
클래스 는 직접 기본 클래스의 멤버를 상속합니다 . 상속은 기본 클래스의 인스턴스 생성자, 종료자 및 정적 생성자를 제외하고 클래스에 직접 기본 클래스의 모든 멤버를 암시적으로 포함한다는 것을 의미합니다. 상속의 몇 가지 중요한 측면은 다음과 같습니다.
상속은 전이적입니다. 에서
C
B
파생되고 파생된 경우B
선언된A
C
멤버와 선언된B
멤버A
를 상속합니다.파생 클래스 는 직접 기본 클래스를 확장합니다 . 파생된 클래스를 상속하는 대상에 새 멤버를 추가할 수 있지만 상속된 멤버의 정의를 제거할 수 없습니다.
인스턴스 생성자, 종료자 및 정적 생성자는 상속되지 않지만 다른 모든 멤버는 선언된 접근성(§7.5)에 관계없이 상속됩니다. 그러나 선언된 접근성에 따라 상속된 멤버는 파생 클래스에서 액세스할 수 없을 수 있습니다.
파생 클래스는 이름이나 서명이 같은 새 멤버를 선언하여 상속된 멤버를 숨길 수 있습니다(§7.7.2.3). 그러나 상속된 멤버를 숨기면 해당 멤버가 제거되지 않으며 파생 클래스를 통해 해당 멤버에 직접 액세스할 수 없게 됩니다.
클래스의 인스턴스에는 클래스 및 해당 기본 클래스에 선언된 모든 인스턴스 필드 집합이 포함되며, 파생 클래스 형식에서 기본 클래스 형식으로의 암시적 변환(§10.2.8)이 존재합니다. 따라서 일부 파생 클래스의 인스턴스에 대한 참조는 해당 기본 클래스의 인스턴스에 대한 참조로 처리될 수 있습니다.
클래스는 가상 메서드, 속성, 인덱서 및 이벤트를 선언할 수 있으며 파생 클래스는 이러한 함수 멤버의 구현을 재정의할 수 있습니다. 이를 통해 클래스는 함수 멤버 호출에 의해 수행되는 작업이 해당 함수 멤버가 호출되는 인스턴스의 런타임 형식에 따라 달라지는 다형적 동작을 나타낼 수 있습니다.
생성된 클래스 형식의 상속된 멤버는 즉시 기본 클래스 형식(§15.2.4.2)의 멤버이며, 이 멤버는 base_class_specification 해당 형식 매개 변수가 발생할 때마다 생성된 형식의 형식 인수를 대체하여 찾습니다. 이러한 멤버는 멤버 선언의 각 type_parameter 대해 base_class_specification 해당 type_argument대체하여 변환됩니다.
예제:
class B<U> { public U F(long index) {...} } class D<T> : B<T[]> { public T G(string s) {...} }
위의 코드에서 생성된 형식에는 형식 매개 변수
D<int>
에 대한 형식int
인수G(string s)
를 대체하여 가져온 상속되지 않은 멤버 공용int
T
이 있습니다.D<int>
에는 클래스 선언B
에서 상속된 멤버도 있습니다. 이 상속된 멤버는 먼저 기본 클래스 사양에서 대체하여 기본 클래스 형식B<int[]>
D<int>
을int
T
결정하여 결정됩니다B<T[]>
. 그런 다음, 형식 인수로 상속B
된 멤버int[]
를U
생성하는 in으로public U F(long index)
대체public int[] F(long index)
됩니다.끝 예제
15.3.5 새 한정자
class_member_declaration 상속된 멤버와 동일한 이름 또는 서명을 가진 멤버를 선언할 수 있습니다. 이 경우 파생 클래스 멤버는 기본 클래스 멤버를 숨깁니다 . 멤버가 상속된 멤버를 숨기는 경우의 정확한 사양은 §7.7.2.3을 참조하세요.
상속된 멤버 M
는 액세스할 수 M
있는 것으로 간주됩니다. 상속된 멤버를 암시적으로 숨기는 것은 오류로 간주되지 않지만 파생 클래스 멤버의 선언에 파생 멤버가 기본 멤버를 숨기려는 것임을 명시적으로 나타내는 new
한정자가 포함되지 않는 한 컴파일러는 경고를 발생합니다. 중첩 형식의 부분 선언(§15.2.7)에 하나 이상의 부분이 한정자를 포함하는 new
경우 중첩된 형식이 사용 가능한 상속된 멤버를 숨기는 경우 경고가 발생하지 않습니다.
new
사용 가능한 상속된 멤버를 숨기지 않는 선언에 한정자가 포함된 경우 해당 효과에 대한 경고가 발생합니다.
15.3.6 액세스 한정자
class_member_declaration 허용된 종류의 선언된 접근성(§7.5.2): public
,protected internal
, protected
, private protected
internal
또는 private
.
protected internal
및 private protected
조합을 제외하고 둘 이상의 액세스 한정자를 지정하는 것은 컴파일 시간 오류입니다.
class_member_declaration 액세스 한정자를 private
포함하지 않는 경우 가정합니다.
15.3.7 구성 요소 형식
멤버 선언에 사용되는 형식을 해당 멤버의 구성 요소 형식이라고 합니다. 가능한 구성 요소 형식은 상수, 필드, 속성, 이벤트 또는 인덱서의 형식, 메서드 또는 연산자의 반환 형식 및 메서드, 인덱서, 연산자 또는 인스턴스 생성자의 매개 변수 형식입니다. 구성원의 구성 유형은 최소한 해당 멤버 자체만큼 액세스할 수 있어야 합니다(§7.5.5).
15.3.8 정적 및 인스턴스 멤버
클래스의 멤버는 정적 멤버 또는 인스턴스 멤버입니다.
참고: 일반적으로 정적 멤버를 클래스에 속하는 것으로 간주하고 인스턴스 멤버를 개체(클래스 인스턴스)에 속하는 것으로 생각하는 것이 유용합니다. 끝 메모
필드, 메서드, 속성, 이벤트, 연산자 또는 생성자 선언에 static
한정자가 포함되어 있으면 정적 멤버를 선언합니다. 또한 상수 또는 형식 선언은 정적 멤버를 암시적으로 선언합니다. 정적 멤버의 특징은 다음과 같습니다.
- 정적 멤버
M
가 폼 의 member_accessE
되는 경우 멤버M
가 있는 형식을 나타냅니다. 인스턴스를 나타내는 컴파일 시간 오류E
입니다. - 제네릭이 아닌 클래스의 정적 필드는 정확히 하나의 스토리지 위치를 식별합니다. 제네릭이 아닌 클래스의 인스턴스가 몇 개나 만들어지더라도 정적 필드의 복사본은 하나뿐입니다. 각 고유의 닫힌 생성 형식(§8.4.3)에는 닫힌 생성된 형식의 인스턴스 수에 관계없이 고유한 정적 필드 집합이 있습니다.
- 정적 함수 멤버(메서드, 속성, 이벤트, 연산자 또는 생성자)는 특정 인스턴스에서 작동하지 않으며 이러한 함수 멤버에서 이를 참조하는 것은 컴파일 시간 오류입니다.
필드, 메서드, 속성, 이벤트, 인덱서, 생성자 또는 종료자 선언에 정적 한정자가 포함되지 않은 경우 인스턴스 멤버를 선언합니다. 인스턴스 멤버를 비정적 멤버라고도 합니다. 인스턴스 멤버의 특징은 다음과 같습니다.
- 인스턴스 멤버
M
가 양식 의 member_accessE
되는 경우 멤버M
가 있는 형식의 인스턴스를 나타냅니다. E가 형식을 나타내는 것은 바인딩 시간 오류입니다. - 클래스의 모든 인스턴스에는 클래스의 모든 인스턴스 필드 집합이 별도로 포함되어 있습니다.
- 인스턴스 함수 멤버(메서드, 속성, 인덱서, 인스턴스 생성자 또는 종료자)는 클래스의 지정된 인스턴스에서 작동하며 이 인스턴스는 (
this
)로 액세스할 수 있습니다.
예제: 다음 예제에서는 정적 및 인스턴스 멤버에 액세스하기 위한 규칙을 보여 줍니다.
class Test { int x; static int y; void F() { x = 1; // Ok, same as this.x = 1 y = 1; // Ok, same as Test.y = 1 } static void G() { x = 1; // Error, cannot access this.x y = 1; // Ok, same as Test.y = 1 } static void Main() { Test t = new Test(); t.x = 1; // Ok t.y = 1; // Error, cannot access static member through instance Test.x = 1; // Error, cannot access instance member through type Test.y = 1; // Ok } }
이 메서드는
F
인스턴스 함수 멤버 에서 simple_name (§12.8.4)를 사용하여 인스턴스 멤버와 정적 멤버 모두에 액세스할 수 있음을 보여 줍니다. 이 메서드는G
정적 함수 멤버에서 simple_name 통해 인스턴스 멤버에 액세스하는 것이 컴파일 시간 오류임을 보여 줍니다. 이 메서드는Main
member_access(§12.8.7)에서 인스턴스 멤버에 인스턴스 멤버에 액세스하고 정적 멤버는 형식을 통해 액세스해야 임을 보여 줍니다.끝 예제
15.3.9 중첩 형식
15.3.9.1 일반
클래스 또는 구조체 내에서 선언된 형식을 중첩된 형식이라고합니다. 컴파일 단위 또는 네임스페이스 내에서 선언된 형식을 중첩되지 않은 형식이라고 합니다.
예: 다음 예제에서:
class A { class B { static void F() { Console.WriteLine("A.B.F"); } } }
클래스
B
는 클래스A
내에서 선언되므로 중첩 형식이고A
클래스는 컴파일 단위 내에서 선언되므로 중첩되지 않은 형식입니다.끝 예제
15.3.9.2 정규화된 이름
중첩 형식 선언 의 정규화된 이름(S.N
)입니다. 여기서 S
형식이 선언된 형식 선언의 정규화된 이름이며 N
중첩된 형식 선언 N
의 정규화되지 않은 이름(§7.8.2)입니다(모든 generic_dimension_specifier 포함)(§12.8.18).
15.3.9.3 접근성 선언됨
중첩되지 않은 형식은 접근성을 포함 public
하거나 internal
선언할 수 있으며 internal
기본적으로 접근성을 선언했습니다. 중첩 형식에는 이러한 형식의 선언된 접근성과 포함된 형식이 클래스인지 구조체인지에 따라 하나 이상의 추가 형식의 선언된 접근성이 있을 수 있습니다.
- 클래스에 선언된 중첩 형식은 허용된 종류의 선언된 접근성을 가질 수 있으며, 다른 클래스 멤버와 마찬가지로 기본값
private
은 선언된 접근성입니다. - 구조체에 선언된 중첩 형식은 세 가지 형식의 선언된 접근성(
public
internal
또는private
)을 가질 수 있으며, 다른 구조체 멤버와 마찬가지로 기본값private
은 선언된 접근성입니다.
예: 예제
public class List { // Private data structure private class Node { public object Data; public Node? Next; public Node(object data, Node? next) { this.Data = data; this.Next = next; } } private Node? first = null; private Node? last = null; // Public interface public void AddToFront(object o) {...} public void AddToBack(object o) {...} public object RemoveFromFront() {...} public object RemoveFromBack() {...} public int Count { get {...} } }
는 프라이빗 중첩 클래스
Node
를 선언합니다.끝 예제
15.3.9.4 숨기기
중첩 형식은 기본 멤버를 숨길 수 있습니다(§7.7.2.2). 숨김 new
을 명시적으로 표현할 수 있도록 중첩된 형식 선언에서 한정자(§15.3.5)가 허용됩니다.
예: 예제
class Base { public static void M() { Console.WriteLine("Base.M"); } } class Derived: Base { public new class M { public static void F() { Console.WriteLine("Derived.M.F"); } } } class Test { static void Main() { Derived.M.F(); } }
에 정의된 메서드
M
를 숨기는 중첩 클래스M
를 보여 줍니다Base
.끝 예제
15.3.9.5 이 액세스
중첩 형식 및 해당 포함 형식은 this_access 관련해서 특별한 관계가 없습니다(§12.8.14). 특히 중 this
첩된 형식 내에서는 포함하는 형식의 인스턴스 멤버를 참조하는 데 사용할 수 없습니다. 중첩된 형식이 포함된 형식의 인스턴스 멤버에 액세스해야 하는 경우 포함된 형식의 인스턴스를 중첩 형식에 대한 생성자 인수로 제공하여 this
액세스를 제공할 수 있습니다.
예: 다음 예제
class C { int i = 123; public void F() { Nested n = new Nested(this); n.G(); } public class Nested { C this_c; public Nested(C c) { this_c = c; } public void G() { Console.WriteLine(this_c.i); } } } class Test { static void Main() { C c = new C(); c.F(); } }
은 이 기술을 보여줍니다. 인스턴스는
C
인스턴스의Nested
인스턴스를 만들고 인스턴스 멤버에 대한 후속 액세스를 제공하기 위해Nested
이 인스턴스를C
'의 생성자에 전달합니다.끝 예제
15.3.9.6 포함하는 형식의 개인 및 보호된 멤버에 대한 액세스
중첩된 형식은 포함된 형식에 액세스할 수 있는 모든 멤버(포함된 형식 private
의 멤버 및 protected
선언된 접근성)에 액세스할 수 있습니다.
예: 예제
class C { private static void F() => Console.WriteLine("C.F"); public class Nested { public static void G() => F(); } } class Test { static void Main() => C.Nested.G(); }
는 중첩된 클래스
C
를 포함하는 클래스Nested
를 보여 줍니다. 내에서Nested
메서드G
는 정의된 정적 메서드F
를C
호출하고F
프라이빗으로 선언된 접근성을 가합니다.끝 예제
중첩된 형식은 포함하는 형식의 기본 형식에 정의된 보호된 멤버에 액세스할 수도 있습니다.
예제: 다음 코드에서
class Base { protected void F() => Console.WriteLine("Base.F"); } class Derived: Base { public class Nested { public void G() { Derived d = new Derived(); d.F(); // ok } } } class Test { static void Main() { Derived.Nested n = new Derived.Nested(); n.G(); } }
중첩 클래스
Derived.Nested
는 인스턴스를 통해 호출하여 '의 기본 클래스F
에 정의된Derived
보호된 메서드Base
에Derived
액세스합니다.끝 예제
15.3.9.7 제네릭 클래스의 중첩 형식
제네릭 클래스 선언에는 중첩된 형식 선언이 포함될 수 있습니다. 바깥쪽 클래스의 형식 매개 변수는 중첩된 형식 내에서 사용할 수 있습니다. 중첩된 형식 선언에는 중첩된 형식에만 적용되는 추가 형식 매개 변수가 포함될 수 있습니다.
제네릭 클래스 선언 내에 포함된 모든 형식 선언은 암시적으로 제네릭 형식 선언입니다. 제네릭 형식 내에 중첩된 형식에 대한 참조를 작성할 때 해당 형식 인수를 포함하여 포함된 생성된 형식의 이름을 지정해야 합니다. 그러나 외부 클래스 내에서 중첩된 형식은 한정 없이 사용할 수 있습니다. 외부 클래스의 인스턴스 형식은 중첩된 형식을 생성할 때 암시적으로 사용될 수 있습니다.
예제: 다음은 생성된
Inner
생성된 형식을 참조하는 세 가지 올바른 방법을 보여 하며 처음 두 가지는 동일합니다.class Outer<T> { class Inner<U> { public static void F(T t, U u) {...} } static void F(T t) { Outer<T>.Inner<string>.F(t, "abc"); // These two statements have Inner<string>.F(t, "abc"); // the same effect Outer<int>.Inner<string>.F(3, "abc"); // This type is different Outer.Inner<string>.F(t, "abc"); // Error, Outer needs type arg } }
끝 예제
잘못된 프로그래밍 스타일이지만 중첩 형식의 형식 매개 변수는 외부 형식에 선언된 멤버 또는 형식 매개 변수를 숨길 수 있습니다.
예제:
class Outer<T> { class Inner<T> // Valid, hides Outer's T { public T t; // Refers to Inner's T } }
끝 예제
15.3.10 예약 멤버 이름
15.3.10.1 일반
기본 C# 런타임 구현을 용이하게 하기 위해 속성, 이벤트 또는 인덱서인 각 원본 멤버 선언에 대해 구현은 멤버 선언의 종류, 이름 및 형식(§15.3.10.2, §15.3.10.3, §15.3.10.4)에 따라 두 개의 메서드 서명을 예약해야 합니다. 기본 런타임 구현에서 이러한 예약을 사용하지 않더라도 동일한 범위에서 선언된 멤버가 예약한 서명과 서명이 일치하는 멤버를 선언하는 것은 프로그램의 컴파일 시간 오류입니다.
예약된 이름은 선언을 도입하지 않으므로 멤버 조회에 참여하지 않습니다. 그러나 선언의 연결된 예약 메서드 서명은 상속(§15.3.4)에 참여하고 한정자(new
)를 사용하여 숨길 수 있습니다.
참고: 이러한 이름의 예약은 다음 세 가지 용도로 사용됩니다.
- 기본 구현에서 일반 식별자를 C# 언어 기능에 대한 액세스 권한을 얻거나 설정하기 위한 메서드 이름으로 사용할 수 있도록 합니다.
- 다른 언어가 C# 언어 기능에 대한 액세스를 얻거나 설정하기 위한 메서드 이름으로 일반 식별자를 사용하여 상호 운용할 수 있도록 합니다.
- 모든 C# 구현에서 예약된 멤버 이름의 세부 사항을 일관되게 설정하여 한 컴파일러가 허용하는 원본이 다른 컴파일러에 의해 수락되도록 합니다.
끝 메모
종료자 선언(§15.13)으로 인해 서명이 예약됩니다(§15.3.10.5).
특정 이름은 연산자 메서드 이름(§15.3.10.6)으로 사용하도록 예약되어 있습니다.
15.3.10.2 속성에 예약된 멤버 이름
T get_P();
void set_P(T value);
속성이 읽기 전용이거나 쓰기 전용인 경우에도 두 서명 모두 예약됩니다.
예제: 다음 코드에서
class A { public int P { get => 123; } } class B : A { public new int get_P() => 456; public new void set_P(int value) { } } class Test { static void Main() { B b = new B(); A a = b; Console.WriteLine(a.P); Console.WriteLine(b.P); Console.WriteLine(b.get_P()); } }
클래스
A
는 읽기 전용 속성을P
정의하므로 서명 및get_P
메서드를set_P
예약합니다.A
클래스B
는 이러한 예약된 서명에서A
파생되고 숨깁니다. 이 예제에서는 출력을 생성합니다.123 123 456
끝 예제
15.3.10.3 이벤트용으로 예약된 멤버 이름
대리자 형식E
의 이벤트 (T
)의 경우 다음 서명이 예약됩니다.
void add_E(T handler);
void remove_E(T handler);
15.3.10.4 인덱서용으로 예약된 멤버 이름
매개 변수 목록을 사용하는 형식 T
의 인덱서(L
)의 경우 다음 서명이 예약되어 있습니다.
T get_Item(L);
void set_Item(L, T value);
인덱서가 읽기 전용이거나 쓰기 전용인 경우에도 두 서명이 모두 예약됩니다.
또한 멤버 이름은 Item
예약되어 있습니다.
15.3.10.5 종료자를 위해 예약된 멤버 이름
종료자(§15.13)를 포함하는 클래스의 경우 다음 서명이 예약되어 있습니다.
void Finalize();
15.3.10.6 연산자를 위해 예약된 메서드 이름
다음 메서드 이름은 예약되어 있습니다. 많은 연산자가 이 사양에 있지만 일부는 이후 버전에서 사용하도록 예약된 반면 일부는 다른 언어와의 interop용으로 예약되어 있습니다.
메서드 이름 | C# 연산자 |
---|---|
op_Addition |
+ (이진) |
op_AdditionAssignment |
(예약됨) |
op_AddressOf |
(예약됨) |
op_Assign |
(예약됨) |
op_BitwiseAnd |
& (이진) |
op_BitwiseAndAssignment |
(예약됨) |
op_BitwiseOr |
\| |
op_BitwiseOrAssignment |
(예약됨) |
op_CheckedAddition |
(나중에 사용하기 위해 예약됨) |
op_CheckedDecrement |
(나중에 사용하기 위해 예약됨) |
op_CheckedDivision |
(나중에 사용하기 위해 예약됨) |
op_CheckedExplicit |
(나중에 사용하기 위해 예약됨) |
op_CheckedIncrement |
(나중에 사용하기 위해 예약됨) |
op_CheckedMultiply |
(나중에 사용하기 위해 예약됨) |
op_CheckedSubtraction |
(나중에 사용하기 위해 예약됨) |
op_CheckedUnaryNegation |
(나중에 사용하기 위해 예약됨) |
op_Comma |
(예약됨) |
op_Decrement |
-- (접두사 및 후위) |
op_Division |
/ |
op_DivisionAssignment |
(예약됨) |
op_Equality |
== |
op_ExclusiveOr |
^ |
op_ExclusiveOrAssignment |
(예약됨) |
op_Explicit |
명시적(축소) 강제 변환 |
op_False |
false |
op_GreaterThan |
> |
op_GreaterThanOrEqual |
>= |
op_Implicit |
암시적(확대) 강제 변환 |
op_Increment |
++ (접두사 및 후위) |
op_Inequality |
!= |
op_LeftShift |
<< |
op_LeftShiftAssignment |
(예약됨) |
op_LessThan |
< |
op_LessThanOrEqual |
<= |
op_LogicalAnd |
(예약됨) |
op_LogicalNot |
! |
op_LogicalOr |
(예약됨) |
op_MemberSelection |
(예약됨) |
op_Modulus |
% |
op_ModulusAssignment |
(예약됨) |
op_MultiplicationAssignment |
(예약됨) |
op_Multiply |
* (이진) |
op_OnesComplement |
~ |
op_PointerDereference |
(예약됨) |
op_PointerToMemberSelection |
(예약됨) |
op_RightShift |
>> |
op_RightShiftAssignment |
(예약됨) |
op_SignedRightShift |
(예약됨) |
op_Subtraction |
- (이진) |
op_SubtractionAssignment |
(예약됨) |
op_True |
true |
op_UnaryNegation |
- (단항) |
op_UnaryPlus |
+ (단항) |
op_UnsignedRightShift |
(나중에 사용하기 위해 예약됨) |
op_UnsignedRightShiftAssignment |
(예약됨) |
15.4 상수
상수는 컴파일 시간에 계산할 수 있는 값인 상수 값을 나타내는 클래스 멤버입니다. constant_declaration 지정된 형식의 상수가 하나 이상 있습니다.
constant_declaration
: attributes? constant_modifier* 'const' type constant_declarators ';'
;
constant_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
;
constant_declaration 특성 집합(§22), new
한정자(§15.3.5) 및 허용된 종류의 선언된 접근성(§15.3.6) 중 하나를 포함할 수 있습니다. 특성 및 한정자는 constant_declaration 선언된 모든 멤버에 적용됩니다. 상수는 정적 멤버 로 간주되지만 constant_declaration 한정자가 필요하거나 허용되지 static
않습니다. 동일한 한정자가 상수 선언에 여러 번 표시되는 것은 오류입니다.
constant_declaration 형식은 선언에 의해 도입된 멤버의 형식을 지정합니다. 형식 뒤에는 constant_declarator목록(§13.6.3)이 있으며, 각각 새 멤버가 도입됩니다.
constant_declarator 멤버 이름을 지정하는 식별자와 "" 토큰, =
(§12.23)로 구성됩니다.
상수 선언에 지정된 형식은 , ,, sbyte
, byte
, short
,ushort
int
uint
long
ulong
char
float
double
decimal
, enum_type 또는 string
각 constant_expression 암시적 변환(§10.2)을 통해 대상 형식 또는 대상 형식으로 변환할 수 있는 형식의 값을 생성해야 합니다.
상수의 형식은 상수 자체(§7.5.5)만큼 액세스 가능해야 합니다.
상수 값은 simple_name(§12.8.4) 또는 member_access(§12.8.7)를 사용하여 식에서 가져옵니다.
상수 자체는 constant_expression 참여할 수 있습니다. 따라서 상수는 constant_expression 필요로 하는 모든 구문에 사용될 수 있습니다.
참고: 이러한 구문의 예로는
case
레이블, 문,goto case
enum
멤버 선언, 특성 및 기타 상수 선언이 있습니다. 끝 메모
참고: §12.23에 설명된 대로 constant_expression 컴파일 타임에 완전히 평가할 수 있는 식입니다. reference_type null이 아닌 값을
null
끝 메모
상수 값의 기호 이름이 필요한 경우, 상수 선언에서 해당 값의 형식이 허용되지 않거나 컴파일 타임 에 constant_expression 값을 계산할 수 없는 경우 읽기 전용 필드(§15.5.3)를 대신 사용할 수 있습니다.
참고: 버전 관리 의미 체계
const
및readonly
차이점(§15.5.3.3). 끝 메모
여러 상수 선언은 특성, 한정자 및 형식이 동일한 단일 상수의 여러 선언과 동일합니다.
예제:
class A { public const double X = 1.0, Y = 2.0, Z = 3.0; }
위의 식은 아래의 식과 동일합니다.
class A { public const double X = 1.0; public const double Y = 2.0; public const double Z = 3.0; }
끝 예제
종속성이 순환 특성이 아닌 한 상수는 동일한 프로그램 내의 다른 상수에 종속될 수 있습니다.
예제: 다음 코드에서
class A { public const int X = B.Z + 1; public const int Y = 10; } class B { public const int Z = A.Y + 1; }
컴파일러는 먼저
A.Y
평가한 다음,B.Z
평가하고, 마지막으로A.X
평가하여10
,11
및12
값을 생성해야 합니다.끝 예제
상수 선언은 다른 프로그램의 상수에 따라 달라질 수 있지만 이러한 종속성은 한 방향으로만 가능합니다.
예: 위의 예제를 참조하여 별도의 프로그램에서 선언된 경우
A
B
종속A.X
될 수 있지만B.Z
동시에 종B.Z
속될A.Y
수는 없습니다. 끝 예제
15.5 필드
15.5.1 일반
필드는 개체 또는 클래스와 연결된 변수를 나타내는 멤버입니다. field_declaration 지정된 형식의 하나 이상의 필드를 소개합니다.
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
field_declaration 특성 집합(§22), new
한정자(§15.3.5), 4개의 액세스 한정자(§15.3.6static
§15.5.2)의 유효한 조합이 포함될 수 있습니다. 또한 field_declaration 한정자(readonly
) 또는 한정자(volatile
)를 포함할 수 있지만 둘 다 포함 할 수는 없습니다. 특성 및 한정자는 field_declaration 선언된 모든 멤버에 적용됩니다. 동일한 한정자가 field_declaration 여러 번 표시되는 것은 오류입니다.
field_declaration 형식은 선언에 의해 도입된 멤버의 형식을 지정합니다. 형식 뒤에는 variable_declarator목록이 있으며 각각 새 멤버가 도입됩니다.
variable_declarator 해당 멤버의 이름을 지정하는 식별자, 필요에 따라 "" 토큰 및 =
초기 값을 제공하는 variable_initializer(§15.5.6)로 구성됩니다.
필드의 형식은 적어도 필드 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
필드 값은 simple_name(§12.8.4), member_access(§12.8.7) 또는 base_access(§12.8.15)를 사용하여 식에서 가져옵니다. 읽기 전용이 아닌 필드의 값은 할당(§12.21)을 사용하여 수정됩니다. 읽기 전용이 아닌 필드의 값은 접두사 증가 및 감소 연산자(§12.8.16) 및 접두사 증가 및 감소 연산자(§12.9.6)를 사용하여 가져오고 수정할 수 있습니다.
여러 필드를 선언하는 필드 선언은 특성, 한정자 및 형식이 동일한 단일 필드의 여러 선언과 동일합니다.
예제:
class A { public static int X = 1, Y, Z = 100; }
위의 식은 아래의 식과 동일합니다.
class A { public static int X = 1; public static int Y; public static int Z = 100; }
끝 예제
15.5.2 정적 및 인스턴스 필드
필드 선언에 한 static
정자가 포함된 경우 선언에서 도입된 필드는 정적 필드입니다. 한정자가 없 static
으면 선언에서 도입된 필드는 인스턴스 필드입니다. 정적 필드와 인스턴스 필드는 C#에서 지원하는 여러 종류의 변수(§9)의 두 가지이며, 때때로 정적 변수와 인스턴스 변수라고도 합니다.
§15.3.8에서 설명한 대로 클래스의 각 인스턴스는 클래스의 인스턴스 필드의 전체 집합을 포함하지만, 클래스 또는 닫힌 생성 형식의 인스턴스 수에 관계없이 제네릭이 아닌 클래스 또는 닫힌 생성 형식에 대한 정적 필드 집합은 하나뿐입니다.
15.5.3 읽기 전용 필드
15.5.3.1 일반
field_declaration 한정자를 readonly
포함하는 경우 선언에서 도입된 필드는 읽기 전용 필드입니다. 읽기 전용 필드에 대한 직접 할당은 해당 선언의 일부 또는 동일한 클래스의 인스턴스 생성자 또는 정적 생성자에서만 발생할 수 있습니다. (이러한 컨텍스트에서 읽기 전용 필드를 여러 번 할당할 수 있습니다.) 특히 읽기 전용 필드에 대한 직접 할당은 다음 컨텍스트에서만 허용됩니다.
- 필드를 소개하는 variable_declarator(선언에 variable_initializer 포함)
- 인스턴스 필드의 경우 필드 선언을 포함하는 클래스의 인스턴스 생성자에서 필드 선언을 포함하는 클래스의 정적 생성자에서 정적 필드의 경우 읽기 전용 필드를 출력 또는 참조 매개 변수로 전달하는 것이 유효한 유일한 컨텍스트이기도 합니다.
읽기 전용 필드에 할당하거나 다른 컨텍스트에서 출력 또는 참조 매개 변수로 전달하려고 하면 컴파일 시간 오류가 발생합니다.
15.5.3.2 상수에 정적 읽기 전용 필드 사용
정적 읽기 전용 필드는 상수 값의 기호 이름을 원하는 경우와 const 선언에서 값 형식이 허용되지 않거나 컴파일 타임에 값을 계산할 수 없는 경우에 유용합니다.
예제: 다음 코드에서
public class Color { public static readonly Color Black = new Color(0, 0, 0); public static readonly Color White = new Color(255, 255, 255); public static readonly Color Red = new Color(255, 0, 0); public static readonly Color Green = new Color(0, 255, 0); public static readonly Color Blue = new Color(0, 0, 255); private byte red, green, blue; public Color(byte r, byte g, byte b) { red = r; green = g; blue = b; } }
Black
컴파일 시간에 값을 계산할 수 없으므로 ,White
,Red
Green
및Blue
멤버를 const 멤버로 선언할 수 없습니다. 그러나 대신 선언하는static readonly
것은 거의 동일한 효과가 있습니다.끝 예제
15.5.3.3 상수 및 정적 읽기 전용 필드 버전 관리
상수 및 읽기 전용 필드에는 서로 다른 이진 버전 관리 의미 체계가 있습니다. 식에서 상수 값을 참조하는 경우 컴파일 시간에 상수 값을 가져오지만 식이 읽기 전용 필드를 참조하는 경우 런타임까지 필드 값을 가져오지 않습니다.
예: 두 개의 개별 프로그램으로 구성된 애플리케이션을 고려합니다.
namespace Program1 { public class Utils { public static readonly int x = 1; } }
및
namespace Program2 { class Test { static void Main() { Console.WriteLine(Program1.Utils.X); } } }
Program1
및Program2
네임스페이스는 별도로 컴파일되는 두 프로그램을 나타냅니다.Program1.Utils.X
필드로static readonly
선언되므로 명령문의Console.WriteLine
값 출력은 컴파일 시간에 알려지지 않고 런타임에 가져옵니다. 따라서 값X
이 변경되고Program1
다시 컴파일되면Console.WriteLine
문이 다시 컴파일되지 않더라Program2
도 새 값을 출력합니다. 그러나 상수였고X
, 값X
은 컴파일될 때Program2
얻었을 것이며, 다시 컴파일될 때까지Program1
변경 내용의Program2
영향을 받지 않습니다.끝 예제
15.5.4 휘발성 필드
field_declaration 한정자를 포함하는 volatile
경우 해당 선언에서 도입된 필드는 휘발성 필드입니다. 비휘발성 필드의 경우 명령을 다시 정렬하는 최적화 기법을 사용하면 lock_statement(§13.13)에서 제공하는 것과 같이 동기화 없이 필드에 액세스하는 다중 스레드 프로그램에서 예기치 않고 예측할 수 없는 결과를 초래할 수 있습니다. 이러한 최적화는 컴파일러, 런타임 시스템 또는 하드웨어에서 수행할 수 있습니다. 휘발성 필드의 경우 이러한 다시 정렬 최적화가 제한됩니다.
- 휘발성 필드의 읽기를 휘발성 읽기라고합니다. 휘발성 읽기에는 "의미 체계 획득"이 있습니다. 즉, 명령 순서에서 메모리를 참조하기 전에 발생하도록 보장됩니다.
- 휘발성 필드의 쓰기를 휘발성 쓰기라고합니다. 휘발성 쓰기에는 "릴리스 의미 체계"가 있습니다. 즉, 명령 시퀀스의 쓰기 명령 이전에 메모리 참조 후에 발생하도록 보장됩니다.
이러한 제한 사항은 모든 스레드가 수행된 순서대로 다른 스레드가 휘발성 쓰기를 수행하게 합니다. 모든 실행 스레드에서 볼 수 있듯이 휘발성 쓰기의 총 순서를 하나만 제공하는 데는 준수 구현이 필요하지 않습니다. 휘발성 필드의 형식은 다음 중 하나여야 합니다.
- reference_type.
- 참조 형식(§15.2.5)으로 알려진 type_parameter.
- 형식
byte
, ,sbyte
,short
,ushort
,int
,uint
char
,float
,bool
System.IntPtr
또는System.UIntPtr
. -
short
형식이ushort
int
있는uint
예: 예제
class Test { public static int result; public static volatile bool finished; static void Thread2() { result = 143; finished = true; } static void Main() { finished = false; // Run Thread2() in a new thread new Thread(new ThreadStart(Thread2)).Start(); // Wait for Thread2() to signal that it has a result // by setting finished to true. for (;;) { if (finished) { Console.WriteLine($"result = {result}"); return; } } } }
다음 출력을 생성합니다.
result = 143
이 예제에서 메서드는 메서드
Main
를 실행하는 새 스레드를Thread2
시작합니다. 이 메서드는 값을 비휘발성 필드에 저장result
한 다음, 휘발성 필드에true
저장finished
합니다. 주 스레드는 필드finished
가 설정true
될 때까지 기다린 다음 필드를result
읽습니다. 선언되었finished
으므로volatile
주 스레드는 필드에서143
값을result
읽어야합니다. 필드finished
가 선언volatile
되지 않은 경우 저장소result
다음에 저장소가 주 스레드에 표시되므로 주 스레드finished
가 필드에서result
값 0을 읽는 것이 허용됩니다. 필드로finished
선언하면volatile
이러한 불일치가 방지됩니다.끝 예제
15.5.5 필드 초기화
정적 필드든 인스턴스 필드이든 필드의 초기 값은 필드 형식의 기본값(§9.3)입니다. 이 기본 초기화가 발생하기 전에 필드 값을 관찰할 수 없으므로 필드가 "초기화되지 않음"이 아닙니다.
예: 예제
class Test { static bool b; int i; static void Main() { Test t = new Test(); Console.WriteLine($"b = {b}, i = {t.i}"); } }
는 출력을 생성합니다.
b = False, i = 0
b
때문에i
둘 다 자동으로 기본값으로 초기화됩니다.끝 예제
15.5.6 변수 이니셜라이저
15.5.6.1 일반
필드 선언에는 variable_initializer포함할 수 있습니다. 정적 필드의 경우 변수 이니셜라이저는 클래스 초기화 중에 실행되는 할당 문에 해당합니다. 인스턴스 필드의 경우 변수 이니셜라이저는 클래스의 인스턴스를 만들 때 실행되는 할당 문에 해당합니다.
예: 예제
class Test { static double x = Math.Sqrt(2.0); int i = 100; string s = "Hello"; static void Main() { Test a = new Test(); Console.WriteLine($"x = {x}, i = {a.i}, s = {a.s}"); } }
는 출력을 생성합니다.
x = 1.4142135623730951, i = 100, s = Hello
은
x
정적 필드 이니셜라이저가 실행되고 인스턴스 필드 이니셜라이저가 실행되면i
할당과s
할당이 발생할 때 발생하므로끝 예제
§15.5.5에 설명된 기본값 초기화는 변수 이니셜라이저가 있는 필드를 포함하여 모든 필드에 대해 발생합니다. 따라서 클래스가 초기화되면 해당 클래스의 모든 정적 필드가 먼저 기본값으로 초기화된 다음 정적 필드 이니셜라이저가 텍스트 순서로 실행됩니다. 마찬가지로 클래스의 인스턴스가 만들어지면 해당 인스턴스의 모든 인스턴스 필드가 먼저 기본값으로 초기화된 다음 인스턴스 필드 이니셜라이저가 텍스트 순서로 실행됩니다. 동일한 형식에 대한 여러 부분 형식 선언에 필드 선언이 있는 경우 파트의 순서가 지정되지 않습니다. 그러나 각 부분 내에서 필드 이니셜라이저는 순서대로 실행됩니다.
변수 이니셜라이저가 있는 정적 필드가 기본값 상태에서 관찰될 수 있습니다.
예: 그러나 스타일 문제로 권장되지는 않습니다. 예제
class Test { static int a = b + 1; static int b = a + 1; static void Main() { Console.WriteLine($"a = {a}, b = {b}"); } }
는 이 동작을 보여 줍니다. 및 프로그램의 순환 정의
a
b
에도 불구하고 유효합니다. 출력이 생성됩니다.a = 1, b = 2
정적 필드
a
가b
이니셜라이저가 실행되기0
전에 (기본값int
)로 초기화되기 때문입니다. 실행에 대한a
이니셜라이저가b
실행되면 값이 0이므로a
.1
실행에 대한b
이니셜라이저가 실행되면 값이 이미1
있으므로b
초기화됩니다2
.끝 예제
15.5.6.2 정적 필드 초기화
클래스의 정적 필드 변수 이니셜라이저는 클래스 선언(§15.5.6.1)에 나타나는 텍스트 순서로 실행되는 할당 시퀀스에 해당합니다. 부분 클래스 내에서 "텍스트 순서"의 의미는 §15.5.6.1로 지정됩니다. 정적 생성자(§15.12)가 클래스에 있는 경우 정적 필드 이니셜라이저의 실행은 해당 정적 생성자를 실행하기 직전에 발생합니다. 그렇지 않으면 정적 필드 이니셜라이저는 해당 클래스의 정적 필드를 처음 사용하기 전에 구현 종속 시간에 실행됩니다.
예: 예제
class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { public static int X = Test.F("Init A"); } class B { public static int Y = Test.F("Init B"); }
는 다음 출력 중 하나를 생성할 수 있습니다.
Init A Init B 1 1
또는 출력:
Init B Init A 1 1
'의 이니셜라이저와
X
이니셜라이저의 실행Y
은 두 순서로 모두 발생할 수 있으므로 해당 필드에 대한 참조 전에만 발생하도록 제한됩니다. 그러나 예제에서는 다음을 수행합니다.class Test { static void Main() { Console.WriteLine($"{B.Y} {A.X}"); } public static int F(string s) { Console.WriteLine(s); return 1; } } class A { static A() {} public static int X = Test.F("Init A"); } class B { static B() {} public static int Y = Test.F("Init B"); }
출력은 다음과 같습니다.
Init B Init A 1 1
는 정적 생성자가 실행되는 경우(§15.12에 정의된 대로)
B
'정적 생성자(따라서B
정적 필드 이니셜라이저)가 '정적 생성자 및 필드 이니셜라이저 앞에A
실행되어야 하므로입니다.끝 예제
15.5.6.3 인스턴스 필드 초기화
클래스의 인스턴스 필드 변수 이니셜라이저는 해당 클래스의 인스턴스 생성자(§15.11.3) 중 하나에 진입할 때 즉시 실행되는 할당 시퀀스에 해당합니다. 부분 클래스 내에서 "텍스트 순서"의 의미는 §15.5.6.1로 지정됩니다. 변수 이니셜라이저는 클래스 선언(§15.5.6.1)에 나타나는 텍스트 순서로 실행됩니다. 클래스 인스턴스 만들기 및 초기화 프로세스는 §15.11에 자세히 설명되어 있습니다.
인스턴스 필드의 변수 이니셜라이저는 생성되는 인스턴스를 참조할 수 없습니다. 따라서 변수 이니셜라이저가 simple_namethis
인스턴스 멤버를 참조 하는 컴파일 시간 오류이므로 변수 이니셜라이저에서 참조하는 것은 컴파일 시간 오류입니다.
예제: 다음 코드에서
class A { int x = 1; int y = x + 1; // Error, reference to instance member of this }
생성되는 인스턴스의 멤버를 참조하기 때문에 컴파일 시간 오류의 결과에 대한
y
변수 이니셜라이저입니다.끝 예제
15.6 메서드
15.6.1 일반
메서드는 개체 또는 클래스에서 수행할 수 있는 계산이나 작업을 구현하는 멤버입니다. 메서드는 method_declaration사용하여 선언됩니다.
method_declaration
: attributes? method_modifiers return_type method_header method_body
| attributes? ref_method_modifiers ref_kind ref_return_type method_header
ref_method_body
;
method_modifiers
: method_modifier* 'partial'?
;
ref_kind
: 'ref'
| 'ref' 'readonly'
;
ref_method_modifiers
: ref_method_modifier*
;
method_header
: member_name '(' parameter_list? ')'
| member_name type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
method_modifier
: ref_method_modifier
| 'async'
;
ref_method_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
return_type
: ref_return_type
| 'void'
;
ref_return_type
: type
;
member_name
: identifier
| interface_type '.' identifier
;
method_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
| ';'
;
ref_method_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
문법 참고 사항:
- unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
- null_conditional_invocation_expression 및 식 대안을 모두 적용할 수 있는 경우 method_body 인식할 때 전자를 선택해야 합니다.
참고: 여기서 대안의 겹침과 우선 순위는 설명적인 편의를 위한 것일 뿐입니다. 겹침을 제거하기 위해 문법 규칙을 자세히 설명할 수 있습니다. ANTLR 및 기타 문법 시스템은 동일한 편의를 채택하므로 method_body 지정된 의미 체계를 자동으로 가합니다. 끝 메모
method_declaration 특성 집합(§22)과 허용된 종류의 선언된 접근성(§15.3.6), (§15.3.5), new
(§15.6.3), static
(§15.15) virtual
중 하나를 포함할 수 있습니다..6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7), extern
(§15.6.8) 및 async
(§15.15) 한정자.
다음 중 모두 true인 경우 선언에 유효한 한정자 조합이 있습니다.
- 선언에는 유효한 액세스 한정자 조합(§15.3.6)이 포함됩니다.
- 선언에는 동일한 한정자가 여러 번 포함되지 않습니다.
- 선언에는 다음 한정자
static
virtual
override
중 최대 하나가 포함됩니다. - 선언에는 다음 한정자
new
중 하나만 포함됩니다override
. - 선언에
abstract
한정자가 포함된 경우 선언에는 다음 한정자static
, ,virtual
sealed
또는extern
. - 선언에
private
한정자가 포함된 경우 선언에는 다음 한정자virtual
, 즉override
.abstract
- 선언에 한정자가 포함된
sealed
경우 선언에도 한정자가 포함됩니다override
. - 선언에
partial
한정자가 포함된 경우 , ,,new
,public
protected
internal
private
virtual
sealed
override
또는 .abstract
extern
메서드는 반환되는 항목(있는 경우)에 따라 분류됩니다.
- 있는 경우
ref
메서드는 ref 단위로 반환되고 선택적으로 읽기 전용인 변수 참조를 반환합니다. - 그렇지 않은 경우 return_type
void
메서드는 반환-no-value 및 값을 반환 하지 않습니다; - 그렇지 않으면 메서드가 값 으로 반환되고 값을 반환합니다.
반환 값 또는 반환 없음 메서드 선언의 return_type 메서드에서 반환된 결과 형식(있는 경우)을 지정합니다. returns-no-value 메서드만 한정자(partial
)를 포함 할 수 있습니다. 선언에 한정자가 포함된 async
경우 return_type 또는 void
메서드가 값별로 반환되고 반환 형식이 작업 형식(§15.15.1)이어야 합니다.
return-by-ref 메서드 선언의 ref_return_type 메서드에서 반환된 variable_reference 참조하는 변수의 형식을 지정합니다.
제네릭 메서드는 선언에 type_parameter_list 포함하는 메서드입니다. 메서드의 형식 매개 변수를 지정합니다. 선택적 type_parameter_constraints_clause형식 매개 변수에 대한 제약 조건을 지정합니다.
명시적 인터페이스 멤버 구현에 대한 제네릭 method_declaration type_parameter_constraints_clause없습니다. 선언은 인터페이스 메서드의 제약 조건으로부터 제약 조건을 상속합니다.
마찬가지로 한정자가 있는 override
메서드 선언에는 type_parameter_constraints_clause없으며 메서드 형식 매개 변수의 제약 조건은 재정의되는 가상 메서드에서 상속됩니다.
member_name 메서드의 이름을 지정합니다. 메서드가 명시적 인터페이스 멤버 구현(§18.6.2) 이 아닌 경우 member_name 단순히 식별자입니다.
명시적 인터페이스 멤버 구현의 경우 member_name "" 및 .
. 이 경우 선언에는 (아마도) extern
또는 async
.
선택적 parameter_list 메서드의 매개 변수를 지정합니다(§15.6.2).
return_type 또는 ref_return_type 메서드의 parameter_list 참조되는 각 형식은 적어도 메서드 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
값별 반환 또는 반환 없음 메서드의 method_body 세미콜론, 블록 본문 또는 식 본문입니다. 블록 본문은 메서드가 호출될 때 실행할 문을 지정하는 블록으로 구성됩니다. 식 본문은 =>
null_conditional_invocation_expression 또는 식과 세미콜론으로 구성되며 메서드가 호출될 때 수행할 단일 식을 표시합니다.
추상 및 extern 메서드의 경우 method_body 단순히 세미콜론으로 구성됩니다. 부분 메서드의 경우 method_body 세미콜론, 블록 본문 또는 식 본문으로 구성될 수 있습니다. 다른 모든 메서드의 경우 method_body 블록 본문 또는 식 본문입니다.
method_body 세미콜론으로 구성된 경우 선언에는 한정자가 async
포함되지 않습니다.
return-by-ref 메서드의 ref_method_body 세미콜론, 블록 본문 또는 식 본문입니다. 블록 본문은 메서드가 호출될 때 실행할 문을 지정하는 블록으로 구성됩니다. 식 본문은 뒤에 variable_reference=>
ref
되며 메서드가 호출될 때 평가할 단일 variable_reference 표시합니다.
추상 및 extern 메서드의 경우 ref_method_body 단순히 세미콜론으로 구성됩니다. 다른 모든 메서드 의 경우 ref_method_body 블록 본문 또는 식 본문입니다.
이름, 형식 매개 변수 수 및 메서드의 매개 변수 목록은 메서드의 시그니처(§7.6)를 정의합니다. 특히 메서드의 서명은 이름, 해당 형식 매개 변수 수 및 숫자, parameter_mode_modifier s(§15.6.2.1) 및 해당 매개 변수 형식으로 구성됩니다. 반환 형식은 메서드의 서명에 속하지 않으며 매개 변수의 이름, 형식 매개 변수의 이름 또는 제약 조건이 아닙니다. 매개 변수 형식이 메서드의 형식 매개 변수를 참조하는 경우 형식 매개 변수의 서수 위치(형식 매개 변수의 이름이 아님)가 형식 동등성에 사용됩니다.
메서드의 이름은 동일한 클래스에 선언된 다른 모든 비 메서드의 이름과 다릅니다. 또한 메서드의 서명은 동일한 클래스에 선언된 다른 모든 메서드의 서명과 달라야 하며, 동일한 클래스에 선언된 두 메서드는 , in
및 out
에 의해ref
서만 다른 시그니처를 갖지 않습니다.
메서드의 type_parameter method_declaration전체 범위에 있으며, return_type 또는 ref_return_type, method_body 또는 ref_method_body해당 범위 전체에서 형식을 형성하는 데 사용할 수 있으며 특성에는 type_parameter_constraints_clause없습니다.
모든 매개 변수와 형식 매개 변수의 이름은 서로 다릅니다.
15.6.2 메서드 매개 변수
15.6.2.1 일반
메서드의 매개 변수(있는 경우)는 메서드의 parameter_list 의해 선언됩니다.
parameter_list
: fixed_parameters
| fixed_parameters ',' parameter_array
| parameter_array
;
fixed_parameters
: fixed_parameter (',' fixed_parameter)*
;
fixed_parameter
: attributes? parameter_modifier? type identifier default_argument?
;
default_argument
: '=' expression
;
parameter_modifier
: parameter_mode_modifier
| 'this'
;
parameter_mode_modifier
: 'ref'
| 'out'
| 'in'
;
parameter_array
: attributes? 'params' array_type identifier
;
매개 변수 목록은 하나 이상의 쉼표로 구분된 매개 변수로 구성되며 마지막 매개 변수만 parameter_array 수 있습니다.
fixed_parameter 선택적 특성 집합(§22), 선택적in
, out
, ref
또는 this
한정자, 형식, 식별자 및 선택적 default_argument 구성됩니다. 각 fixed_parameter 지정된 이름의 지정된 형식의 매개 변수를 선언합니다.
this
한정자는 메서드를 확장 메서드로 지정하고 제네릭이 아닌 중첩되지 않은 정적 클래스에서 정적 메서드의 첫 번째 매개 변수에만 허용됩니다. 매개 변수가 struct
형식 또는 형식 매개 변수로 제한되는 struct
this
경우 한정자는 한정자와 결합될 수 있지만 한정자는 결합 ref
in
할 수 없습니다out
. 확장 메서드는 §15.6.10에 자세히 설명되어 있습니다.
default_argument 있는 fixed_parameter 선택적 매개 변수라고 하는 반면 default_argument 없는 fixed_parameter 필수 매개 변수입니다. 필수 매개 변수는 parameter_list 선택적 매개 변수 후에 나타나지 않습니다.
또는 ref
out
한정자가 있는 this
매개 변수는 default_argument 가질 수 없습니다. 입력 매개 변수에 default_argument 있을 수 있습니다. default_argument 식은 다음 중 하나여야 합니다.
- a constant_expression
- 값 형식인 폼
new S()
S
의 식입니다. - 값 형식인 폼
default(S)
S
의 식입니다.
식은 ID로 암시적으로 변환하거나 매개 변수 형식으로 nullable 변환할 수 있어야 합니다.
구현 부분 메서드 선언(§15.6.9)에서 선택적 매개 변수가 발생하는 경우 명시적 인터페이스 멤버 구현(§18.6.2), 단일 매개 변수 인덱서 선언(§) 15.9) 또는 연산자 선언(§15.10.1)에서는 이러한 멤버를 인수를 생략할 수 있는 방식으로 호출할 수 없으므로 컴파일러에서 경고를 제공해야 합니다.
parameter_array 선택적 특성 집합(§22), 한정자, params
및 식별자로 구성됩니다. 매개 변수 배열은 지정된 이름의 지정된 배열 형식의 단일 매개 변수를 선언합니다.
매개 변수 배열의 array_type 1차원 배열 형식(§17.2)이어야 합니다. 메서드 호출에서 매개 변수 배열은 지정된 배열 형식의 단일 인수를 지정하거나 배열 요소 형식의 인수를 0개 이상 지정할 수 있도록 허용합니다. 매개 변수 배열은 §15.6.2.4에 자세히 설명되어 있습니다.
parameter_array 선택적 매개 변수 후에 발생할 수 있지만 기본값을 가질 수 없습니다. parameter_array 인수를 생략하면 빈 배열이 생성됩니다.
예제: 다음은 다양한 종류의 매개 변수를 보여 줍니다.
void M<T>( ref int i, decimal d, bool b = false, bool? n = false, string s = "Hello", object o = null, T t = default(T), params int[] a ) { }
i
필수 매개 변수이고,ref
필수d
값 매개 변수이며b
,s
o
t
선택적 값 매개 변수이며a
매개 변수 배열입니다.끝 예제
메서드 선언은 매개 변수 및 형식 매개 변수에 대해 별도의 선언 공간(§7.3)을 만듭니다. 이름은 형식 매개 변수 목록과 메서드의 매개 변수 목록을 통해 이 선언 공간에 도입됩니다. 메서드의 본문(있는 경우)은 이 선언 공간 내에 중첩된 것으로 간주됩니다. 메서드 선언 공간의 두 멤버가 같은 이름을 갖는 것은 오류입니다.
메서드 호출(§12.8.10.2)은 메서드의 매개 변수 및 지역 변수에 대한 해당 호출과 관련된 복사본을 만들고 호출의 인수 목록은 새로 만든 매개 변수에 값 또는 변수 참조를 할당합니다. 메서드 블록 내에서 매개 변수는 simple_name 식(§12.8.4)의 식별자에서 참조할 수 있습니다.
다음과 같은 종류의 매개 변수가 있습니다.
- 값 매개 변수(§15.6.2.2).
- 입력 매개 변수(§15.6.2.3.2).
- 출력 매개 변수(§15.6.2.3.4).
- 참조 매개 변수(§15.6.2.3.3).
- 매개 변수 배열(§15.6.2.4).
참고: §7.6
in
설명된 대로 ,out
및ref
한정자는 메서드 서명의 일부이지만params
한정자는 그렇지 않습니다. 끝 메모
15.6.2.2 값 매개 변수
한정자 없이 선언된 매개 변수는 값 매개 변수입니다. 값 매개 변수는 메서드 호출에 제공된 해당 인수에서 초기 값을 가져오는 지역 변수입니다.
명확한 할당 규칙은 §9.2.5를 참조하세요.
메서드 호출의 해당 인수는 매개 변수 형식으로 암시적으로 변환할 수 있는 식(§10.2)이어야 합니다.
메서드는 값 매개 변수에 새 값을 할당할 수 있습니다. 이러한 할당은 값 매개 변수가 나타내는 로컬 스토리지 위치에만 영향을 줍니다. 메서드 호출에 지정된 실제 인수에는 영향을 주지 않습니다.
15.6.2.3 참조 매개 변수
15.6.2.3.1 일반
입력, 출력 및 참조 매개 변수는 참조 매개 변수입니다. 참조 기준 매개 변수는 로컬 참조 변수(§9.7)이며, 초기 참조는 메서드 호출에 제공된 해당 인수에서 가져옵니다.
참고: 참조 매개 변수 참조는 ref assignment(
= ref
) 연산자를 사용하여 변경할 수 있습니다.
매개 변수가 참조 매개 변수인 경우 메서드 호출의 해당 인수는 매개 변수와 동일한 형식의 variable_referenceref
해당하는 키워드out
로 구성됩니다. 그러나 매개 변수가 매개 변수인 경우 인수는 in
해당 인수 식에서 해당 매개 변수의 형식으로 암시적 변환(§10.2)이 존재하는 식일 수 있습니다.
반복기(§15.14) 또는 비동기 함수(§15.15)로 선언된 함수에는 참조별 매개 변수가 허용되지 않습니다.
여러 참조 매개 변수를 사용하는 메서드에서는 여러 이름이 동일한 스토리지 위치를 나타낼 수 있습니다.
15.6.2.3.2 입력 매개 변수
한정자를 사용하여 in
선언된 매개 변수는 입력 매개 변수입니다. 입력 매개 변수에 해당하는 인수는 메서드 호출 시점에 존재하는 변수이거나 메서드 호출에서 구현(§12.6.2.3)에 의해 생성된 변수입니다. 명확한 할당 규칙은 §9.2.8을 참조하세요.
입력 매개 변수의 값을 수정하는 것은 컴파일 시간 오류입니다.
참고: 입력 매개 변수의 주요 목적은 효율성을 위한 것입니다. 메서드 매개 변수의 형식이 메모리 요구 사항 측면에서 큰 구조체인 경우 메서드를 호출할 때 인수의 전체 값을 복사하지 않도록 하는 것이 유용합니다. 입력 매개 변수를 사용하면 메서드가 메모리의 기존 값을 참조하는 동시에 해당 값에 대한 원치 않는 변경에 대한 보호를 제공할 수 있습니다. 끝 메모
15.6.2.3.3 참조 매개 변수
한정자를 사용하여 ref
선언된 매개 변수는 참조 매개 변수입니다. 명확한 할당 규칙은 §9.2.6을 참조하세요.
예: 예제
class Test { static void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } static void Main() { int i = 1, j = 2; Swap(ref i, ref j); Console.WriteLine($"i = {i}, j = {j}"); } }
는 출력을 생성합니다.
i = 2, j = 1
in 호출의
Swap
경우 ,Main
를 나타내x
고i
나타냅니다y
.j
따라서 호출은 값과 의 값을i
교환하는 효과가 있습니다j
.끝 예제
예제: 다음 코드에서
class A { string s; void F(ref string a, ref string b) { s = "One"; a = "Two"; b = "Three"; } void G() { F(ref s, ref s); } }
in 호출
F
은 둘 다G
에 대한 참조s
를 전달합니다a
.b
따라서 해당 호출의 경우 이름s
및a
b
모든 항목은 동일한 스토리지 위치를 참조하며, 세 가지 할당은 모두 인스턴스 필드를s
수정합니다.끝 예제
struct
인스턴스 메서드, 인스턴스 접근자(§12.2.1) 또는 생성자 이니셜라이저 this
가 있는 인스턴스 생성자의 경우 키워드는 구조체 형식(§12.8.14)의 참조 매개 변수로 정확하게 동작합니다.
15.6.2.3.4 출력 매개 변수
한정자를 사용하여 out
선언된 매개 변수는 출력 매개 변수입니다. 명확한 할당 규칙은 §9.2.7을 참조하세요.
부분 메서드(§15.6.9)로 선언된 메서드에는 출력 매개 변수가 없습니다.
참고: 출력 매개 변수는 일반적으로 여러 반환 값을 생성하는 메서드에서 사용됩니다. 끝 메모
예제:
class Test { static void SplitPath(string path, out string dir, out string name) { int i = path.Length; while (i > 0) { char ch = path[i - 1]; if (ch == '\\' || ch == '/' || ch == ':') { break; } i--; } dir = path.Substring(0, i); name = path.Substring(i); } static void Main() { string dir, name; SplitPath(@"c:\Windows\System\hello.txt", out dir, out name); Console.WriteLine(dir); Console.WriteLine(name); } }
이 예제에서는 출력을 생성합니다.
c:\Windows\System\ hello.txt
dir
변수 및name
변수는 전달SplitPath
되기 전에 할당되지 않을 수 있으며 호출 후에는 확실히 할당된 것으로 간주됩니다.끝 예제
15.6.2.4 매개 변수 배열
한정자로 선언된 params
매개 변수는 매개 변수 배열입니다. 매개 변수 목록에 매개 변수 배열이 포함된 경우 이 매개 변수는 목록의 마지막 매개 변수여야 하며 1차원 배열 형식이어야 합니다.
예: 형식
string[]
이며string[][]
매개 변수 배열의 형식으로 사용할 수 있지만 형식string[,]
은 사용할 수 없습니다. 끝 예제
참고: 한정자를 한정자와
params
in
결합out
할 수 없습니다. 또는ref
. 끝 메모
매개 변수 배열은 메서드 호출의 두 가지 방법 중 하나로 인수를 지정할 수 있도록 허용합니다.
- 매개 변수 배열에 지정된 인수는 매개 변수 배열 형식으로 암시적으로 변환할 수 있는 단일 식(§10.2)일 수 있습니다. 이 경우 매개 변수 배열은 값 매개 변수처럼 정확하게 작동합니다.
- 또는 호출에서 매개 변수 배열에 대해 0개 이상의 인수를 지정할 수 있습니다. 여기서 각 인수는 매개 변수 배열의 요소 형식으로 암시적으로 변환할 수 있는 식(§10.2)입니다. 이 경우 호출은 인수 수에 해당하는 길이가 있는 매개 변수 배열 형식의 인스턴스를 만들고, 지정된 인수 값을 사용하여 배열 인스턴스의 요소를 초기화하고, 새로 만든 배열 인스턴스를 실제 인수로 사용합니다.
호출에서 가변 개수의 인수를 허용하는 경우를 제외하고 매개 변수 배열은 동일한 형식의 값 매개 변수(§15.6.2.2)와 정확히 동일합니다.
예: 예제
class Test { static void F(params int[] args) { Console.Write($"Array contains {args.Length} elements:"); foreach (int i in args) { Console.Write($" {i}"); } Console.WriteLine(); } static void Main() { int[] arr = {1, 2, 3}; F(arr); F(10, 20, 30, 40); F(); } }
는 출력을 생성합니다.
Array contains 3 elements: 1 2 3 Array contains 4 elements: 10 20 30 40 Array contains 0 elements:
첫 번째 호출
F
은 단순히 배열arr
을 값 매개 변수로 전달합니다. F의 두 번째 호출은 지정된 요소 값이 있는 4개 요소를int[]
자동으로 만들고 해당 배열 인스턴스를 값 매개 변수로 전달합니다. 마찬가지로 세 번째 호출F
은 0 요소를int[]
만들고 해당 인스턴스를 값 매개 변수로 전달합니다. 두 번째 및 세 번째 호출은 쓰기와 정확히 동일합니다.F(new int[] {10, 20, 30, 40}); F(new int[] {});
끝 예제
오버로드 확인을 수행할 때 매개 변수 배열이 있는 메서드는 일반 형식 또는 확장된 형식(§12.6.4.2)에서 적용할 수 있습니다. 메서드의 확장된 형식은 메서드의 일반 형식을 적용할 수 없는 경우에만 사용할 수 있으며 확장된 양식과 서명이 같은 해당 메서드가 동일한 형식으로 선언되지 않은 경우에만 사용할 수 있습니다.
예: 예제
class Test { static void F(params object[] a) => Console.WriteLine("F(object[])"); static void F() => Console.WriteLine("F()"); static void F(object a0, object a1) => Console.WriteLine("F(object,object)"); static void Main() { F(); F(1); F(1, 2); F(1, 2, 3); F(1, 2, 3, 4); } }
는 출력을 생성합니다.
F() F(object[]) F(object,object) F(object[]) F(object[])
이 예제에서는 매개 변수 배열이 있는 메서드의 확장 가능한 두 가지 형태가 이미 클래스에 일반 메서드로 포함되어 있습니다. 따라서 이러한 확장된 양식은 오버로드 확인을 수행할 때 고려되지 않으며, 따라서 첫 번째 및 세 번째 메서드 호출은 일반 메서드를 선택합니다. 클래스가 매개 변수 배열을 사용하여 메서드를 선언하는 경우 일부 확장된 폼을 일반 메서드로 포함하는 것은 드문 일이 아닙니다. 이렇게 하면 매개 변수 배열이 있는 메서드의 확장된 형식이 호출될 때 발생하는 배열 인스턴스의 할당을 방지할 수 있습니다.
끝 예제
배열은 참조 형식이므로 매개 변수 배열에 대해 전달된 값이 될
null
수 있습니다.예: 예제:
class Test { static void F(params string[] array) => Console.WriteLine(array == null); static void Main() { F(null); F((string) null); } }
다음 출력을 생성합니다.
True False
두 번째 호출은 동일한
False
형식으로 생성F(new string[] { null })
되고 단일 null 참조를 포함하는 배열을 전달합니다.끝 예제
매개 변수 배열의 형식이면 object[]
메서드의 정규 형식과 단일 object
매개 변수의 확장된 형식 간에 모호성이 발생할 수 있습니다. 모호성이 object[]
있는 이유는 그 자체가 암시적으로 형식 object
으로 변환할 수 있기 때문입니다. 그러나 필요한 경우 캐스트를 삽입하여 해결할 수 있으므로 모호성은 문제가 되지 않습니다.
예: 예제
class Test { static void F(params object[] args) { foreach (object o in args) { Console.Write(o.GetType().FullName); Console.Write(" "); } Console.WriteLine(); } static void Main() { object[] a = {1, "Hello", 123.456}; object o = a; F(a); F((object)a); F(o); F((object[])o); } }
는 출력을 생성합니다.
System.Int32 System.String System.Double System.Object[] System.Object[] System.Int32 System.String System.Double
첫 번째 및 마지막 호출
F
에서는 인수 형식에서 매개 변수 형식(둘 다 형식F
)으로 암시적 변환이 존재하기 때문에 일반적인 형식object[]
을 적용할 수 있습니다. 따라서 오버로드 확인은 일반 형식을F
선택하고 인수는 일반 값 매개 변수로 전달됩니다. 두 번째 및 세 번째 호출에서는 인수 형식에서 매개 변수 형식(형식F
을 형식으로 암시적으로 변환object
할 수 없음)으로의 암시적 변환이 없기 때문에 일반적인 형식object[]
을 적용할 수 없습니다. 그러나 확장된 형식F
을 적용할 수 있으므로 오버로드 확인으로 선택합니다. 결과적으로 호출을 통해 한 요소가object[]
만들어지고 배열의 단일 요소가 지정된 인수 값(그 자체가 참조object[]
)을 사용하여 초기화됩니다.끝 예제
15.6.3 정적 및 인스턴스 메서드
메서드 선언에 한 static
정자가 포함된 경우 해당 메서드는 정적 메서드라고 합니다. 한정자가 없 static
으면 메서드를 인스턴스 메서드라고 합니다.
정적 메서드는 특정 인스턴스에서 작동하지 않으며 정적 메서드에서 참조 this
하는 컴파일 시간 오류입니다.
인스턴스 메서드는 클래스의 지정된 인스턴스에서 작동하며 해당 인스턴스는 (this
)로 액세스할 수 있습니다.
정적 멤버와 인스턴스 멤버의 차이점은 §15.3.8에서 자세히 설명합니다.
15.6.4 가상 메서드
인스턴스 메서드 선언에 가상 한정자가 포함된 경우 해당 메서드는 가상 메서드라고 합니다. 가상 한정자가 없는 경우 메서드는 가상이 아닌 메서드라고 합니다.
가상이 아닌 메서드의 구현은 고정됩니다. 구현은 메서드가 선언된 클래스의 인스턴스 또는 파생 클래스의 인스턴스에서 호출되는지 여부에 관계없이 동일합니다. 반면, 가상 메서드의 구현은 파생 클래스로 대체될 수 있습니다. 상속된 가상 메서드의 구현을 대체하는 프로세스를 해당 메서드를 재정의하는 것으로 알려져 있습니다(§15.6.5).
가상 메서드 호출 에서 해당 호출이 수행되는 인스턴스의 런타임 형식 은 호출할 실제 메서드 구현을 결정합니다. 가상이 아닌 메서드 호출에서 인스턴스의 컴파일 시간 형식 이 결정 요소입니다. 정확히 말하자면, 컴파일 시간 형식 및 런타임 N
형식A
(파생된 클래스 중 하나 C
또는 클래스)이 있는 R
인스턴스의 R
인수 목록을 C
사용하여 명명 C
된 메서드를 호출하면 호출이 다음과 같이 처리됩니다.
- 바인딩 시 오버로드 확인이 적용
C
되고 ,N
에A
의해 선언되고 상속된 메서드 집합에서 특정 메서드M
를C
선택합니다. 이 내용은 §12.8.10.2에 설명되어 있습니다. - 그런 다음 런타임에 다음을 수행합니다.
- 가상이 아닌 메서드
M
인 경우M
호출됩니다. - 그렇지 않으면
M
가상 메서드이며 가장 파생된 구현M
과 관련하여R
호출됩니다.
- 가상이 아닌 메서드
클래스에서 선언되거나 클래스에서 상속된 모든 가상 메서드에 대해 해당 클래스와 관련하여 메서드의 가장 파생된 구현이 있습니다. 클래스 M
와 관련하여 가상 메서드 R
의 가장 파생된 구현은 다음과 같이 결정됩니다.
- 도입된 가상 선언
R
이 포함된 경우M
이는 다음에 대한 가장 파생된 구현M
R
입니다. - 그렇지 않은 경우 재정의
R
를 포함 하는 경우M
, 다음에 대 한 가장 파생 된 구현M
R
입니다. - 그렇지 않으면 가장 파생된 구현과 관련하여
M
가장 파생된 구현R
은 직접 기본 클래스M
와 관련하여 가장 파생된 구현R
과 동일합니다.
예제: 다음 예제에서는 가상 메서드와 가상이 아닌 메서드 간의 차이점을 보여 줍니다.
class A { public void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public new void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class Test { static void Main() { B b = new B(); A a = b; a.F(); b.F(); a.G(); b.G(); } }
이 예제
A
에서는 가상이 아닌 메서드F
와 가상 메서드G
를 소개합니다. 이 클래스B
는 가상이 아닌 새F
하여 상속된 메서드를 숨기고 상속된F
메서드도G
. 이 예제에서는 출력을 생성합니다.A.F B.F B.G B.G
문
a.G()
은 .가 아니라B.G
호출됩니다A.G
. 이는 인스턴스의 컴파일 시간 형식이 아닌 인스턴스의 런타임 형식(즉,)이 호출할 실제 메서드 구현을 결정하기 때문입니다B
A
.끝 예제
메서드는 상속된 메서드를 숨길 수 있으므로 클래스에 동일한 서명이 있는 여러 가상 메서드를 포함할 수 있습니다. 가장 파생된 메서드를 제외한 모든 메서드가 숨겨져 있으므로 모호성 문제가 발생하지 않습니다.
예제: 다음 코드에서
class A { public virtual void F() => Console.WriteLine("A.F"); } class B : A { public override void F() => Console.WriteLine("B.F"); } class C : B { public new virtual void F() => Console.WriteLine("C.F"); } class D : C { public override void F() => Console.WriteLine("D.F"); } class Test { static void Main() { D d = new D(); A a = d; B b = d; C c = d; a.F(); b.F(); c.F(); d.F(); } }
C
및D
클래스에는 동일한 서명이 있는 두 개의 가상 메서드가 포함됩니다. 하나는 도입된 메서드이고 다른 하나는 에 의해A
C
도입되었습니다. 에 의해C
도입된 메서드는 상속된A
메서드를 숨깁니다. 따라서 재정의 선언은D
다음에 의해 도입된 메서드를 재정의하며, 에 의해C
D
도입된 메서드를A
재정의할 수 없습니다. 이 예제에서는 출력을 생성합니다.B.F B.F D.F D.F
메서드가 숨겨지지 않은 덜 파생된 형식을 통해 인스턴스
D
에 액세스하여 숨겨진 가상 메서드를 호출할 수 있습니다.끝 예제
15.6.5 메서드 재정의
인스턴스 메서드 선언에 한 override
정자가 포함된 경우 메서드는 재정의 메서드라고 합니다. 재정의 메서드는 동일한 서명을 사용하여 상속된 가상 메서드를 재정의합니다. 가상 메서드 선언 은 새 메서드를 도입하는 반면, 재정의 메서드 선언 은 해당 메서드의 새 구현을 제공하여 기존 상속된 가상 메서드를 특수화합니다.
재정의 선언에 의해 재정의된 메서드를 재정의된 기본 메서드라고 합니다C
의 경우 재정의된 기본 메서드는 각 기본 클래스를 C
검사하여 결정됩니다. 즉, 지정된 기본 클래스 C
형식에서 형식 인수를 대체한 후와 동일한 시그니처가 있는 액세스 가능한 메서드가 하나 이상 위치할 때까지 각 연속된 직접 기본 클래스로 시작하고 각 연속된 직접 기본 클래스를 M
계속합니다. 재정의된 기본 메서드를 찾기 위해 메서드가 있는 경우, 메서드가 있는 경우public
, 메서드가 있는 경우 protected
protected internal
또는 같은 프로그램에서 선언되거나 internal
선언된 경우 private protected
액세스할 수 있는 것으로 C
간주됩니다.
재정의 선언에 대해 다음이 모두 true가 아닌 경우 컴파일 시간 오류가 발생합니다.
- 재정의된 기본 메서드는 위에서 설명한 대로 배치할 수 있습니다.
- 이러한 재정의된 기본 메서드는 정확히 하나 있습니다. 이 제한은 기본 클래스 형식이 생성된 형식인 경우에만 적용됩니다. 여기서 형식 인수를 대체하면 두 메서드의 시그니처가 동일합니다.
- 재정의된 기본 메서드는 가상, 추상 또는 재정의 메서드입니다. 즉, 재정의된 기본 메서드는 정적 또는 가상이 아닌 메서드일 수 없습니다.
- 재정의된 기본 메서드는 봉인된 메서드가 아닙니다.
- 재정의된 기본 메서드의 반환 형식과 재정의 메서드 간에 ID 변환이 있습니다.
- 재정의 선언과 재정의된 기본 메서드는 동일한 선언된 접근성을 갖습니다. 즉, 재정의 선언은 가상 메서드의 접근성을 변경할 수 없습니다. 그러나 재정의된 기본 메서드가 내부적으로 보호되고 재정의 선언을 포함하는 어셈블리와 다른 어셈블리에 선언된 경우 재정의 선언의 선언된 접근성이 보호됩니다.
- 재정의 선언은 type_parameter_constraints_clause지정하지 않습니다. 대신 제약 조건은 재정의된 기본 메서드에서 상속됩니다. 재정의된 메서드의 형식 매개 변수인 제약 조건은 상속된 제약 조건의 형식 인수로 대체될 수 있습니다. 이렇게 하면 값 형식 또는 봉인된 형식과 같이 명시적으로 지정할 때 유효하지 않은 제약 조건이 발생할 수 있습니다.
예제: 다음은 제네릭 클래스에 대해 재정의 규칙이 작동하는 방식을 보여 줍니다.
abstract class C<T> { public virtual T F() {...} public virtual C<T> G() {...} public virtual void H(C<T> x) {...} } class D : C<string> { public override string F() {...} // Ok public override C<string> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<string> } class E<T,U> : C<U> { public override U F() {...} // Ok public override C<U> G() {...} // Ok public override void H(C<T> x) {...} // Error, should be C<U> }
끝 예제
재정의 선언은 base_access(§12.8.15)를 사용하여 재정의된 기본 메서드에 액세스할 수 있습니다.
예제: 다음 코드에서
class A { int x; public virtual void PrintFields() => Console.WriteLine($"x = {x}"); } class B : A { int y; public override void PrintFields() { base.PrintFields(); Console.WriteLine($"y = {y}"); } }
base.PrintFields()
호출에서B
PrintFields 메서드A
를 호출 합니다. base_access 가상 호출 메커니즘을 사용하지 않도록 설정하고 기본 메서드를 비 메서드virtual
로 처리합니다. 호출B
이 기록((A)this).PrintFields()
된 경우 가상이고 런타임 형식PrintFields
B
이기 때문에A
선언된PrintFields
메서드가 아니라 선언된((A)this)
메서드를 재귀적으로 호출B
합니다.끝 예제
한정자를 override
포함해야만 메서드가 다른 메서드를 재정의할 수 있습니다. 다른 모든 경우에서 상속된 메서드와 시그니처가 같은 메서드는 상속된 메서드를 숨기기만 하면 됩니다.
예제: 다음 코드에서
class A { public virtual void F() {} } class B : A { public virtual void F() {} // Warning, hiding inherited F() }
의 메서드는
F
B
한override
정자를 포함하지 않으므로 메서드를 재정의F
A
하지 않습니다. 대신 메서드를F
B
숨기면 선언에A
새 한정자가 포함되지 않으므로 경고가 보고됩니다.끝 예제
예제: 다음 코드에서
class A { public virtual void F() {} } class B : A { private new void F() {} // Hides A.F within body of B } class C : B { public override void F() {} // Ok, overrides A.F }
의 메서드는
F
.에서B
F
상속된 가상A
메서드를 숨깁니다. newF
inB
에는 프라이빗 액세스 권한이 있으므로 해당 범위에는 클래스 본문B
만 포함되며 확장C
되지 않습니다. 따라서 inF
선언은 상속된C
을F
재정의A
할 수 있습니다.끝 예제
15.6.6 Sealed 메서드
인스턴스 메서드 선언에 한 sealed
정자가 포함된 경우 해당 메서드는 봉인된 메서드라고 합니다. 봉인된 메서드는 상속된 가상 메서드를 동일한 서명으로 재정의합니다. 봉인된 메서드도 한정자를 사용하여 override
표시해야 합니다. 한정자를 사용하면 파생 클래스가 메서드를 추가로 재정의 sealed
할 수 없게 됩니다.
예: 예제
class A { public virtual void F() => Console.WriteLine("A.F"); public virtual void G() => Console.WriteLine("A.G"); } class B : A { public sealed override void F() => Console.WriteLine("B.F"); public override void G() => Console.WriteLine("B.G"); } class C : B { public override void G() => Console.WriteLine("C.G"); }
클래스
B
는 두 가지 재정의 메서드를 제공합니다. 한F
정자가 있는sealed
메서드와G
그렇지 않은 메서드입니다.B
'의 한정자를 사용하면 더 이상 재정의sealed
할 수 없습니다C
F
.끝 예제
15.6.7 추상 메서드
인스턴스 메서드 선언에 한 abstract
정자가 포함된 경우 해당 메서드는 추상 메서드라고 합니다. 추상 메서드는 암시적으로도 가상 메서드이지만 한정자를 virtual
가질 수 없습니다.
추상 메서드 선언은 새 가상 메서드를 도입하지만 해당 메서드의 구현을 제공하지는 않습니다. 대신 비 추상 파생 클래스는 해당 메서드를 재정의하여 고유한 구현을 제공해야 합니다. 추상 메서드는 실제 구현을 제공하지 않으므로 추상 메서드의 메서드 본문은 단순히 세미콜론으로 구성됩니다.
추상 메서드 선언은 추상 클래스(§15.2.2.2)에서만 허용됩니다.
예제: 다음 코드에서
public abstract class Shape { public abstract void Paint(Graphics g, Rectangle r); } public class Ellipse : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawEllipse(r); } public class Box : Shape { public override void Paint(Graphics g, Rectangle r) => g.DrawRect(r); }
클래스는
Shape
자신을 그릴 수 있는 기하 도형 개체의 추상 개념을 정의합니다.Paint
의미 있는 기본 구현이 없으므로 메서드는 추상적입니다.Ellipse
클래스 및Box
클래스는 구체적인Shape
구현입니다. 이러한 클래스는 추상이 아니므로 메서드를 재정의Paint
하고 실제 구현을 제공해야 합니다.끝 예제
추상 메서드를 참조하는 base_access(§12.8.15)의 컴파일 시간 오류입니다.
예제: 다음 코드에서
abstract class A { public abstract void F(); } class B : A { // Error, base.F is abstract public override void F() => base.F(); }
컴파일 시간 오류는 추상 메서드를
base.F()
참조하기 때문에 호출에 대해 보고됩니다.끝 예제
추상 메서드 선언은 가상 메서드를 재정의할 수 있습니다. 이렇게 하면 추상 클래스가 파생 클래스에서 메서드를 강제로 다시 구현할 수 있으며 메서드의 원래 구현을 사용할 수 없게 됩니다.
예제: 다음 코드에서
class A { public virtual void F() => Console.WriteLine("A.F"); } abstract class B: A { public abstract override void F(); } class C : B { public override void F() => Console.WriteLine("C.F"); }
클래스
A
는 가상 메서드를 선언하고, 클래스B
는 추상 메서드로 이 메서드를 재정의하고, 클래스C
는 추상 메서드를 재정의하여 자체 구현을 제공합니다.끝 예제
15.6.8 외부 메서드
메서드 선언에 한 extern
정자가 포함된 경우 메서드는 외부 메서드라고 합니다. 외부 메서드는 일반적으로 C#이 아닌 언어를 사용하여 외부에서 구현됩니다. 외부 메서드 선언은 실제 구현을 제공하지 않으므로 외부 메서드의 메서드 본문은 단순히 세미콜론으로 구성됩니다. 외부 메서드는 제네릭이 아니어야 합니다.
외부 메서드에 대한 연결을 달성하는 메커니즘은 구현에서 정의됩니다.
예제: 다음 예제에서는 한정자와 특성을 사용하는
extern
방법을DllImport
보여 줍니다.class Path { [DllImport("kernel32", SetLastError=true)] static extern bool CreateDirectory(string name, SecurityAttribute sa); [DllImport("kernel32", SetLastError=true)] static extern bool RemoveDirectory(string name); [DllImport("kernel32", SetLastError=true)] static extern int GetCurrentDirectory(int bufSize, StringBuilder buf); [DllImport("kernel32", SetLastError=true)] static extern bool SetCurrentDirectory(string name); }
끝 예제
15.6.9 Partial 메서드
메서드 선언에 한 partial
정자가 포함된 경우 해당 메서드는 부분 메서드라고 합니다. Partial 메서드는 partial 형식(§15.2.7)의 멤버로만 선언할 수 있으며 여러 제한 사항이 적용됩니다.
부분 메서드는 형식 선언의 한 부분에서 정의되고 다른 부분에서 구현될 수 있습니다. 구현은 선택 사항입니다. 부분 메서드를 구현하는 파트가 없으면 부분 메서드 선언과 해당 부분의 모든 호출이 부분의 조합으로 인해 형식 선언에서 제거됩니다.
부분 메서드는 액세스 한정자를 정의하지 않습니다. 암시적으로 비공개입니다. 반환 형식은 void
출력 매개 변수가 아니어야 합니다. 식별자 partial은 키워드 바로 앞에 나타나는 경우에만 메서드 선언에서 컨텍스트 키워드(void
)로 인식됩니다. partial 메서드는 인터페이스 메서드를 명시적으로 구현할 수 없습니다.
부분 메서드 선언에는 두 가지 종류가 있습니다. 메서드 선언의 본문이 세미콜론인 경우 선언은 부분 메서드 선언을 정의하는 것이라고 합니다. 본문이 세미콜론이 아닌 경우 선언은 구현 부분 메서드 선언이라고 합니다. 형식 선언의 일부에서 지정된 시그니처를 사용하여 partial 메서드 선언을 정의하는 것은 하나뿐일 수 있으며 지정된 시그니처를 사용하여 partial 메서드 선언을 구현하는 메서드 선언은 하나만 있을 수 있습니다. 구현 부분 메서드 선언이 지정된 경우 해당 부분 메서드 선언을 정의해야 하며 선언은 다음에 지정된 대로 일치해야 합니다.
- 선언에는 동일한 한정자(반드시 같은 순서는 아니지만), 메서드 이름, 형식 매개 변수 수 및 매개 변수 수가 있어야 합니다.
- 선언의 해당 매개 변수에는 동일한 한정자(반드시 동일한 순서는 아니지만)와 동일한 형식 또는 ID 변환 가능 형식(형식 매개 변수 이름의 모듈로 차이)이 있어야 합니다.
- 선언의 해당 형식 매개 변수는 동일한 제약 조건(형식 매개 변수 이름의 모듈로 차이)을 가져야 합니다.
부분 메서드 선언 구현은 해당 부분 메서드 선언을 정의하는 부분 메서드 선언과 동일한 부분에 나타날 수 있습니다.
부분 메서드 정의만 오버로드 확인에 참여합니다. 따라서 구현 선언이 제공되었는지 여부에 관계없이 호출 식은 partial 메서드의 호출로 확인될 수 있습니다. partial 메서드는 항상 반환 void
되므로 이러한 호출 식은 항상 식 문입니다. 또한 partial 메서드는 암시적으로 private
있기 때문에 해당 문은 항상 partial 메서드가 선언되는 형식 선언의 일부 내에서 발생합니다.
참고: 부분 메서드 선언을 정의하고 구현하는 일치 정의에서는 매개 변수 이름을 일치시킬 필요가 없습니다. 이는 명명된 인수(§12.6.2.1)가 사용될 때 잘 정의되었지만 놀라운 동작을 생성할 수 있습니다. 예를 들어 한 파일에서 부분 메서드 선언
M
을 정의하고 다른 파일에서 partial 메서드 선언을 구현하는 경우는 다음과 같습니다.// File P1.cs: partial class P { static partial void M(int x); } // File P2.cs: partial class P { static void Caller() => M(y: 0); static partial void M(int y) {} }
는 호출이 부분 메서드 선언을 정의하는 것이 아니라 구현의 인수 이름을 사용하기 때문에 유효 하지 않습니다.
끝 메모
partial 형식 선언의 일부가 지정된 partial 메서드에 대한 구현 선언을 포함하지 않는 경우 이를 호출하는 식 문은 결합된 형식 선언에서 단순히 제거됩니다. 따라서 하위 식이 포함된 호출 식은 런타임에 영향을 주지 않습니다. 부분 메서드 자체도 제거되며 결합된 형식 선언의 멤버가 되지 않습니다.
지정된 partial 메서드에 대한 구현 선언이 있는 경우 partial 메서드의 호출은 유지됩니다. partial 메서드는 다음을 제외하고 구현하는 partial 메서드 선언과 유사한 메서드 선언을 발생합니다.
한
partial
정자는 포함되지 않습니다.결과 메서드 선언의 특성은 정의의 결합된 특성과 지정되지 않은 순서로 부분 메서드 선언을 구현하는 특성입니다. 중복 항목은 제거되지 않습니다.
결과 메서드 선언의 매개 변수에 대한 특성은 정의에 해당하는 매개 변수의 결합된 특성과 지정되지 않은 순서로 부분 메서드 선언을 구현하는 특성입니다. 중복 항목은 제거되지 않습니다.
부분 메서드 M
에 대해 구현 선언이 아닌 정의 선언을 지정하는 경우 다음 제한 사항이 적용됩니다.
(§12.8.17.6)에서
M
대리자를 만드는 것은 컴파일 시간 오류입니다.호출
M
의 일부로 발생하는 식은 컴파일 시간 오류로 이어질 수 있는 명확한 할당 상태(§9.4)에 영향을 미치지 않습니다.M
애플리케이션의 진입점이 될 수 없습니다(§7.1).
부분 메서드는 형식 선언의 한 부분이 다른 부분(예: 도구에서 생성되는 부분)의 동작을 사용자 지정하도록 허용하는 데 유용합니다. 다음 부분 클래스 선언을 고려합니다.
partial class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
partial void OnNameChanging(string newName);
partial void OnNameChanged();
}
이 클래스가 다른 부분 없이 컴파일되는 경우 부분 메서드 선언과 해당 호출을 정의하는 것이 제거되고 결과 결합 클래스 선언은 다음과 같습니다.
class Customer
{
string name;
public string Name
{
get => name;
set => name = value;
}
}
그러나 부분 메서드의 구현 선언을 제공하는 다른 부분이 있다고 가정합니다.
partial class Customer
{
partial void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
partial void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
그런 다음 결과 결합된 클래스 선언은 다음과 같습니다.
class Customer
{
string name;
public string Name
{
get => name;
set
{
OnNameChanging(value);
name = value;
OnNameChanged();
}
}
void OnNameChanging(string newName) =>
Console.WriteLine($"Changing {name} to {newName}");
void OnNameChanged() =>
Console.WriteLine($"Changed to {name}");
}
15.6.10 Extension 메서드
메서드의 첫 번째 매개 변수에 한정자가 포함된 this
경우 해당 메서드는 확장 메서드라고 합니다. 확장 메서드는 제네릭이 아닌 중첩되지 않은 정적 클래스에서만 선언되어야 합니다. 확장 메서드의 첫 번째 매개 변수는 다음과 같이 제한됩니다.
- 값 형식이 있는 경우에만 입력 매개 변수일 수 있습니다.
- 값 형식이 있거나 구조체로 제한되는 제네릭 형식이 있는 경우에만 참조 매개 변수일 수 있습니다.
- 포인터 형식이 아니어야 합니다.
예제: 다음은 두 개의 확장 메서드를 선언하는 정적 클래스의 예입니다.
public static class Extensions { public static int ToInt32(this string s) => Int32.Parse(s); public static T[] Slice<T>(this T[] source, int index, int count) { if (index < 0 || count < 0 || source.Length - index < count) { throw new ArgumentException(); } T[] result = new T[count]; Array.Copy(source, index, result, 0, count); return result; } }
끝 예제
확장 메서드는 일반 정적 메서드입니다. 또한 바깥쪽 정적 클래스가 범위에 있는 경우 수신기 식을 첫 번째 인수로 사용하여 인스턴스 메서드 호출 구문(§12.8.10.3)을 사용하여 확장 메서드를 호출할 수 있습니다.
예: 다음 프로그램은 위에 선언된 확장 메서드를 사용합니다.
static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in strings.Slice(1, 2)) { Console.WriteLine(s.ToInt32()); } } }
메서드는
Slice
확장 메서드로 선언되었으므로 메서드에서string[]
사용할ToInt32
수 있습니다string
. 프로그램의 의미는 일반적인 정적 메서드 호출을 사용하여 다음과 같습니다.static class Program { static void Main() { string[] strings = { "1", "22", "333", "4444" }; foreach (string s in Extensions.Slice(strings, 1, 2)) { Console.WriteLine(Extensions.ToInt32(s)); } } }
끝 예제
15.6.11 메서드 본문
메서드 선언의 메서드 본문은 블록 본문, 식 본문 또는 세미콜론으로 구성됩니다.
추상 및 외부 메서드 선언은 메서드 구현을 제공하지 않으므로 메서드 본문은 단순히 세미콜론으로 구성됩니다. 다른 메서드의 경우 메서드 본문은 해당 메서드가 호출될 때 실행할 문이 포함된 블록(§13.3)입니다.
메서드의 유효 반환 형식«TaskType»
그렇지 않으면 비동기 메서드의 유효 반환 형식은 반환 형식이며 반환 형식(«TaskType»<T>
)이 있는 비동기 메서드의 유효 반환 형식은 반환 형식입니다T
.
메서드의 유효 반환 형식이 void
있고 메서드에 블록 본문 return
이 있는 경우 블록의 문(§13.10.5)은 식을 지정하지 않습니다. void 메서드의 블록 실행이 정상적으로 완료되면(즉, 컨트롤이 메서드 본문의 끝에서 흐른 경우) 해당 메서드는 호출자에게만 반환됩니다.
메서드의 유효 반환 형식이 void
메서드에 식 본문이 있는 경우 식 E
은 statement_expression 되며 본문은 폼 { E; }
의 블록 본문과 정확히 동일합니다.
값별 반환 메서드(§15.6.1)의 경우 해당 메서드 본문의 각 반환 문은 유효 반환 형식으로 암시적으로 변환할 수 있는 식을 지정해야 합니다.
return-by-ref 메서드(§15.6.1)의 경우 해당 메서드 본문의 각 반환 문은 해당 형식이 유효 반환 형식이고 호출자 컨텍스트의 ref-safe-context (§9.7.2)를 갖는 식을 지정해야 합니다.
값별 반환 및 return-by-ref 메서드의 경우 메서드 본문의 엔드포인트에 연결할 수 없습니다. 즉, 컨트롤이 메서드 본문의 끝에서 흐를 수 없습니다.
예제: 다음 코드에서
class A { public int F() {} // Error, return value required public int G() { return 1; } public int H(bool b) { if (b) { return 1; } else { return 0; } } public int I(bool b) => b ? 1 : 0; }
값 반환
F
메서드는 컨트롤이 메서드 본문의 끝에서 흐를 수 있으므로 컴파일 시간 오류가 발생합니다.G
가능한 모든 실행 경로가 반환 값을 지정하는 return 문으로 끝나기 때문에 메서드와H
메서드는 정확합니다. 메서드는I
본문이 단일 반환 문이 있는 블록과 동일하기 때문에 올바르습니다.끝 예제
15.7 속성
15.7.1 일반
속성은 개체 또는 클래스의 특성에 대한 액세스를 제공하는 멤버입니다. 속성의 예로는 문자열의 길이, 글꼴 크기, 창 캡션 및 고객 이름이 있습니다. 속성은 필드의 자연스러운 확장입니다. 둘 다 연결된 형식의 명명된 멤버이며 필드 및 속성에 액세스하기 위한 구문은 동일합니다. 그러나 필드와 달리 속성은 스토리지 위치를 명시하지 않습니다. 대신, 속성에는 해당 값을 읽거나 쓸 때 실행될 문을 지정하는 접근자가 있습니다. 따라서 속성은 개체 또는 클래스 특성의 읽기 및 쓰기와 작업을 연결하기 위한 메커니즘을 제공합니다. 또한 이러한 특성을 계산할 수 있습니다.
속성은 property_declaration사용하여 선언됩니다.
property_declaration
: attributes? property_modifier* type member_name property_body
| attributes? property_modifier* ref_kind type member_name ref_property_body
;
property_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
property_body
: '{' accessor_declarations '}' property_initializer?
| '=>' expression ';'
;
property_initializer
: '=' variable_initializer ';'
;
ref_property_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
두 가지 종류의 property_declaration 있습니다.
- 첫 번째는 참조 값이 아닌 속성을 선언합니다. 해당 값의 형식은 형식 입니다. 이러한 종류의 속성은 읽기 및/또는 쓰기 가능할 수 있습니다.
- 두 번째는 ref-valued 속성을 선언합니다. 해당 값은 형식의 변수에 대한 variable_reference(
readonly
)입니다. 이러한 종류의 속성은 읽기 전용입니다.
property_declaration 특성 집합(§22) 및 허용된 종류의 선언된 접근성(§15.3.6), (§15.3.5), new
(§15.7.2virtual
, §15.7.6), override
(§15.6.5, §15.7.6), sealed
(§15.6.6), abstract
(§15.6.7, §15.7.6) 및 extern
(§15.6.8) 한정자.
속성 선언에는 유효한 한정자 조합과 관련하여 메서드 선언(§15.6)과 동일한 규칙이 적용됩니다.
member_name(§15.6.1)는 속성의 이름을 지정합니다. 속성이 명시적 인터페이스 멤버 구현 이 아니면 member_name 단순히 식별자입니다. 명시적 인터페이스 멤버 구현(§18.6.2)의 경우 member_name interface_type 뒤에 ".
"와 식별자로 구성됩니다.
속성의 유형은 적어도 속성 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
property_body 문 본문 또는 식 본문으로 구성됩니다. 문 본문}
속성의 접근자(§15.7.3)를 선언합니다. 접근자가 속성 읽기 및 쓰기와 관련된 실행 문을 지정합니다.
property_body 식과 세미콜론으로 구성된 =>
E
식 본문은 문 본문{ get { return E; } }
과 정확히 동일하므로 get 접근자의 결과가 단일 식으로 제공되는 읽기 전용 속성을 지정하는 데만 사용할 수 있습니다.
자동으로 구현된 속성(§15.7.4)에 대해서만 property_initializer 제공될 수 있으며 이러한 속성의 기본 필드가 식에서 지정한 값으로 초기화됩니다.
ref_property_body 문 본문 또는 식 본문으로 구성 될 수 있습니다. 문 본문 에서 get_accessor_declaration 속성의 get 접근자(§15.7.3)를 선언합니다. 접근자가 속성 읽기와 관련된 실행 문을 지정합니다.
ref_property_body 뒤에 variable_reference =>
ref
및 세미콜론으로 구성된 V
식 본문은 문 본문{ get { return ref V; } }
과 정확히 동일합니다.
참고: 속성에 액세스하기 위한 구문이 필드의 구문과 동일하더라도 속성은 변수로 분류되지 않습니다. 따라서 속성이 ref-valued이므로 변수 참조(
in
)를 반환하지 않는 한 속성을 또는 인수로out
ref
전달할 수 없습니다. 끝 메모
속성 선언에 한정자가 extern
포함된 경우 속성은 외부 속성이라고 합니다. 외부 속성 선언은 실제 구현을 제공하지 않으므로 accessor_declarations 각 accessor_body 세미콜론이어야 합니다.
15.7.2 정적 및 인스턴스 속성
속성 선언에 한정자가 static
포함된 경우 속성은 정적 속성이라고 합니다. 한정자가 없 static
으면 속성은 인스턴스 속성이라고 합니다.
정적 속성은 특정 인스턴스와 연결되지 않으며 정적 속성의 접근자에서 참조하는 this
컴파일 시간 오류입니다.
인스턴스 속성은 클래스의 지정된 인스턴스와 연결되며 해당 속성의 접근자에서 해당 인스턴스에 (this
)로 액세스할 수 있습니다.
정적 멤버와 인스턴스 멤버의 차이점은 §15.3.8에서 자세히 설명합니다.
15.7.3 접근자
참고: 이 절은 속성(§15.7) 및 인덱서(§15.9)에 모두 적용됩니다. 이 절은 속성 측면에서 작성되며, 인덱서를 읽을 때 속성/속성에 대한 인덱서/인덱서를 대체하고 §15.9.2에 지정된 속성과 인덱서 간의 차이점 목록을 참조합니다. 끝 메모
속성의 accessor_declarations 해당 속성 작성 및/또는 읽기와 관련된 실행 문을 지정합니다.
accessor_declarations
: get_accessor_declaration set_accessor_declaration?
| set_accessor_declaration get_accessor_declaration?
;
get_accessor_declaration
: attributes? accessor_modifier? 'get' accessor_body
;
set_accessor_declaration
: attributes? accessor_modifier? 'set' accessor_body
;
accessor_modifier
: 'protected'
| 'internal'
| 'private'
| 'protected' 'internal'
| 'internal' 'protected'
| 'protected' 'private'
| 'private' 'protected'
;
accessor_body
: block
| '=>' expression ';'
| ';'
;
ref_get_accessor_declaration
: attributes? accessor_modifier? 'get' ref_accessor_body
;
ref_accessor_body
: block
| '=>' 'ref' variable_reference ';'
| ';'
;
accessor_declarations get_accessor_declaration, set_accessor_declaration 또는 둘 다로 구성됩니다. 각 접근자 선언은 선택적 특성, 선택적 accessor_modifier, 토큰 get
또는 set
accessor_body 구성됩니다.
ref-valued 속성의 경우 ref_get_accessor_declaration 선택적 특성, 선택적 accessor_modifier, 토큰 get
, ref_accessor_body 구성됩니다.
accessor_modifier사용은 다음과 같은 제한 사항이 적용됩니다.
- accessor_modifier 인터페이스 또는 명시적 인터페이스 멤버 구현에서 사용할 수 없습니다.
- 한정자가 없는
override
속성 또는 인덱서의 경우 속성 또는 인덱서에 get 및 set 접근자가 모두 있고 해당 접근자 중 하나에서만 허용되는 경우에만 accessor_modifier 허용됩니다. - 한정자를 포함하는
override
속성 또는 인덱서의 경우 접근자는 재정의되는 접근자의 accessor_modifier 일치해야 합니다. -
accessor_modifier 속성 또는 인덱서 자체의 선언된 접근성보다 엄격하게 더 제한적인 접근성을 선언해야 합니다. 정확하게 말하면 다음을 수행합니다.
- 속성 또는 인덱서에 선언된 접근성
public
이 있는 경우 accessor_modifier 선언된 접근성은private protected
,protected internal
,internal
protected
또는private
. - 속성 또는 인덱서에 선언된 접근성
protected internal
이 있는 경우 accessor_modifier 선언된 접근성은private protected
,protected private
,internal
protected
또는private
. - 속성 또는 인덱서의 접근성
internal
이 선언되었거나protected
, accessor_modifierprivate
접근성은 - 속성 또는 인덱서에 선언된 접근성
private protected
이 있는 경우 accessor_modifier 선언된 접근성은 다음과 입니다private
. - 속성 또는 인덱서에 선언된 접근성이
private
있는 경우 accessor_modifier 사용할 수 없습니다.
- 속성 또는 인덱서에 선언된 접근성
참조 값이 아닌 속성의 abstract
경우 extern
지정된 각 접근자에 대한 모든 accessor_body 단순히 세미콜론입니다. 인덱서가 아닌 비 추상 속성은 세미콜론으로 지정된 모든 접근자에 대한 accessor_body 가질 수도 있습니다. 이 경우 자동으로 구현된 속성(§15.7.4)입니다. 자동으로 구현된 속성에는 최소한 get 접근자가 있어야 합니다. 다른 비 추상적이 아닌 extern 속성 의 접근자의 경우 accessor_body 중 하나입니다.
- 해당 접근자가 호출될 때 실행할 문을 지정하는 블록입니다.
- 식 본문은 식과 세미콜론으로
=>
이루어져 있으며 해당 접근자가 호출될 때 실행할 단일 식을 표시합니다.
참조 값 속성의 abstract
경우 extern
ref_accessor_body 단순히 세미콜론입니다. 다른 비 추상적이 아닌 extern 속성의 접근자의 경우 ref_accessor_body 다음 중 하나입니다.
- get 접근자가 호출될 때 실행할 문을 지정하는 블록입니다.
- 뒤에 variable_reference
=>
및 세미콜론으로ref
구성된 식 본문입니다. 변수 참조는 get 접근자가 호출될 때 평가됩니다.
참조 값이 아닌 속성의 get 접근자가 속성 형식의 반환 값을 가진 매개 변수가 없는 메서드에 해당합니다. 할당의 대상으로 제외하면 이러한 속성이 식에서 참조되는 경우 get 접근자가 호출되어 속성 값(§12.2.2)을 계산합니다.
참조 값이 아닌 속성에 대한 get 접근자의 본문은 §15.6.11에 설명된 값 반환 메서드에 대한 규칙을 준수해야 합니다. 특히 get 접근자의 본문에 있는 모든 return
문은 속성 형식으로 암시적으로 변환할 수 있는 식을 지정해야 합니다. 또한 get 접근자의 엔드포인트에 연결할 수 없습니다.
ref-valued 속성의 get 접근자가 속성 형식의 변수에 대한 variable_reference 반환 값이 있는 매개 변수 없는 메서드에 해당합니다. 이러한 속성이 식에서 참조되면 get 접근자가 호출되어 속성의 variable_reference 값을 계산합니다. 그 변수 참조는 다른 변수와 마찬가지로 읽기 전용 이 아닌 variable_reference컨텍스트에서 요구하는 대로 참조된 변수를 읽거나 쓰는 데 사용됩니다.
예제: 다음 예제에서는 참조 값 속성을 할당의 대상으로 보여 줍니다.
class Program { static int field; static ref int Property => ref field; static void Main() { field = 10; Console.WriteLine(Property); // Prints 10 Property = 20; // This invokes the get accessor, then assigns // via the resulting variable reference Console.WriteLine(field); // Prints 20 } }
끝 예제
ref-valued 속성에 대한 get 접근자의 본문은 §15.6.11에 설명된 ref-valued 메서드에 대한 규칙을 준수해야 합니다.
set 접근자가 속성 형식과 반환 형식의 단일 값 매개 변수를 사용하는 메서드에 void
해당합니다. set 접근자의 암시적 매개 변수는 항상 이름이 지정 value
됩니다. 속성이 할당 대상(§12.21) 또는 피연산자++
(–-
§12.8.16, §12.9.6)로 참조되는 경우 set 접근자는 새 값(§12.21.2)을 제공하는 인수를 사용하여 호출됩니다. 집합 접근자의 본문은 §15.6.11void
설명된 메서드에 대한 규칙을 준수해야 합니다. 특히 set 접근자 본문의 return 문은 식을 지정할 수 없습니다. set 접근자에는 암시적으로 명명 value
된 매개 변수가 있으므로 해당 이름을 갖는 집합 접근자의 지역 변수 또는 상수 선언에 대한 컴파일 시간 오류입니다.
get 및 set 접근자의 존재 여부 또는 부재에 따라 속성은 다음과 같이 분류됩니다.
- get 접근자와 set 접근자를 모두 포함하는 속성은 읽기/쓰기 속성이라고 합니다.
- get 접근자만 있는 속성은 읽기 전용 속성이라고 합니다. 읽기 전용 속성이 할당의 대상이 되는 것은 컴파일 시간 오류입니다.
- set 접근자만 있는 속성은 쓰기 전용 속성이라고 합니다. 할당의 대상으로 제외하면 식에서 쓰기 전용 속성을 참조하는 것은 컴파일 시간 오류입니다.
참고: 이러한 연산자는 새 연산자를 쓰기 전에 피연산자의 이전 값을 읽기 때문에 접
++
두사 및--
연산자 및 복합 할당 연산자를 쓰기 전용 속성에 적용할 수 없습니다. 끝 메모
예제: 다음 코드에서
public class Button : Control { private string caption; public string Caption { get => caption; set { if (caption != value) { caption = value; Repaint(); } } } public override void Paint(Graphics g, Rectangle r) { // Painting code goes here } }
컨트롤이
Button
publicCaption
속성을 선언합니다. Caption 속성의 get 접근자가 개인string
필드에 저장된 값을 반환caption
합니다. set 접근자가 새 값이 현재 값과 다른지 확인하고, 있는 경우 새 값을 저장하고 컨트롤을 다시 칠합니다. 속성은 종종 위에 표시된 패턴을 따릅니다. get 접근자는 필드에 저장된private
값을 반환하고 집합 접근자는 해당private
필드를 수정한 다음 개체의 상태를 완전히 업데이트하는 데 필요한 추가 작업을 수행합니다. 위의 클래스를Button
고려할 때 속성의 사용Caption
예는 다음과 같습니다.Button okButton = new Button(); okButton.Caption = "OK"; // Invokes set accessor string s = okButton.Caption; // Invokes get accessor
여기서 set 접근자가 속성에 값을 할당하여 호출되고 식에서 속성을 참조하여 get 접근자가 호출됩니다.
끝 예제
속성의 get 및 set 접근자는 고유 멤버가 아니며 속성의 접근자를 별도로 선언할 수 없습니다.
예: 예제
class A { private string name; // Error, duplicate member name public string Name { get => name; } // Error, duplicate member name public string Name { set => name = value; } }
는 단일 읽기-쓰기 속성을 선언하지 않습니다. 대신 이름이 같은 두 개의 속성을 선언합니다. 하나는 읽기 전용이고 다른 하나는 쓰기 전용입니다. 동일한 클래스에 선언된 두 멤버의 이름은 같을 수 없으므로 컴파일 시간 오류가 발생하는 예제입니다.
끝 예제
파생 클래스가 상속된 속성과 동일한 이름으로 속성을 선언하는 경우 파생 속성은 읽기 및 쓰기와 관련하여 상속된 속성을 숨깁니다.
예제: 다음 코드에서
class A { public int P { set {...} } } class B : A { public new int P { get {...} } }
의 속성
P
은B
P
읽기 및 쓰기와 관련하여 속성을A
숨깁니다. 따라서 문에서B b = new B(); b.P = 1; // Error, B.P is read-only ((A)b).P = 1; // Ok, reference to A.P
에 있는 읽기 전용 속성이 쓰기 전용
b.P
P
속성을B
P
숨기므로 컴파일 시간 오류가 보고되도록 하는 할당A
입니다. 그러나 캐스트를 사용하여 숨겨진P
속성에 액세스할 수 있습니다.끝 예제
공용 필드와 달리 속성은 개체의 내부 상태와 공용 인터페이스를 구분합니다.
예제: 구조체를
Point
사용하여 위치를 나타내는 다음 코드를 고려합니다.class Label { private int x, y; private string caption; public Label(int x, int y, string caption) { this.x = x; this.y = y; this.caption = caption; } public int X => x; public int Y => y; public Point Location => new Point(x, y); public string Caption => caption; }
여기서 클래스는
Label
두int
개의 필드를x
y
사용하고 위치를 저장합니다. 위치는 형식의 속성X
및Y
속성으로Location
Point
공개적으로 노출됩니다. 이후 버전의Label
경우 내부적으로Point
위치를 저장하는 것이 더 편리해지면 클래스의 공용 인터페이스에 영향을 주지 않고 변경할 수 있습니다.class Label { private Point location; private string caption; public Label(int x, int y, string caption) { this.location = new Point(x, y); this.caption = caption; } public int X => location.X; public int Y => location.Y; public Point Location => location; public string Caption => caption; }
x
필드가y
있었다면public readonly
, 클래스를 변경하는Label
것은 불가능했을 것입니다.끝 예제
참고: 속성을 통해 상태를 노출하는 것이 필드를 직접 노출하는 것보다 덜 효율적인 것은 아닙니다. 특히 속성이 가상이 아니고 적은 양의 코드만 포함된 경우 실행 환경에서 접근자에 대한 호출을 접근자의 실제 코드로 바꿀 수 있습니다. 이 프로세스를 인라인 처리라고 하며 필드 액세스만큼 효율적으로 속성에 액세스할 수 있지만 속성의 유연성이 향상됩니다. 끝 메모
예: get 접근자를 호출하는 것은 개념적으로 필드 값을 읽는 것과 동일하기 때문에 get 접근자가 관찰 가능한 부작용을 가지는 것은 잘못된 프로그래밍 스타일로 간주됩니다. 예제에서
class Counter { private int next; public int Next => next++; }
속성 값은
Next
이전에 속성에 액세스한 횟수에 따라 달라집니다. 따라서 속성에 액세스하면 관찰 가능한 부작용이 발생하며 대신 메서드로 속성을 구현해야 합니다.get 접근자에 대한 "부작용 없음" 규칙이 필드에 저장된 값을 반환하기 위해 get 접근자를 항상 작성해야 한다는 의미는 아닙니다. 실제로 get 접근자는 여러 필드에 액세스하거나 메서드를 호출하여 속성 값을 계산하는 경우가 많습니다. 그러나 올바르게 디자인된 get 접근자가 개체의 상태를 관찰할 수 있는 변경을 유발하는 작업을 수행하지 않습니다.
끝 예제
속성을 사용하여 리소스가 처음 참조될 때까지 리소스 초기화를 지연할 수 있습니다.
예제:
public class Console { private static TextReader reader; private static TextWriter writer; private static TextWriter error; public static TextReader In { get { if (reader == null) { reader = new StreamReader(Console.OpenStandardInput()); } return reader; } } public static TextWriter Out { get { if (writer == null) { writer = new StreamWriter(Console.OpenStandardOutput()); } return writer; } } public static TextWriter Error { get { if (error == null) { error = new StreamWriter(Console.OpenStandardError()); } return error; } } ... }
클래스에는
Console
각각 표준 입력,In
Out
출력 및 오류 디바이스를 나타내는 세 가지 속성, 및Error
이 속성이 포함됩니다. 이러한 멤버를 속성으로 노출하면 클래스가Console
실제로 사용될 때까지 초기화를 지연할 수 있습니다. 예를 들어, 속성을 처음 참조할 때Out
와 같이Console.Out.WriteLine("hello, world");
출력 디바이스의 기본
TextWriter
이 만들어집니다. 그러나 애플리케이션이 및In
속성을 참조Error
하지 않으면 해당 디바이스에 대해 개체가 만들어지지 않습니다.끝 예제
15.7.4 자동으로 구현된 속성
자동으로 구현된 속성(또는 짧은 경우 자동 속성)은 세미콜론 전용 accessor_bodys를 사용하는 비 추상적, 비 extern, 비-ref-valued 속성입니다. 자동 속성에는 get 접근자가 있어야 하며 필요에 따라 set 접근자가 있을 수 있습니다.
속성이 자동으로 구현된 속성으로 지정되면 숨겨진 지원 필드를 속성에 자동으로 사용할 수 있으며 접근자는 해당 지원 필드에서 읽고 쓰도록 구현됩니다. 숨겨진 지원 필드는 액세스할 수 없으며, 포함된 형식 내에서도 자동으로 구현된 속성 접근자를 통해서만 읽고 쓸 수 있습니다. auto 속성에 set 접근자가 없으면 지원 필드가 고려됩니다 readonly
(§15.5.3). 필드와 readonly
마찬가지로, 바깥쪽 클래스의 생성자 본문에 읽기 전용 자동 속성을 할당할 수도 있습니다. 이러한 할당은 속성의 읽기 전용 지원 필드에 직접 할당됩니다.
자동 속성에는 필요에 따라 variable_initializer(§17.7)로 지원 필드에 직접 적용되는 property_initializer 있을 수 있습니다.
예제:
public class Point { public int X { get; set; } // Automatically implemented public int Y { get; set; } // Automatically implemented }
는 다음 선언과 동일합니다.
public class Point { private int x; private int y; public int X { get { return x; } set { x = value; } } public int Y { get { return y; } set { y = value; } } }
끝 예제
예: 다음에서
public class ReadOnlyPoint { public int X { get; } public int Y { get; } public ReadOnlyPoint(int x, int y) { X = x; Y = y; } }
는 다음 선언과 동일합니다.
public class ReadOnlyPoint { private readonly int __x; private readonly int __y; public int X { get { return __x; } } public int Y { get { return __y; } } public ReadOnlyPoint(int x, int y) { __x = x; __y = y; } }
읽기 전용 필드에 대한 할당은 생성자 내에서 발생하므로 유효합니다.
끝 예제
지원 필드는 숨겨지지만 해당 필드에는 자동으로 구현된 속성의 property_declaration (§15.7.1)을 통해 필드 대상 특성이 직접 적용될 수 있습니다.
예제: 다음 코드
[Serializable] public class Foo { [field: NonSerialized] public string MySecret { get; set; } }
그러면 다음과 같이 코드가 작성된 것처럼 필드 대상 특성
NonSerialized
이 컴파일러에서 생성된 지원 필드에 적용됩니다.[Serializable] public class Foo { [NonSerialized] private string _mySecretBackingField; public string MySecret { get { return _mySecretBackingField; } set { _mySecretBackingField = value; } } }
끝 예제
15.7.5 접근성
접근자에 accessor_modifier 있는 경우 접근자의 접근성 도메인(§7.5.3)은 accessor_modifier 선언된 접근성을 사용하여 결정됩니다. 접근자에 accessor_modifier 없는 경우 접근자의 접근성 도메인은 속성 또는 인덱서의 선언된 접근성에서 결정됩니다.
accessor_modifier 있는 것은 멤버 조회(§12.5) 또는 오버로드 확인(§12.6.4)에 영향을 미치지 않습니다. 속성 또는 인덱서의 한정자는 액세스 컨텍스트에 관계없이 항상 바인딩되는 속성 또는 인덱서를 결정합니다.
특정 비-ref-valued 속성 또는 비-ref-valued 인덱서가 선택되면 관련 접근자의 접근성 도메인을 사용하여 해당 사용이 유효한지 확인합니다.
- 사용량이 값(§12.2.2)인 경우 get 접근자가 존재하여 액세스할 수 있어야 합니다.
- 사용량이 단순 할당(§12.21.2)의 대상으로 지정된 경우 set 접근자가 존재하여 액세스할 수 있어야 합니다.
- 사용이 복합 할당(§12.21.4)의 대상이거나 또는
++
연산자(--
, §12.9.6)의 대상으로 사용하는 경우 get 접근자와 집합 접근자가 모두 존재하고 액세스할 수 있어야 합니다.
예: 다음 예제
A.Text
에서는 set 접근자만 호출되는 컨텍스트에서도 속성에 의해 속성B.Text
이 숨겨집니다. 반면, 클래스에서 속성B.Count
에M
액세스할 수 없으므로 액세스 가능한 속성A.Count
이 대신 사용됩니다.class A { public string Text { get => "hello"; set { } } public int Count { get => 5; set { } } } class B : A { private string text = "goodbye"; private int count = 0; public new string Text { get => text; protected set => text = value; } protected new int Count { get => count; set => count = value; } } class M { static void Main() { B b = new B(); b.Count = 12; // Calls A.Count set accessor int i = b.Count; // Calls A.Count get accessor b.Text = "howdy"; // Error, B.Text set accessor not accessible string s = b.Text; // Calls B.Text get accessor } }
끝 예제
특정 ref-valued 속성 또는 ref-valued 인덱서가 선택되면 사용량이 값인지, 단순 할당의 대상인지, 복합 할당의 대상인지 여부 관련된 get 접근자의 접근성 도메인은 해당 사용이 유효한지 확인하는 데 사용됩니다.
인터페이스를 구현하는 데 사용되는 접근자에는 accessor_modifier 없습니다. 인터페이스를 구현하는 데 한 접근자만 사용하는 경우 다른 접근자는 accessor_modifier 사용하여 선언될 수 있습니다.
예제:
public interface I { string Prop { get; } } public class C : I { public string Prop { get => "April"; // Must not have a modifier here internal set {...} // Ok, because I.Prop has no set accessor } }
끝 예제
15.7.6 가상, 봉인, 재정의 및 추상 접근자
참고: 이 절은 속성(§15.7) 및 인덱서(§15.9)에 모두 적용됩니다. 이 절은 속성 측면에서 작성되며, 인덱서를 읽을 때 속성/속성에 대한 인덱서/인덱서를 대체하고 §15.9.2에 지정된 속성과 인덱서 간의 차이점 목록을 참조합니다. 끝 메모
가상 속성 선언은 속성의 접근자가 가상임을 지정합니다. 한 virtual
정자는 속성의 모든 비 프라이빗 접근자에 적용됩니다. 가상 속성 private
의 접근자에 accessor_modifier 있는 경우 프라이빗 접근자가 암시적으로 가상이 아닙니다.
추상 속성 선언은 속성의 접근자가 가상이지만 접근자의 실제 구현을 제공하지 않음을 지정합니다. 대신 속성을 재정의하여 접근자에 대한 고유한 구현을 제공하려면 비 추상 파생 클래스가 필요합니다. 추상 속성 선언에 대한 접근자가 실제 구현을 제공하지 않으므로 해당 accessor_body 단순히 세미콜론으로 구성됩니다. 추상 속성에는 접근자가 private
있어야 합니다.
속성과 abstract
한정자를 모두 override
포함하는 속성 선언은 속성이 추상이며 기본 속성을 재정의한다는 것을 지정합니다. 이러한 속성의 접근자도 추상적입니다.
추상 속성 선언은 추상 클래스(§15.2.2.2)에서만 허용됩니다. 상속된 가상 속성의 접근자를 지시문을 지정하는 속성 선언을 포함하여 파생 클래스에서 재정의 override
할 수 있습니다. 이를 재정의 속성 선언이라고 합니다. 재정의 속성 선언은 새 속성을 선언하지 않습니다. 대신 기존 가상 속성의 접근자의 구현을 특수화합니다.
재정의 선언 및 재정의된 기본 속성은 동일한 선언된 접근성을 가져야 합니다. 즉, 재정의 선언은 기본 속성의 접근성을 변경하지 않습니다. 그러나 재정의된 기본 속성이 내부적으로 보호되고 재정의 선언을 포함하는 어셈블리와 다른 어셈블리에 선언된 경우 재정의 선언의 선언된 접근성이 보호됩니다. 상속된 속성에 단일 접근자만 있는 경우(즉, 상속된 속성이 읽기 전용이거나 쓰기 전용인 경우) 재정의 속성은 해당 접근자만 포함해야 합니다. 상속된 속성에 두 접근자(즉, 상속된 속성이 읽기-쓰기인 경우)가 모두 포함된 경우 재정의 속성은 단일 접근자 또는 두 접근자를 모두 포함할 수 있습니다. 재정의 형식과 상속된 속성 간에 ID 변환이 있어야 합니다.
재정의 속성 선언에는 한정자가 sealed
포함될 수 있습니다. 이 한정자를 사용하면 파생 클래스가 속성을 추가로 재정의할 수 없습니다. 봉인된 속성의 접근자도 봉인됩니다.
선언 및 호출 구문의 차이를 제외하고 가상, 봉인, 재정의 및 추상 접근자가 가상, 봉인, 재정의 및 추상 메서드와 똑같이 동작합니다. 특히 §15.6.4, §15.6.5, §15.6.6 및 §15.6.7에 설명된 규칙은 접근자가 해당 양식의 메서드인 것처럼 적용됩니다.
- get 접근자는 속성 형식의 반환 값과 포함하는 속성과 동일한 한정자를 가진 매개 변수가 없는 메서드에 해당합니다.
- set 접근자는 속성 형식의 단일 값 매개 변수, void 반환 형식 및 포함하는 속성과 동일한 한정자가 있는 메서드에 해당합니다.
예제: 다음 코드에서
abstract class A { int y; public virtual int X { get => 0; } public virtual int Y { get => y; set => y = value; } public abstract int Z { get; set; } }
X
는 가상 읽기 전용 속성이고,Y
가상 읽기-쓰기 속성이며Z
, 추상 읽기-쓰기 속성입니다.Z
추상이므로 포함하는 클래스 A도 추상으로 선언되어야 합니다.파생
A
되는 클래스는 다음과 같습니다.class B : A { int z; public override int X { get => base.X + 1; } public override int Y { set => base.Y = value < 0 ? 0: value; } public override int Z { get => z; set => z = value; } }
여기서는 ,
X
Y
및 속성 선언의 선언을 재정의Z
합니다. 각 속성 선언은 해당 상속된 속성의 접근성 한정자, 형식 및 이름과 정확히 일치합니다. 기본 키워드를X
사용하여 상속된 접근자에 액세스하는 get 접근자 및 set 접근Y
자입니다. 두 추상 접근자를 재정의Z
하는 선언입니다. 따라서 미해결abstract
함수 멤버B
B
는 없으며 비 추상 클래스로 허용됩니다.끝 예제
속성이 재정의로 선언되면 재정의된 접근자에 재정의 코드에 액세스할 수 있어야 합니다. 또한 속성 또는 인덱서 자체와 접근자의 선언된 접근성은 재정의된 멤버 및 접근자의 액세스 가능성과 일치해야 합니다.
예제:
public class B { public virtual int P { get {...} protected set {...} } } public class D: B { public override int P { get {...} // Must not have a modifier here protected set {...} // Must specify protected here } }
끝 예제
15.8 이벤트
15.8.1 일반
이벤트는 개체 또는 클래스가 알림을 제공할 수 있도록 하는 멤버입니다. 클라이언트는 이벤트 처리기를 제공하여 이벤트에 대한 실행 코드를 연결할 수 있습니다.
이벤트는 event_declaration사용하여 선언됩니다.
event_declaration
: attributes? event_modifier* 'event' type variable_declarators ';'
| attributes? event_modifier* 'event' type member_name
'{' event_accessor_declarations '}'
;
event_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
event_accessor_declarations
: add_accessor_declaration remove_accessor_declaration
| remove_accessor_declaration add_accessor_declaration
;
add_accessor_declaration
: attributes? 'add' block
;
remove_accessor_declaration
: attributes? 'remove' block
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
event_declaration 특성 집합(§22) 및 허용된 종류의 선언된 접근성(§15.3.6), (§15.3.5static
§15.6.3, virtual
, §15.8.5), override
(§15.6.5, §15.8.5), sealed
(§15.6.6), abstract
(§15.6.7, §15.8.5) 및 extern
(§15.6.8) 한정자.
이벤트 선언에는 유효한 한정자 조합과 관련하여 메서드 선언(§15.6)과 동일한 규칙이 적용됩니다.
이벤트 선언의 유형은 delegate_type(§8.2.8)이어야 하며, 해당 delegate_type 이벤트 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
이벤트 선언에는 event_accessor_declaration포함될 수 있습니다. 그러나 비-extern, 비추상 이벤트의 경우 컴파일러는 자동으로 제공해야 합니다(§15.8.2); extern
이벤트의 경우 엑세서는 외부에서 제공됩니다.
event_accessor_declaration생략하는 이벤트 선언은 variable_declarator 각각에 대해 하나 이상의 이벤트를 정의합니다. 특성 및 한정자는 이러한 event_declaration 선언된 모든 멤버에 적용됩니다.
한정자와 event_accessor_declaration 모두 abstract
포함하는 event_declaration 컴파일 시간 오류입니다.
이벤트 선언에 한 extern
정자가 포함된 경우 이벤트는 외부 이벤트라고 합니다. 외부 이벤트 선언은 실제 구현을 제공하지 않으므로 한정자와 extern
모두 포함하는 것은 오류입니다.
variable_initializer 포함하거나 한정자가 있는 이벤트 선언 abstract
의 variable_declaratorexternal
입니다.
이벤트는 및 +=
연산자의 -=
왼쪽 피연산자로 사용할 수 있습니다. 이러한 연산자는 이벤트 처리기를 이벤트 처리기에 연결하거나 이벤트에서 이벤트 처리기를 제거하는 데 각각 사용되며, 이벤트의 액세스 한정자는 이러한 작업이 허용되는 컨텍스트를 제어합니다.
해당 이벤트가 선언된 형식 외부의 코드에 의해 이벤트에 허용되는 유일한 작업은 다음과 +=
같습니다-=
. 따라서 이러한 코드는 이벤트에 대한 처리기를 추가 및 제거할 수 있지만 이벤트 처리기의 기본 목록을 직접 가져오거나 수정할 수는 없습니다.
폼 x += y
의 연산에서 또는 x –= y
이벤트인 경우 x
작업의 결과에 형식 void
이 있습니다(§12.21.5)(비 이벤트 형식x
에 정의된 다른 x
+=
연산자와 마찬가지로 할당 후 값을 -=
가진 형식이 있는 것과 반대). 이렇게 하면 외부 코드가 이벤트의 기본 대리자를 간접적으로 검사하지 못하게 됩니다.
예제: 다음 예제에서는 이벤트 처리기가 클래스의
Button
인스턴스에 연결되는 방법을 보여 줍니다.public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; } public class LoginDialog : Form { Button okButton; Button cancelButton; public LoginDialog() { okButton = new Button(...); okButton.Click += new EventHandler(OkButtonClick); cancelButton = new Button(...); cancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, EventArgs e) { // Handle okButton.Click event } void CancelButtonClick(object sender, EventArgs e) { // Handle cancelButton.Click event } }
LoginDialog
여기서 인스턴스 생성자는 두 개의Button
인스턴스를 만들고 이벤트 처리기를 이벤트에 연결합니다Click
.끝 예제
15.8.2 필드와 유사한 이벤트
이벤트 선언을 포함하는 클래스 또는 구조체의 프로그램 텍스트 내에서 특정 이벤트를 필드처럼 사용할 수 있습니다. 이러한 방식으로 사용하려면 이벤트는 추상적이거나 외설적이지 않으며 event_accessor_declaration명시적으로 포함하지 않아야 합니다. 이러한 이벤트는 필드를 허용하는 모든 컨텍스트에서 사용할 수 있습니다. 필드에는 이벤트에 추가된 이벤트 처리기 목록을 참조하는 대리자(§20)가 포함됩니다. 이벤트 처리기가 추가되지 않은 경우 필드에는 null
.
예제: 다음 코드에서
public delegate void EventHandler(object sender, EventArgs e); public class Button : Control { public event EventHandler Click; protected void OnClick(EventArgs e) { EventHandler handler = Click; if (handler != null) { handler(this, e); } } public void Reset() => Click = null; }
Click
는 클래스 내Button
의 필드로 사용됩니다. 예제에서 알 수 있듯이 대리자 호출 식에서 필드를 검사, 수정 및 사용할 수 있습니다. 클래스의 메서드는OnClick
Button
이벤트를 "발생"Click
합니다. 이벤트 발생 개념은 이벤트가 나타내는 대리자를 호출하는 것과 정확히 동일하므로 이벤트 발생을 위한 특수한 언어 구문은 없습니다. 대리자 호출 앞에는 대리자가 null이 아닌지 확인하고 스레드 안전을 보장하기 위해 로컬 복사본에서 검사가 수행되는지 확인합니다.클래스 선언
Button
외부에서 멤버는Click
다음과 같이 및+=
연산자의–=
왼쪽에서만 사용할 수 있습니다.b.Click += new EventHandler(...);
이벤트의 호출 목록에
Click
대리자를 추가하는Click –= new EventHandler(...);
이벤트의 호출 목록에서 대리자를 제거합니다
Click
.끝 예제
필드와 유사한 이벤트를 컴파일할 때 컴파일러는 대리자를 보관할 스토리지를 자동으로 만들고 대리자 필드에 이벤트 처리기를 추가하거나 제거하는 이벤트에 대한 접근자를 만들어야 합니다. 추가 및 제거 작업은 스레드로부터 안전하며 인스턴스 이벤트의 포함 개체에 대한 잠금(§13.13) 또는 정적 이벤트에 대한 개체(§12.8.18System.Type
하지만 필요하지는 않음).
참고: 따라서 폼의 인스턴스 이벤트 선언은 다음과 같습니다.
class X { public event D Ev; }
은 다음과 같은 항목으로 컴파일되어야 합니다.
class X { private D __Ev; // field to hold the delegate public event D Ev { add { /* Add the delegate in a thread safe way */ } remove { /* Remove the delegate in a thread safe way */ } } }
클래스
X
내에서 및Ev
연산자의+=
왼쪽–=
에 대한 참조로 인해 add 및 remove 접근자가 호출됩니다. 다른 모든 참조Ev
는 숨겨진 필드를__Ev
대신 참조하도록 컴파일됩니다(§12.8.7). 이름 "__Ev
"은 임의입니다. 숨겨진 필드에는 이름이 있거나 이름이 전혀 없을 수 있습니다.끝 메모
15.8.3 이벤트 접근자
참고: 이벤트 선언은 일반적으로 위의 예제와 같이 event_accessor_declaration
Button
합니다. 예를 들어 이벤트당 하나의 필드의 스토리지 비용이 허용되지 않는 경우 포함할 수 있습니다. 이러한 경우 클래스는 event_accessor_declaration포함할 수 있으며 이벤트 처리기 목록을 저장하기 위한 프라이빗 메커니즘을 사용할 수 있습니다. 끝 메모
이벤트의 event_accessor_declarations 이벤트 처리기 추가 및 제거와 관련된 실행 문을 지정합니다.
접근자 선언은 add_accessor_declaration remove_accessor_declaration 구성됩니다. 각 접근자 선언은 토큰 추가 또는 제거와 블록으로 구성됩니다. add_accessor_declaration 연결된 블록은 이벤트 처리기가 추가될 때 실행할 문을 지정하고, remove_accessor_declaration 연결된 블록은 이벤트 처리기가 제거될 때 실행할 문을 지정합니다.
각 add_accessor_declaration 및 remove_accessor_declaration 이벤트 형식의 단일 값 매개 변수 및 반환 형식이 있는 메서드에 void
해당합니다. 이벤트 접근자의 암시적 매개 변수 이름은 value
.입니다. 이벤트 할당에서 이벤트를 사용하는 경우 적절한 이벤트 접근자가 사용됩니다. 특히 대입 연산 +=
자가 있는 경우 add 접근자가 사용되고 할당 연산 –=
자가 있으면 제거 접근자가 사용됩니다. 두 경우 모두 할당 연산자의 오른쪽 피연산자가 이벤트 접근자에 대한 인수로 사용됩니다. add_accessor_declaration 또는 remove_accessor_declaration 블록은 §15.6.9void
설명된 방법에 대한 규칙을 준수해야 합니다. 특히 return
이러한 블록의 문은 식을 지정할 수 없습니다.
이벤트 접근자에는 암시적으로 명명 value
된 매개 변수가 있으므로 지역 변수 또는 이벤트 접근자에 선언된 상수에 대한 컴파일 시간 오류입니다.
예제: 다음 코드에서
class Control : Component { // Unique keys for events static readonly object mouseDownEventKey = new object(); static readonly object mouseUpEventKey = new object(); // Return event handler associated with key protected Delegate GetEventHandler(object key) {...} // Add event handler associated with key protected void AddEventHandler(object key, Delegate handler) {...} // Remove event handler associated with key protected void RemoveEventHandler(object key, Delegate handler) {...} // MouseDown event public event MouseEventHandler MouseDown { add { AddEventHandler(mouseDownEventKey, value); } remove { RemoveEventHandler(mouseDownEventKey, value); } } // MouseUp event public event MouseEventHandler MouseUp { add { AddEventHandler(mouseUpEventKey, value); } remove { RemoveEventHandler(mouseUpEventKey, value); } } // Invoke the MouseUp event protected void OnMouseUp(MouseEventArgs args) { MouseEventHandler handler; handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey); if (handler != null) { handler(this, args); } } }
클래스는
Control
이벤트에 대한 내부 스토리지 메커니즘을 구현합니다. 메서드는AddEventHandler
대리자 값을 키와 연결하고,GetEventHandler
메서드는 현재 키와 연결된 대리자를 반환하고RemoveEventHandler
, 메서드는 지정된 이벤트에 대한 이벤트 처리기로 대리자를 제거합니다. 아마도 기본 스토리지 메커니즘은 null 대리자 값을 키와 연결하기 위한 비용이 없도록 설계되었으므로 처리되지 않은 이벤트는 스토리지를 사용하지 않습니다.끝 예제
15.8.4 정적 및 인스턴스 이벤트
이벤트 선언에 한 static
정자가 포함된 경우 이벤트는 정적 이벤트라고 합니다. 한정자가 없 static
으면 이벤트를 인스턴스 이벤트라고 합니다.
정적 이벤트는 특정 인스턴스와 연결되지 않으며 정적 이벤트의 접근자에서 참조하는 this
컴파일 시간 오류입니다.
인스턴스 이벤트는 클래스의 지정된 인스턴스와 연결되며 이 인스턴스는 해당 이벤트의 접근자에서 (this
)로 액세스할 수 있습니다.
정적 멤버와 인스턴스 멤버의 차이점은 §15.3.8에서 자세히 설명합니다.
15.8.5 가상, 봉인, 재정의 및 추상 접근자
가상 이벤트 선언은 해당 이벤트의 접근자가 가상임을 지정합니다. 한 virtual
정자는 이벤트의 두 접근자에 모두 적용됩니다.
추상 이벤트 선언은 이벤트의 접근자가 가상이지만 접근자의 실제 구현을 제공하지 않음을 지정합니다. 대신 비 추상 파생 클래스는 이벤트를 재정의하여 접근자에 대한 고유한 구현을 제공해야 합니다. 추상 이벤트 선언에 대한 접근자는 실제 구현을 제공하지 않으므로 event_accessor_declaration제공하지 않습니다.
한정자와 한정자를 모두 abstract
override
포함하는 이벤트 선언은 이벤트가 추상임을 지정하고 기본 이벤트를 재정의합니다. 이러한 이벤트의 접근자도 추상적입니다.
추상 이벤트 선언은 추상 클래스(§15.2.2.2)에서만 허용됩니다.
상속된 가상 이벤트의 접근자는 한정자를 지정하는 이벤트 선언을 포함하여 파생 클래스에서 재정의 override
할 수 있습니다. 이를 재정의 이벤트 선언이라고 합니다. 재정의 이벤트 선언은 새 이벤트를 선언하지 않습니다. 대신 기존 가상 이벤트의 접근자의 구현을 특수화합니다.
재정의 이벤트 선언은 재정의된 이벤트와 정확히 동일한 접근성 한정자와 이름을 지정해야 하며, 재정의 형식과 재정의된 이벤트 간에 ID 변환이 있어야 하며, 추가 및 제거 접근자를 선언 내에 지정해야 합니다.
재정의 이벤트 선언에는 한정자가 sealed
포함될 수 있습니다. 한정자를 사용하면 파생 클래스가 이벤트를 추가로 재정의 this
하지 못하게 됩니다. 봉인된 이벤트의 접근자도 봉인됩니다.
한정자를 포함하는 재정의 이벤트 선언에 대한 컴파일 시간 오류입니다 new
.
선언 및 호출 구문의 차이를 제외하고 가상, 봉인, 재정의 및 추상 접근자가 가상, 봉인, 재정의 및 추상 메서드와 똑같이 동작합니다. 특히 §15.6.4, §15.6.5, §15.6.6 및 §15.6.7에 설명된 규칙은 접근자가 해당 양식의 메서드인 것처럼 적용됩니다. 각 접근자는 이벤트 형식의 단일 값 매개 변수, void
반환 형식 및 포함하는 이벤트와 동일한 한정자가 있는 메서드에 해당합니다.
15.9 인덱서
15.9.1 일반
인 덱서는 배열과 동일한 방식으로 개체를 인덱싱할 수 있는 멤버입니다. 인덱서는 indexer_declaration사용하여 선언됩니다.
indexer_declaration
: attributes? indexer_modifier* indexer_declarator indexer_body
| attributes? indexer_modifier* ref_kind indexer_declarator ref_indexer_body
;
indexer_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'virtual'
| 'sealed'
| 'override'
| 'abstract'
| 'extern'
| unsafe_modifier // unsafe code support
;
indexer_declarator
: type 'this' '[' parameter_list ']'
| type interface_type '.' 'this' '[' parameter_list ']'
;
indexer_body
: '{' accessor_declarations '}'
| '=>' expression ';'
;
ref_indexer_body
: '{' ref_get_accessor_declaration '}'
| '=>' 'ref' variable_reference ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
두 가지 종류의 indexer_declaration 있습니다.
- 첫 번째는 참조 값이 아닌 인덱서입니다. 해당 값의 형식은 형식 입니다. 이러한 종류의 인덱서는 읽을 수 있거나 쓸 수 있습니다.
- 두 번째는 ref-valued 인덱서입니다. 해당 값은 형식의 변수에 대한 variable_reference(
readonly
)입니다. 이러한 종류의 인덱서는 읽기 전용입니다.
indexer_declaration 특성 집합(§22) 및 허용된 종류의 선언된 접근성(§15.3.6), new
(§15.3.5), (§15.15) virtual
중 하나를 포함할 수 있습니다..6.4), override
(§15.6.5), sealed
(§15.6.6), abstract
(§15.6.7) 및 extern
(§15.6.8) 한정자.
인덱서 선언에는 유효한 한정자 조합과 관련하여 메서드 선언(§15.6)과 동일한 규칙이 적용되며 static
한 가지 예외는 한정자가 인덱서 선언에서 허용되지 않는다는 것입니다.
인덱서 선언의 형식은 선언에 의해 도입된 인덱서의 요소 형식을 지정합니다.
참고: 인덱서는 배열 요소와 같은 컨텍스트에서 사용하도록 설계되므로 배열에 정의된 용어 요소 형식 도 인덱서와 함께 사용됩니다. 끝 메모
인덱서가 명시적 인터페이스 멤버 구현 이 아니면 형식 뒤에 키워드 this
가 잇습니다. 명시적 인터페이스 멤버 구현의 경우 형식 뒤에 interface_type, ".
" 및 키워드 this
가 잇습니다. 다른 멤버와 달리 인덱서에는 사용자 정의 이름이 없습니다.
parameter_list 인덱서의 매개 변수를 지정합니다. 인덱서의 매개 변수 목록은 하나 이상의 매개 변수를 지정해야 하며 , this
ref
및 매개 변수 한정자가 허용되지 않는다는 점을 제외하고 메서드(out
)의 매개 변수 목록에 해당합니다.
인덱서의 형식과 parameter_list 참조되는 각 형식은 적어도 인덱서 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
indexer_body 문 본문(§15.7.1) 또는 식 본문(§15.6.1)으로 구성됩니다. 문 본문}
인덱서의 접근자(§15.7.3)를 선언합니다. 접근자는 인덱서 요소 읽기 및 쓰기와 관련된 실행 문을 지정합니다.
indexer_body 식과 세미콜론으로 구성된 식 =>
E
본문은 문 본문{ get { return E; } }
과 정확히 같으므로 get 접근자의 결과가 단일 식으로 제공되는 읽기 전용 인덱서를 지정하는 데만 사용할 수 있습니다.
ref_indexer_body 문 본문 또는 식 본문으로 구성됩니다. 문 본문 에서 get_accessor_declaration 인덱서의 get 접근자(§15.7.3)를 선언합니다. 접근자는 인덱서 읽기와 관련된 실행 문을 지정합니다.
ref_indexer_body 뒤에 variable_reference =>
ref
및 세미콜론으로 구성된 V
식 본문은 문 본문{ get { return ref V; } }
과 정확히 동일합니다.
참고: 인덱서 요소에 액세스하기 위한 구문이 배열 요소의 구문과 동일하더라도 인덱서 요소는 변수로 분류되지 않습니다. 따라서 인덱서가 ref-valued이므로 참조(§9.7)를 반환하지 않는 한 인덱서 요소를 또는
in
out
인수로ref
전달할 수 없습니다. 끝 메모
인덱서의 parameter_list 인덱서의 서명(§7.6)을 정의합니다. 특히 인덱서의 서명은 매개 변수의 수와 형식으로 구성됩니다. 매개 변수의 요소 형식 및 이름은 인덱서 서명의 일부가 아닙니다.
인덱서의 서명은 동일한 클래스에 선언된 다른 모든 인덱서의 서명과 다릅니다.
인덱서 선언에 한 extern
정자가 포함된 경우 인덱서는 외부 인덱서라고 합니다. 외부 인덱서 선언은 실제 구현을 제공하지 않으므로 accessor_declarations 각 accessor_body 세미콜론이어야 합니다.
예제: 아래 예제에서는 비트 배열의
BitArray
개별 비트에 액세스하기 위한 인덱서 구현 클래스를 선언합니다.class BitArray { int[] bits; int length; public BitArray(int length) { if (length < 0) { throw new ArgumentException(); } bits = new int[((length - 1) >> 5) + 1]; this.length = length; } public int Length => length; public bool this[int index] { get { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } return (bits[index >> 5] & 1 << index) != 0; } set { if (index < 0 || index >= length) { throw new IndexOutOfRangeException(); } if (value) { bits[index >> 5] |= 1 << index; } else { bits[index >> 5] &= ~(1 << index); } } } }
클래스의
BitArray
인스턴스는 해당bool[]
값보다 훨씬 적은 메모리를 사용하지만(전자의 각 값은 후byte
자의 값이 아닌 1비트만 차지하기 때문에) 동일한 작업을 허용합니다bool[]
.다음
CountPrimes
클래스는 aBitArray
및 클래식 "체" 알고리즘을 사용하여 2에서 지정된 최대값 사이의 소수 수를 계산합니다.class CountPrimes { static int Count(int max) { BitArray flags = new BitArray(max + 1); int count = 0; for (int i = 2; i <= max; i++) { if (!flags[i]) { for (int j = i * 2; j <= max; j += i) { flags[j] = true; } count++; } } return count; } static void Main(string[] args) { int max = int.Parse(args[0]); int count = Count(max); Console.WriteLine($"Found {count} primes between 2 and {max}"); } }
요소에 액세스하기
BitArray
위한 구문은 .의 구문과bool[]
정확하게 동일합니다.다음 예제에서는 두 개의 매개 변수가 있는 인덱서가 있는 26×10 그리드 클래스를 보여줍니다. 첫 번째 매개 변수는 A-Z 범위의 대문자 또는 소문자여야 하며, 두 번째 매개 변수는 0-9 범위의 정수여야 합니다.
class Grid { const int NumRows = 26; const int NumCols = 10; int[,] cells = new int[NumRows, NumCols]; public int this[char row, int col] { get { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } return cells[row - 'A', col]; } set { row = Char.ToUpper(row); if (row < 'A' || row > 'Z') { throw new ArgumentOutOfRangeException ("row"); } if (col < 0 || col >= NumCols) { throw new ArgumentOutOfRangeException ("col"); } cells[row - 'A', col] = value; } } }
끝 예제
15.9.2 인덱서 및 속성 차이점
인덱서와 속성은 개념에서 매우 유사하지만 다음과 같은 방법이 다릅니다.
- 속성은 이름으로 식별되는 반면 인덱서는 해당 서명으로 식별됩니다.
- 속성은 simple_name(§12.8.4) 또는 member_access(§12.8.7)를 통해 액세스하는 반면 인덱서 요소는 element_access(§12.8.12.3)를 통해 액세스됩니다.
- 속성은 정적 멤버일 수 있지만 인덱서는 항상 인스턴스 멤버입니다.
- 속성의 get 접근자는 매개 변수가 없는 메서드에 해당하지만 인덱서의 get 접근자는 인덱서와 동일한 매개 변수 목록을 가진 메서드에 해당합니다.
- 속성의 set 접근자는 이름이
value
단일 매개 변수인 메서드에 해당하지만 인덱서의 set 접근자는 인덱서와 동일한 매개 변수 목록과 명명된value
추가 매개 변수를 가진 메서드에 해당합니다. - 인덱서 접근자가 인덱서 매개 변수와 이름이 같은 지역 변수 또는 로컬 상수를 선언하는 것은 컴파일 시간 오류입니다.
- 재정의 속성 선언에서 상속된 속성은 구문을
base.P
사용하여 액세스합니다. 여기서P
는 속성 이름입니다. 재정의 인덱서 선언에서 상속된 인덱서는 쉼표로 구분된 식 목록인 구문을base[E]
E
사용하여 액세스합니다. - "자동으로 구현된 인덱서"라는 개념은 없습니다. 세미콜론 accessor_body있는 비 추상적 외부 인덱서가 있는 것은 오류입니다.
이러한 차이점 외에도 §15.7.3, §15.7.5 및 §15.7.6에 정의된 모든 규칙은 인덱서 접근자 및 속성 접근자에 적용됩니다.
§15.7.3, §15.7.5 및 §15.7.6을 읽을 때 속성/속성을 인덱서/인덱서로 바꾸는 것은 정의된 용어에도 적용됩니다. 특히 읽기-쓰기 속성은 읽기/쓰기 인덱서가 되고, 읽기 전용 속성은 읽기 전용 인덱서가 되고, 쓰기 전용 속성은 쓰기 전용 인덱서가 됩니다.
15.10 연산자
15.10.1 일반
연산자는 클래스의 인스턴스에 적용할 수 있는 식 연산자의 의미를 정의하는 멤버입니다. 연산자는 operator_declaration사용하여 선언됩니다.
operator_declaration
: attributes? operator_modifier+ operator_declarator operator_body
;
operator_modifier
: 'public'
| 'static'
| 'extern'
| unsafe_modifier // unsafe code support
;
operator_declarator
: unary_operator_declarator
| binary_operator_declarator
| conversion_operator_declarator
;
unary_operator_declarator
: type 'operator' overloadable_unary_operator '(' fixed_parameter ')'
;
logical_negation_operator
: '!'
;
overloadable_unary_operator
: '+' | '-' | logical_negation_operator | '~' | '++' | '--' | 'true' | 'false'
;
binary_operator_declarator
: type 'operator' overloadable_binary_operator
'(' fixed_parameter ',' fixed_parameter ')'
;
overloadable_binary_operator
: '+' | '-' | '*' | '/' | '%' | '&' | '|' | '^' | '<<'
| right_shift | '==' | '!=' | '>' | '<' | '>=' | '<='
;
conversion_operator_declarator
: 'implicit' 'operator' type '(' fixed_parameter ')'
| 'explicit' 'operator' type '(' fixed_parameter ')'
;
operator_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
참고: 접두사 논리 부정(§12.9.4) 및 접두사 null 용서 연산자(§12.8.9)는 동일 어휘 토큰(!
)으로 표현되지만 고유합니다. 후자는 오버로드 가능한 연산자가 아닙니다.
끝 메모
오버로드 가능한 연산자의 세 가지 범주는 단항 연산자(§15.10.2), 이진 연산자(§15.10.3) 및 변환 연산자(§15.10.4)입니다.
operator_body 세미콜론, 블록 본문(§15.6.1) 또는 식 본문(§15.6.1)입니다. 블록 본문은 연산자를 호출할 때 실행할 문을 지정하는 블록으로 구성됩니다. 블록은 §15.6.11에 설명된 값 반환 방법에 대한 규칙을 준수해야 합니다. 식 본문은 식과 세미콜론으로 구성 =>
되며 연산자가 호출될 때 수행할 단일 식을 표시합니다.
연산자의 경우 extern
operator_body 단순히 세미콜론으로 구성됩니다. 다른 모든 연산자의 경우 operator_body 블록 본문 또는 식 본문입니다.
다음 규칙은 모든 연산자 선언에 적용됩니다.
- 연산자 선언에는 a와 한
public
static
정자가 모두 포함되어야 합니다. - 연산
in
자의 매개 변수에는 . - 연산자의 서명(§15.10.2, §15.10.3, §15.10.4)은 동일한 클래스에 선언된 다른 모든 연산자의 서명과 다릅니다.
- 연산자 선언에서 참조되는 모든 형식은 적어도 연산자 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
- 동일한 한정자가 연산자 선언에 여러 번 표시되는 것은 오류입니다.
각 연산자 범주는 다음 하위 항목에 설명된 대로 추가 제한을 적용합니다.
다른 멤버와 마찬가지로 기본 클래스에 선언된 연산자는 파생 클래스에서 상속됩니다. 연산자 선언에는 연산자가 연산자의 서명에 참여하도록 선언된 클래스 또는 구조체가 항상 필요하므로 파생 클래스에 선언된 연산자가 기본 클래스에 선언된 연산자를 숨기는 것은 불가능합니다.
new
따라서 연산자 선언에서는 한정자가 필요하지 않으므로 허용되지 않습니다.
단항 및 이진 연산자에 대한 추가 정보는 §12.4에서 찾을 수 있습니다.
변환 연산자에 대한 추가 정보는 §10.5에서 찾을 수 있습니다.
15.10.2 단항 연산자
다음 규칙은 연산자 T
선언을 포함하는 클래스 또는 구조체의 인스턴스 형식을 나타내는 단항 연산자 선언에 적용됩니다.
- 단항
+
,-
(!
논리 부정만 해당) 또는~
연산자는 형식의 단일 매개 변수를 사용하거나T
모든 형식T?
을 반환할 수 있습니다. - 단항
++
또는--
연산자는 형식T
의 단일 매개 변수를 취하거나T?
동일한 형식 또는 파생된 형식을 반환해야 합니다. - 단항
true
또는false
연산자는 형식의 단일 매개 변수를 취하거나T
형식T?
bool
을 반환해야 합니다.
단항 연산자의 서명은 연산자 토큰(+
,, -
, !
, ~
, ++
--
true
또는false
) 및 단일 매개 변수의 형식으로 구성됩니다. 반환 형식은 단항 연산자의 서명에 속하지 않으며 매개 변수의 이름도 아닙니다.
true
단항 연산자와 false
단항 연산자는 쌍 단위 선언이 필요합니다. 클래스가 다른 연산자를 선언하지 않고 이러한 연산자 중 하나를 선언하는 경우 컴파일 시간 오류가 발생합니다.
true
및 false
연산자는 §12.24에 자세히 설명되어 있습니다.
예제: 다음 예제에서는 정수 벡터 클래스에 대한 operator++의 구현 및 후속 사용을 보여 줍니다.
public class IntVector { public IntVector(int length) {...} public int Length { get { ... } } // Read-only property public int this[int index] { get { ... } set { ... } } // Read-write indexer public static IntVector operator++(IntVector iv) { IntVector temp = new IntVector(iv.Length); for (int i = 0; i < iv.Length; i++) { temp[i] = iv[i] + 1; } return temp; } } class Test { static void Main() { IntVector iv1 = new IntVector(4); // Vector of 4 x 0 IntVector iv2; iv2 = iv1++; // iv2 contains 4 x 0, iv1 contains 4 x 1 iv2 = ++iv1; // iv2 contains 4 x 2, iv1 contains 4 x 2 } }
연산자 메서드는 접두사 증가 및 감소 연산자(§12.8.16) 및 접두사 증가 및 감소 연산자(§12.9.6)와 마찬가지로 피연산자에 1을 추가하여 생성된 값을 반환하는 방법을 확인합니다. C++와 달리 이 메서드는 후위 증가 연산자의 표준 의미 체계(§12.8.16)를 위반하므로 피연산자의 값을 직접 수정해서는 안 됩니다.
끝 예제
15.10.3 이진 연산자
다음 규칙은 연산자 T
선언을 포함하는 클래스 또는 구조체의 인스턴스 형식을 나타내는 이진 연산자 선언에 적용됩니다.
- 이진 비시프트 연산자는 두 개의 매개 변수를 가져와야 하며, 그 중 하나 이상이 형식
T
이거나T?
형식을 반환할 수 있습니다. - 이진
<<
또는>>
연산자(§12.11)는 두 개의 매개 변수를 가져와야 하며, 그 중 첫 번째 매개 변수에는 형식T
또는 T가 있어야 하며 두 번째 매개 변수는 형식 또는int
형식int?
을 포함해야 하며 모든 형식을 반환할 수 있습니다.
이진 연산자의 서명은 연산자 토큰(+
, ,-
, *
, /
, %
&
, |
^
<<
>>
==
!=
>
<
>=
또는<=
) 및 두 매개 변수의 형식으로 구성됩니다. 반환 형식 및 매개 변수 이름은 이진 연산자의 서명에 포함되지 않습니다.
특정 이진 연산자는 쌍 단위 선언이 필요합니다. 쌍의 모든 연산자 선언에 대해 쌍의 다른 연산자에 대한 일치 선언이 있어야 합니다. 반환 형식과 해당 매개 변수 형식 간에 ID 변환이 존재하는 경우 두 연산자 선언이 일치합니다. 다음 연산자는 쌍 단위 선언이 필요합니다.
- 연산
==
자 및 연산자!=
- 연산
>
자 및 연산자<
- 연산
>=
자 및 연산자<=
15.10.4 변환 연산자
변환 연산자 선언은 미리 정의된 암시적 및 명시적 변환을 보강하는 사용자 정의 변환 (§10.5)을 도입합니다.
키워드를 포함하는 변환 연산자 선언은 implicit
사용자 정의 암시적 변환을 도입합니다. 암시적 변환은 함수 멤버 호출, 캐스트 식 및 할당을 비롯한 다양한 상황에서 발생할 수 있습니다. 이 내용은 §10.2에 자세히 설명되어 있습니다.
키워드를 포함하는 변환 연산자 선언은 explicit
사용자 정의 명시적 변환을 도입합니다. 명시적 변환은 캐스트 식에서 발생할 수 있으며 §10.3에 자세히 설명되어 있습니다.
변환 연산자는 변환 연산자의 매개 변수 형식으로 표시된 원본 형식에서 변환 연산자의 반환 형식으로 표시된 대상 형식으로 변환합니다.
지정된 원본 형식 및 대상 형식 S
T
의 경우 null 허용 값 형식인 S
경우 T
해당 기본 형식을 참조 S₀
T₀
하고, S₀
그렇지 않으면 같고 T₀
S
각각 같 T
음입니다. 클래스 또는 구조체는 다음이 모두 true인 경우에만 원본 형식에서 대상 형식 S
T
으로의 변환을 선언할 수 있습니다.
S₀
은T₀
서로 다른 형식입니다.S₀
T₀
또는 연산자 선언을 포함하는 클래스 또는 구조체의 인스턴스 형식입니다.S₀
interface_type 것도 아닙니다T₀
.사용자 정의 변환을 제외하면 변환
S
T
T
S
이 있습니다.
이러한 규칙의 목적을 위해 다른 형식과 상속 관계가 없는 고유 형식과 S
연결되거나 T
고유한 형식으로 간주되며 해당 형식 매개 변수에 대한 제약 조건은 무시됩니다.
예: 다음에서:
class C<T> {...} class D<T> : C<T> { public static implicit operator C<int>(D<T> value) {...} // Ok public static implicit operator C<string>(D<T> value) {...} // Ok public static implicit operator C<T>(D<T> value) {...} // Error }
처음 두 연산자 선언은 각각 관계가 없는 고유한 형식으로 간주되기 때문에
T
int
string
허용됩니다. 그러나 세 번째 연산자는 .의C<T>
기본 클래스이므로 오류D<T>
입니다.끝 예제
두 번째 규칙에서 변환 연산자는 연산자가 선언된 클래스 또는 구조체 형식에서 변환되어야 합니다.
예: 클래스 또는 구조체 형식
C
이 변환을 대/반C
으로 정의할 수 있지만 변환은 정의int
int
할C
수int
없습니다bool
. 끝 예제
미리 정의된 변환을 직접 다시 정의할 수 없습니다. 따라서 다른 모든 형식 간에 object
암시적 및 명시적 변환이 이미 존재하기 때문에 변환 연산자는 변환 object
할 수 없습니다. 마찬가지로 변환이 이미 존재하기 때문에 변환의 원본 형식이나 대상 형식은 다른 변환의 기본 형식일 수 없습니다. 그러나 특정 형식 인수에 대해 미리 정의된 변환으로 이미 존재하는 변환을 지정하는 제네릭 형식에 대한 연산자를 선언할 수 있습니다.
예제:
struct Convertible<T> { public static implicit operator Convertible<T>(T value) {...} public static explicit operator T(Convertible<T> value) {...} }
형식
object
이 형식 인수T
로 지정되면 두 번째 연산자는 이미 존재하는 변환을 선언합니다(암시적이므로 형식에서 형식 개체로의 명시적 변환도 있음).끝 예제
두 형식 간에 미리 정의된 변환이 존재하는 경우 해당 형식 간의 사용자 정의 변환은 무시됩니다. 특별한 사항
- 미리 정의된 암시적 변환(§10.2
T
- 미리 정의된 명시적 변환(§10.3
T
더욱이:- 인터페이스 형식이거나
S
T
인터페이스 형식인 경우 사용자 정의 암시적 변환은S
T
무시됩니다. - 그렇지 않으면 사용자 정의 암시적 변환을
S
T
계속 고려합니다.
- 인터페이스 형식이거나
위의 object
형식으로 선언된 연산자는 모든 형식에 Convertible<T>
대해 미리 정의된 변환과 충돌하지 않습니다.
예제:
void F(int i, Convertible<int> n) { i = n; // Error i = (int)n; // User-defined explicit conversion n = i; // User-defined implicit conversion n = (Convertible<int>)i; // User-defined implicit conversion }
그러나 형식
object
의 경우 미리 정의된 변환은 다음을 제외한 모든 경우에 사용자 정의 변환을 숨깁니다.void F(object o, Convertible<object> n) { o = n; // Pre-defined boxing conversion o = (object)n; // Pre-defined boxing conversion n = o; // User-defined implicit conversion n = (Convertible<object>)o; // Pre-defined unboxing conversion }
끝 예제
사용자 정의 변환은 interface_type변환할 수 없습니다. 특히 이 제한은 interface_type 변환할 때 사용자 정의 변환이 발생하지 않도록 하고 변환되는 interface_type 실제로 지정된 object
이 성공하도록 합니다.
변환 연산자의 서명은 원본 형식과 대상 형식으로 구성됩니다. (이 형식은 반환 형식이 서명에 참여하는 유일한 멤버 형식입니다.) 변환 연산자의 암시적 또는 명시적 분류는 연산자의 서명에 포함되지 않습니다. 따라서 클래스 또는 구조체는 소스 및 대상 형식이 동일한 암시적 변환 연산자와 명시적 변환 연산자를 모두 선언할 수 없습니다.
참고: 일반적으로 사용자 정의 암시적 변환은 예외를 throw하지 않으며 정보를 잃지 않도록 설계되어야 합니다. 사용자 정의 변환으로 인해 예외가 발생하거나(예: 원본 인수가 범위를 벗어났기 때문에) 정보가 손실될 수 있는 경우(예: 상위 비트 삭제) 해당 변환은 명시적 변환으로 정의되어야 합니다. 끝 메모
예제: 다음 코드에서
public struct Digit { byte value; public Digit(byte value) { if (value < 0 || value > 9) { throw new ArgumentException(); } this.value = value; } public static implicit operator byte(Digit d) => d.value; public static explicit operator Digit(byte b) => new Digit(b); }
Digit
변환은 예외를byte
throw하거나 정보를 손실하지 않으므로 암시적이지만byte
Digit
변환은 명시적입니다Digit
. 이는 가능한 값의byte
하위 집합만 나타낼 수 있기 때문입니다.끝 예제
15.11 인스턴스 생성자
15.11.1 일반
인스턴스 생성자는 클래스의 인스턴스를 초기화하는 데 필요한 작업을 구현하는 멤버입니다. 인스턴스 생성자는 constructor_declaration사용하여 선언됩니다.
constructor_declaration
: attributes? constructor_modifier* constructor_declarator constructor_body
;
constructor_modifier
: 'public'
| 'protected'
| 'internal'
| 'private'
| 'extern'
| unsafe_modifier // unsafe code support
;
constructor_declarator
: identifier '(' parameter_list? ')' constructor_initializer?
;
constructor_initializer
: ':' 'base' '(' argument_list? ')'
| ':' 'this' '(' argument_list? ')'
;
constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
constructor_declaration 특성 집합(§22), 허용된 종류의 선언된 접근성(§15.3.6) 및 extern
(§15.6.8) 한정자가 포함될 수 있습니다. 생성자 선언은 동일한 한정자를 여러 번 포함할 수 없습니다.
constructor_declarator 식별자는인스턴스 생성자가 선언된 클래스의 이름을 지정해야 합니다. 다른 이름을 지정하면 컴파일 시간 오류가 발생합니다.
인스턴스 생성자의 선택적 parameter_list 메서드의 parameter_list(§15.6)와 동일한 규칙이 적용됩니다.
this
매개 변수의 한정자는 확장 메서드(§15.6.10)에만 적용됩니다. 생성자의 parameter_list 매개 변수에는 한정자가 포함되지 this
않습니다. 매개 변수 목록은 인스턴스 생성자의 서명(§7.6)을 정의하고 오버로드 확인(§12.6.4)이 호출에서 특정 인스턴스 생성자를 선택하는 프로세스를 제어합니다.
인스턴스 생성자의 parameter_list 참조되는 각 형식은 적어도 생성자 자체(§7.5.5)만큼 액세스할 수 있어야 합니다.
선택적 constructor_initializer 이 인스턴스 생성자의 constructor_body 지정된 문을 실행하기 전에 호출할 다른 인스턴스 생성자를 지정합니다. 이 내용은 §15.11.2에 자세히 설명되어 있습니다.
생성자 선언에 한 extern
정자가 포함된 경우 생성자는 외부 생성자라고 합니다. 외부 생성자 선언은 실제 구현을 제공하지 않으므로 해당 constructor_body 세미콜론으로 구성됩니다. 다른 모든 생성자의 경우 constructor_body 둘 중 하나로 구성됩니다.
- 클래스의 새 인스턴스를 초기화하는 문을 지정하는 블록입니다.
- 식 본문은 식과 세미콜론으로
=>
이루어져 있으며 클래스의 새 인스턴스를 초기화하는 단일 식을 표시합니다.
블록 또는 식 본문인 constructor_body 반환 형식(§15.6.11)이 있는 void
인스턴스 메서드의 블록에 정확히 해당합니다.
인스턴스 생성자는 상속되지 않습니다. 따라서 클래스에는 클래스에 인스턴스 생성자 선언이 없는 경우 기본 인스턴스 생성자가 자동으로 제공된다는 점을 제외하고 클래스에 실제로 선언된 생성자 이외의 인스턴스 생성자가 없습니다(§15.11.5).
인스턴스 생성자는 object_creation_expression(§12.8.17.2) 및 constructor_initializer통해 호출됩니다.
15.11.2 생성자 이니셜라이저
클래스를 object
제외한 모든 인스턴스 생성자는 constructor_body 바로 앞에 다른 인스턴스 생성자의 호출을 암시적으로 포함합니다. 암시적으로 호출할 생성자는 constructor_initializer 의해 결정됩니다.
- 양식
base(
)
선택 사항)를 사용하면 직접 기본 클래스의 인스턴스 생성자가 호출됩니다. 해당 생성자는 argument_list 및 §12.6.4의 오버로드 확인 규칙을 사용하여 선택됩니다. 후보 인스턴스 생성자 집합은 직접 기본 클래스의 액세스 가능한 모든 인스턴스 생성자로 구성됩니다. 이 집합이 비어 있거나 단일 최상의 인스턴스 생성자를 식별할 수 없는 경우 컴파일 시간 오류가 발생합니다. - 양식
this(
argument_list)
인스턴스 생성자 이니셜라이저( argument_list 선택 사항)는 동일한 클래스에서 다른 인스턴스 생성자를 호출합니다. 생성자는 argument_list 및 §12.6.4의 오버로드 확인 규칙을 사용하여 선택됩니다. 후보 인스턴스 생성자 집합은 클래스 자체에 선언된 모든 인스턴스 생성자로 구성됩니다. 적용 가능한 인스턴스 생성자의 결과 집합이 비어 있거나 단일 최상의 인스턴스 생성자를 식별할 수 없는 경우 컴파일 시간 오류가 발생합니다. 인스턴스 생성자 선언이 하나 이상의 생성자 이니셜라이저 체인을 통해 자신을 호출하는 경우 컴파일 시간 오류가 발생합니다.
인스턴스 생성자에 생성자 이니셜라이저가 없는 경우 폼 base()
의 생성자 이니셜라이저가 암시적으로 제공됩니다.
참고: 따라서 폼의 인스턴스 생성자 선언
C(...) {...}
은
C(...) : base() {...}
끝 메모
인스턴스 생성자 선언의 parameter_list 지정된 매개 변수의 범위에는 해당 선언의 생성자 이니셜라이저가 포함됩니다. 따라서 생성자 이니셜라이저는 생성자의 매개 변수에 액세스할 수 있습니다.
예제:
class A { public A(int x, int y) {} } class B: A { public B(int x, int y) : base(x + y, x - y) {} }
끝 예제
인스턴스 생성자 이니셜라이저는 생성되는 인스턴스에 액세스할 수 없습니다. 따라서 simple_name 통해 인스턴스 멤버를 참조하는 인수 식의 컴파일 시간 오류이므로 생성자 이니셜라이저의 인수 식에서 이를 참조하는 것은 컴파일 시간 오류입니다.
15.11.3 인스턴스 변수 이니셜라이저
인스턴스 생성자에 생성자 이니셜라이저가 없거나 폼 base(...)
의 생성자 이니셜라이저가 있는 경우 해당 생성자는 클래스에 선언된 인스턴스 필드의 variable_initializer지정한 초기화를 암시적으로 수행합니다. 이는 생성자에 진입할 때와 직접 기본 클래스 생성자의 암시적 호출 전에 즉시 실행되는 할당 시퀀스에 해당합니다. 변수 이니셜라이저는 클래스 선언(§15.5.6)에 표시되는 텍스트 순서로 실행됩니다.
15.11.4 생성자 실행
변수 이니셜라이저는 대입 문으로 변환되며, 이러한 할당 문은 기본 클래스 인스턴스 생성자를 호출하기 전에 실행됩니다. 이 순서를 지정하면 해당 인스턴스에 액세스할 수 있는 문이 실행되기 전에 모든 인스턴스 필드가 변수 이니셜라이저에 의해 초기화됩니다.
예: 다음을 지정합니다.
class A { public A() { PrintFields(); } public virtual void PrintFields() {} } class B: A { int x = 1; int y; public B() { y = -1; } public override void PrintFields() => Console.WriteLine($"x = {x}, y = {y}"); }
인스턴스를 만드는 데 new
B()
가B
사용되는 경우 다음 출력이 생성됩니다.x = 1, y = 0
기본 클래스 인스턴스 생성자가 호출되기 전에 변수 이니셜라이저가 실행되기 때문에 값
x
은 1입니다. 그러나 기본 클래스 생성자가 반환될 때까지 할당y
이 실행되지 않으므로 값int
은 0(기본값y
)입니다. 인스턴스 변수 이니셜라이저 및 생성자 이니셜라이저를 constructor_body 전에 자동으로 삽입되는 문으로 생각하는 것이 유용합니다. 예제class A { int x = 1, y = -1, count; public A() { count = 0; } public A(int n) { count = n; } } class B : A { double sqrt2 = Math.Sqrt(2.0); ArrayList items = new ArrayList(100); int max; public B(): this(100) { items.Add("default"); } public B(int n) : base(n - 1) { max = n; } }
에는 여러 변수 이니셜라이저가 포함되어 있습니다. 또한 두 폼(
base
및this
)의 생성자 이니셜라이저도 포함합니다. 이 예제는 아래 표시된 코드에 해당합니다. 여기서 각 주석은 자동으로 삽입된 문을 나타냅니다(자동으로 삽입된 생성자 호출에 사용되는 구문은 유효하지 않지만 메커니즘을 설명하기 위한 용도로만 사용됨).class A { int x, y, count; public A() { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = 0; } public A(int n) { x = 1; // Variable initializer y = -1; // Variable initializer object(); // Invoke object() constructor count = n; } } class B : A { double sqrt2; ArrayList items; int max; public B() : this(100) { B(100); // Invoke B(int) constructor items.Add("default"); } public B(int n) : base(n - 1) { sqrt2 = Math.Sqrt(2.0); // Variable initializer items = new ArrayList(100); // Variable initializer A(n - 1); // Invoke A(int) constructor max = n; } }
끝 예제
15.11.5 기본 생성자
클래스에 인스턴스 생성자 선언이 없는 경우 기본 인스턴스 생성자가 자동으로 제공됩니다. 이 기본 생성자는 폼 base()
의 생성자 이니셜라이저가 있는 것처럼 직접 기본 클래스의 생성자를 호출합니다. 클래스가 추상인 경우 기본 생성자에 대해 선언된 접근성이 보호됩니다. 그렇지 않으면 기본 생성자에 대해 선언된 접근성이 public입니다.
참고: 따라서 기본 생성자는 항상 양식입니다.
protected C(): base() {}
또는
public C(): base() {}
클래스의 이름은
C />입니다. 끝 메모
오버로드 확인에서 기본 클래스 생성자 이니셜라이저에 가장 적합한 고유한 후보를 확인할 수 없는 경우 컴파일 시간 오류가 발생합니다.
예제: 다음 코드에서
class Message { object sender; string text; }
클래스에 인스턴스 생성자 선언이 없기 때문에 기본 생성자가 제공됩니다. 따라서 이 예제는 정확히
class Message { object sender; string text; public Message() : base() {} }
끝 예제
15.12 정적 생성자
정적 생성자는 닫힌 클래스를 초기화하는 데 필요한 작업을 구현하는 멤버입니다. 정적 생성자는 static_constructor_declaration사용하여 선언됩니다.
static_constructor_declaration
: attributes? static_constructor_modifiers identifier '(' ')'
static_constructor_body
;
static_constructor_modifiers
: 'static'
| 'static' 'extern' unsafe_modifier?
| 'static' unsafe_modifier 'extern'?
| 'extern' 'static' unsafe_modifier?
| 'extern' unsafe_modifier 'static'
| unsafe_modifier 'static' 'extern'?
| unsafe_modifier 'extern' 'static'
;
static_constructor_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
static_constructor_declaration 특성 집합(§22) 및 한 정자(§15.6.8)를 포함할 수 있습니다.extern
static_constructor_declaration 식별자는정적 생성자가 선언되는 클래스의 이름을 지정해야 합니다. 다른 이름을 지정하면 컴파일 시간 오류가 발생합니다.
정적 생성자 선언에 한 extern
정자가 포함된 경우 정적 생성자는 외부 정적 생성자라고 합니다. 외부 정적 생성자 선언은 실제 구현을 제공하지 않으므로 해당 static_constructor_body 세미콜론으로 구성됩니다. 다른 모든 정적 생성자 선언의 경우 static_constructor_body 둘 중 하나로 구성됩니다.
- 클래스를 초기화하기 위해 실행할 문을 지정하는 블록입니다.
- 식 본문은 식과 세미콜론으로
=>
이루어져 있으며 클래스를 초기화하기 위해 실행할 단일 식을 표시합니다.
블록 또는 식 본문인 static_constructor_body 반환 형식(§15.6.11)이 있는 정적 메서드 void
의 method_body 정확히 일치합니다.
정적 생성자는 상속되지 않으며 직접 호출할 수 없습니다.
닫힌 클래스에 대한 정적 생성자는 지정된 애플리케이션 도메인에서 한 번만 실행됩니다. 정적 생성자의 실행은 애플리케이션 도메인 내에서 발생할 다음 이벤트 중 첫 번째에 의해 트리거됩니다.
- 클래스의 인스턴스가 만들어집니다.
- 클래스의 정적 멤버가 참조됩니다.
클래스에 실행이 Main
시작되는 메서드(§7.1)가 포함되어 있으면 메서드가 호출되기 전에 해당 클래스의 Main
정적 생성자가 실행됩니다.
새 닫힌 클래스 형식을 초기화하려면 먼저 해당 특정 닫힌 형식에 대한 새 정적 필드 집합(§15.5.2)을 만듭니다. 각 정적 필드는 기본값(§15.5.5)으로 초기화됩니다. 다음으로 정적 필드 이니셜라이저(§15.5.6.2)가 해당 정적 필드에 대해 실행됩니다. 마지막으로 정적 생성자가 실행됩니다.
예: 예제
class Test { static void Main() { A.F(); B.F(); } } class A { static A() { Console.WriteLine("Init A"); } public static void F() { Console.WriteLine("A.F"); } } class B { static B() { Console.WriteLine("Init B"); } public static void F() { Console.WriteLine("B.F"); } }
는 출력을 생성해야 합니다.
Init A A.F Init B B.F
는 '의 정적 생성자 실행
A
이 호출에A.F
의해 트리거되고 ' 정적 생성자의 실행B
이 호출B.F
에 의해 트리거되기 때문입니다.끝 예제
변수 이니셜라이저가 있는 정적 필드가 기본값 상태에서 관찰될 수 있도록 하는 순환 종속성을 생성할 수 있습니다.
예: 예제
class A { public static int X; static A() { X = B.Y + 1; } } class B { public static int Y = A.X + 1; static B() {} static void Main() { Console.WriteLine($"X = {A.X}, Y = {B.Y}"); } }
는 출력을 생성합니다.
X = 1, Y = 2
메서드를
Main
실행하기 위해 시스템은 먼저 클래스B.Y
의 정적 생성자 이전의 이니셜라이저B
를 실행합니다.Y
'의 이니셜라이저는 값이 참조되기 때문에 'sA
static
생성자가 실행되도록 합니다A.X
. 정적 생성자는A
차례로 값X
계산을 진행하고, 이 경우 기본값Y
인 0을 가져옵니다.A.X
은 1로 초기화됩니다. 그러면 '의 정적 필드 이니셜라이저 및 정적 생성자를 실행하는A
프로세스가 완료되고 초기 값의Y
계산으로 돌아가면 결과는 2가 됩니다.끝 예제
정적 생성자는 닫힌 생성된 각 클래스 형식에 대해 정확히 한 번 실행되므로 제약 조건(§15.2.5)을 통해 컴파일 타임에 확인할 수 없는 형식 매개 변수에 대해 런타임 검사를 적용하는 것이 편리합니다.
예: 다음 형식은 정적 생성자를 사용하여 형식 인수가 열거형임을 적용합니다.
class Gen<T> where T : struct { static Gen() { if (!typeof(T).IsEnum) { throw new ArgumentException("T must be an enum"); } } }
끝 예제
15.13 종료자
참고: 이 사양의 이전 버전에서는 현재 "종료자"라고 하는 것을 "소멸자"라고 합니다. 경험에 따르면 "소멸자"라는 용어는 혼동을 일으켰으며, 특히 C++를 아는 프로그래머에게 잘못된 기대치를 초래하는 경우가 많습니다. C++에서는 소멸자가 결정적인 방식으로 호출되는 반면 C#에서는 종료자가 호출되지 않습니다. C#에서 확인 동작을 얻으려면 .를 사용해야
Dispose
합니다. 끝 메모
종료자는 클래스의 인스턴스를 종결하는 데 필요한 작업을 구현하는 멤버입니다. 종료자는 finalizer_declaration 사용하여 선언됩니다.
finalizer_declaration
: attributes? '~' identifier '(' ')' finalizer_body
| attributes? 'extern' unsafe_modifier? '~' identifier '(' ')'
finalizer_body
| attributes? unsafe_modifier 'extern'? '~' identifier '(' ')'
finalizer_body
;
finalizer_body
: block
| '=>' expression ';'
| ';'
;
unsafe_modifier(§23.2)는 안전하지 않은 코드(§23)에서만 사용할 수 있습니다.
finalizer_declaration 특성 집합(§22)을 포함할 수 있습니다.
finalizer_declarator 식별자는종료자가 선언된 클래스의 이름을 지정해야 합니다. 다른 이름을 지정하면 컴파일 시간 오류가 발생합니다.
종료자 선언에 한 extern
정자가 포함되어 있으면 종료자는 외부 종료자라고 합니다. 외부 종료자 선언은 실제 구현을 제공하지 않으므로 해당 finalizer_body 세미콜론으로 구성됩니다. 다른 모든 종료자의 경우 finalizer_body 둘 중 하나로 구성됩니다.
- 클래스의 인스턴스를 완료하기 위해 실행할 문을 지정하는 블록입니다.
- 식과 세미콜론으로 구성되고 클래스 인스턴스
=>
를 완료하기 위해 실행할 단일 식을 나타내는 식 본문 또는 식 본문입니다.
블록 또는 식 본문인 finalizer_body 반환 형식(§15.6.11)이 있는 인스턴스 메서드 void
의 method_body 정확히 일치합니다.
종료자는 상속되지 않습니다. 따라서 클래스에는 해당 클래스에서 선언할 수 있는 종료자 이외의 종료자가 없습니다.
참고: 종료자에는 매개 변수가 없어야 하므로 오버로드할 수 없으므로 클래스에는 최대 하나의 종료자가 있을 수 있습니다. 끝 메모
종료자는 자동으로 호출되며 명시적으로 호출할 수 없습니다. 더 이상 코드에서 해당 인스턴스를 사용할 수 없으면 인스턴스를 종료할 수 있게 됩니다. 인스턴스에 대한 종료자 실행은 인스턴스가 종료될 수 있게 된 후 언제든지 발생할 수 있습니다(§7.9). 인스턴스가 종료되면 해당 인스턴스의 상속 체인에 있는 종료자가 대부분의 파생에서 최소 파생으로 순서대로 호출됩니다. 종료자는 모든 스레드에서 실행될 수 있습니다. 종료자가 실행되는 시기와 방법을 제어하는 규칙에 대한 자세한 내용은 §7.9를 참조하세요.
예제: 예제의 출력
class A { ~A() { Console.WriteLine("A's finalizer"); } } class B : A { ~B() { Console.WriteLine("B's finalizer"); } } class Test { static void Main() { B b = new B(); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
is
B's finalizer A's finalizer
상속 체인의 종료자는 대부분 파생에서 최소 파생으로 순서대로 호출되기 때문에
끝 예제
종료자는 가상 메서드 Finalize
를 재정의하여 구현됩니다 System.Object
. C# 프로그램은 이 메서드를 재정의하거나 직접 호출(또는 재정의)할 수 없습니다.
예: 예를 들어 프로그램
class A { override protected void Finalize() {} // Error public void F() { this.Finalize(); // Error } }
에는 두 개의 오류가 포함됩니다.
끝 예제
컴파일러는 이 메서드와 재정의가 전혀 없는 것처럼 동작해야 합니다.
예: 따라서 이 프로그램은 다음과 같습니다.
class A { void Finalize() {} // Permitted }
가 유효하고 표시된 메서드가 's
System.Object
메서드를Finalize
숨깁니다.끝 예제
종료자에서 예외가 throw되는 경우 동작에 대한 자세한 내용은 §21.4를 참조하세요.
15.14 반복기
15.14.1 일반
반복기 블록(§13.3)을 사용하여 구현된 함수 멤버(§12.6)를 반복기라고 합니다.
반복기 블록은 해당 함수 멤버의 반환 형식이 열거자 인터페이스(§15.14.2) 또는 열거 가능한 인터페이스 중 하나(§15.14.3) 중 하나일 경우 함수 멤버의 본문으로 사용될 수 있습니다. method_body, operator_body 또는 accessor_body 발생할 수 있지만 이벤트, 인스턴스 생성자, 정적 생성자 및 종료자는 반복기로 구현되지 않습니다.
반복기 블록을 사용하여 함수 멤버를 구현하는 경우 함수 멤버의 매개 변수 목록에서 형식의 매개 변수 또는 매개 변수를 지정in
out
하는 것은 컴파일 시간 오류입니다ref
.ref struct
15.14.2 열거자 인터페이스
열거자 인터페이스는 제네릭 인터페이스가 아닌 인터페이스 및 제네릭 인터페이스 System.Collections.IEnumerator
System.Collections.Generic.IEnumerator<T>
의 모든 인스턴스화입니다. 간결성을 위해 이 하위 클래스와 해당 형제에서 이러한 인터페이스는 각각과 같이 IEnumerator
IEnumerator<T>
참조됩니다.
15.14.3 열거 가능한 인터페이스
열거 가능한 인터페이스는 제네릭 인터페이스가 아닌 인터페이스 및 제네릭 인터페이스 System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>
의 모든 인스턴스화입니다. 간결성을 위해 이 하위 클래스와 해당 형제에서 이러한 인터페이스는 각각과 같이 IEnumerable
IEnumerable<T>
참조됩니다.
15.14.4 수익률 유형
반복기는 모두 동일한 형식의 값 시퀀스를 생성합니다. 이 형식을 반복기의 수율 형식 이라고 합니다.
- 반환
IEnumerator
하거나IEnumerable
반환하는 반복기의 수율 형식입니다object
. - 반환
IEnumerator<T>
하거나IEnumerable<T>
반환하는 반복기의 수율 형식입니다T
.
15.14.5 열거자 개체
15.14.5.1 일반
반복기 블록을 사용하여 열거자 인터페이스 형식을 반환하는 함수 멤버를 구현하는 경우 함수 멤버를 호출해도 반복기 블록에서 코드를 즉시 실행하지는 않습니다. 대신 열 거자 개체 가 만들어지고 반환됩니다. 이 개체는 반복기 블록에 지정된 코드를 캡슐화하고 반복기 블록의 코드 실행은 열거자 개체의 MoveNext
메서드가 호출될 때 발생합니다. 열거자 개체의 특징은 다음과 같습니다.
- 반복기의 수율 형식을 구현
IEnumerator
하고IEnumerator<T>
여기서T
구현합니다. -
System.IDisposable
을 구현합니다. - 함수 멤버에 전달된 인수 값(있는 경우) 및 인스턴스 값의 복사본을 사용하여 초기화됩니다.
- 이전, 실행, 일시 중단 및 이후의 4가지 잠재적 상태가 있으며 처음에는 이전 상태에 있습니다.
열거자 개체는 일반적으로 반복기 블록의 코드를 캡슐화하고 열거자 인터페이스를 구현하는 컴파일러 생성 열거자 클래스의 인스턴스이지만 다른 구현 방법이 가능합니다. 컴파일러에서 열거자 클래스를 생성하는 경우 해당 클래스는 함수 멤버를 포함하는 클래스에서 직접 또는 간접적으로 중첩되고 프라이빗 접근성을 가지며 컴파일러 사용을 위해 예약된 이름(§6.4.3)을 갖습니다.
열거자 개체는 위에서 지정한 인터페이스보다 더 많은 인터페이스를 구현할 수 있습니다.
다음 하위 클래스는 열거자 개체에서 제공하는 인터페이스 구현 및 인터페이스 구현의 MoveNext
Current
필수 동작 Dispose
IEnumerator
및 IEnumerator<T>
멤버에 대해 설명합니다.
열거자 개체는 메서드를 IEnumerator.Reset
지원하지 않습니다. 이 메서드를 호출하면 throw System.NotSupportedException
됩니다.
15.14.5.2 MoveNext 메서드
열거자 개체의 메서드는 MoveNext
반복기 블록의 코드를 캡슐화합니다. 메서드를 MoveNext
호출하면 반복기 블록에서 코드가 실행되고 열거자 개체의 속성이 적절하게 설정 Current
됩니다. 수행되는 MoveNext
정확한 동작은 호출될 때 MoveNext
열거자 개체의 상태에 따라 달라집니다.
- 열거자 개체의 상태가 이전 상태이
MoveNext
- 상태를 실행 중으로 변경합니다.
- 반복기 블록의 매개 변수(포함
this
)를 열거자 개체가 초기화될 때 저장된 인수 값 및 인스턴스 값으로 초기화합니다. - 아래 설명된 대로 실행이 중단될 때까지 처음부터 반복기 블록을 실행합니다.
- 열거자 개체의 상태가 실행 중이면 호출
MoveNext
결과가 지정되지 않습니다. - 열거자 개체 의 상태가 일시 중단된 경우 MoveNext를 호출합니다.
- 상태를 실행 중으로 변경합니다.
- 반복기 블록의 실행이 마지막으로 일시 중단되었을 때 저장된 값으로 모든 지역 변수 및 매개 변수(포함
this
)의 값을 복원합니다.참고: 이러한 변수에서 참조하는 개체의 내용은 이전 호출
MoveNext
이후 변경되었을 수 있습니다. 끝 메모 - 실행 일시 중단을 발생시킨 yield return 문 바로 다음에 반복기 블록의 실행을 다시 시작하고 아래 설명된 대로 실행이 중단될 때까지 계속됩니다.
- 열거자 개체의 상태가 이후이면 호출하면 false가
MoveNext
반환됩니다.
반복기 블록을 실행하면 MoveNext
명령문, 문, yield return
반복기 블록의 끝을 발견하여 실행이 중단 yield break
될 수 있으며 반복기 블록에서 throw 및 전파되는 예외가 있습니다.
-
yield return
문이 발견되면(§9.4.4.20):- 문에 지정된 식은 계산되고 암시적으로 수율 형식으로 변환되며 열거자 개체의 속성에
Current
할당됩니다. - 반복기 본문의 실행이 일시 중단됩니다. 이
this
문의 위치와 마찬가지로 모든 지역 변수 및 매개 변수(포함yield return
)의 값이 저장됩니다.yield return
문이 하나 이상의try
블록 내에 있는 경우 연결된 최종 블록은 현재 실행되지 않습니다. - 열거자 개체의 상태가 일시 중단됨으로 변경됩니다.
- 메서드는
MoveNext
해당 호출자에게 반환true
되며, 이는 반복이 다음 값으로 성공적으로 진행되었음을 나타냅니다.
- 문에 지정된 식은 계산되고 암시적으로 수율 형식으로 변환되며 열거자 개체의 속성에
-
yield break
문이 발견되면(§9.4.4.20):-
yield break
문이 하나 이상의try
블록 내에 있으면 연결된finally
블록이 실행됩니다. - 열거자 개체의 상태가 After로 변경됩니다.
- 메서드가
MoveNext
호출자에게 반환false
되어 반복이 완료되었음을 나타냅니다.
-
- 반복기 본문의 끝이 발견되면 다음을 수행합니다.
- 열거자 개체의 상태가 After로 변경됩니다.
- 메서드가
MoveNext
호출자에게 반환false
되어 반복이 완료되었음을 나타냅니다.
- 예외가 throw되고 반복기 블록 밖으로 전파되는 경우:
- 반복기 본문의 적절한
finally
블록은 예외 전파에 의해 실행됩니다. - 열거자 개체의 상태가 After로 변경됩니다.
- 예외 전파는 메서드의
MoveNext
호출자에게 계속됩니다.
- 반복기 본문의 적절한
15.14.5.3 현재 속성
열거자 개체의 Current
속성은 반복기 블록의 문에 의해 yield return
영향을 받습니다.
열거자 개체가 일시 중단된 상태인 경우 값 Current
은 이전 호출 MoveNext
에서 설정한 값입니다. 열거자 개체가 이전, 실행 중 또는 이후 상태에 있는 경우 액세스 결과가 지정되지 않습니다.Current
수율 형식이 아닌 object
다른 반복기의 경우 열거자 개체의 구현을 통해 액세스한 Current
결과는 열거자 개체의 IEnumerable
Current
구현을 통해 액세스하고 IEnumerator<T>
결과를 캐스팅하는 데 object
해당합니다.
15.14.5.4 Dispose 메서드
이 Dispose
메서드는 열거자 개체를 after 상태로 가져와서 반복을 정리하는 데 사용됩니다.
- 열거자 개체의 상태가 이전인 경우 호출하면 상태가 후
Dispose
변경됩니다. - 열거자 개체의 상태가 실행 중이면 호출
Dispose
결과가 지정되지 않습니다. - 열거자 개체 의 상태가 일시 중단된 경우 다음을 호출합니다
Dispose
.- 상태를 실행 중으로 변경합니다.
- 마지막으로 실행된
yield return
문이 문yield break
인 것처럼 마지막 블록을 실행합니다. 이로 인해 예외가 throw되고 반복기 본문 밖으로 전파되면 열거자 개체의 상태가 후로 설정되고 예외가 메서드의Dispose
호출자로 전파됩니다. - 상태를 다음으로 변경합니다.
- 열거자 개체의 상태가 이후이면 호출에
Dispose
영향을 주지 않습니다.
15.14.6 열거 가능한 개체
15.14.6.1 일반
반복기 블록을 사용하여 열거 가능한 인터페이스 형식을 반환하는 함수 멤버를 구현하는 경우 함수 멤버를 호출해도 반복기 블록에서 코드를 즉시 실행하지는 않습니다. 대신 열거 가능한 개체 가 만들어지고 반환됩니다. 열거 가능 개체의 GetEnumerator
메서드는 반복기 블록에 지정된 코드를 캡슐화하는 열거자 개체를 반환하고, 반복기 블록의 코드 실행은 열거자 개체의 MoveNext
메서드를 호출할 때 발생합니다. 열거 가능한 개체의 특징은 다음과 같습니다.
- 반복기의 수율 형식을 구현
IEnumerable
하고IEnumerable<T>
여기서T
구현합니다. - 함수 멤버에 전달된 인수 값(있는 경우) 및 인스턴스 값의 복사본을 사용하여 초기화됩니다.
열거 가능한 개체는 일반적으로 반복기 블록의 코드를 캡슐화하고 열거 가능한 인터페이스를 구현하는 컴파일러 생성 열거 가능 클래스의 인스턴스이지만 다른 구현 방법이 가능합니다. 컴파일러에서 열거 가능한 클래스를 생성하는 경우 해당 클래스는 함수 멤버를 포함하는 클래스에서 직접 또는 간접적으로 중첩되고 프라이빗 접근성을 가지며 컴파일러 사용을 위해 예약된 이름(§6.4.3)을 갖습니다.
열거 가능한 개체는 위에서 지정한 인터페이스보다 더 많은 인터페이스를 구현할 수 있습니다.
참고: 예를 들어 열거 가능한 개체를 구현
IEnumerator
하여IEnumerator<T>
열거 가능 개체와 열거자 모두로 사용할 수 있습니다. 일반적으로 이러한 구현은 첫 번째 호출에서 할당을 저장하기 위해 자체 인스턴스를 반환합니다GetEnumerator
. 이후 호출은GetEnumerator
새 클래스 인스턴스(일반적으로 동일한 클래스)를 반환하므로 다른 열거자 인스턴스에 대한 호출은 서로 영향을 주지 않습니다. 이전 열거자가 이미 시퀀스의 끝을 지나 열거한 경우에도 동일한 인스턴스를 반환할 수 없습니다. 이후의 모든 열거자가 모두 예외를 throw해야 하므로 끝 메모
15.14.6.2 GetEnumerator 메서드
열거 가능한 개체는 및 GetEnumerator
인터페이스의 IEnumerable
메서드 구현을 IEnumerable<T>
제공합니다. 두 GetEnumerator
메서드는 사용 가능한 열거자 개체를 획득하고 반환하는 공통 구현을 공유합니다. 열거자 개체는 열거 가능한 개체가 초기화될 때 저장된 인수 값과 인스턴스 값으로 초기화되지만 그렇지 않으면 §15.14.5에 설명된 대로 열거자 개체가 작동합니다.
15.15 비동기 함수
15.15.1 일반
한정자가 있는 메서드(§15.6) 또는 무명 함수(§12.19)를 비동기 함수async
. 일반적으로 비동기라는 용어는 한정자가 있는 모든 종류의 함수를 async
설명하는 데 사용됩니다.
형식의 매개 변수 또는 매개 변수를 지정in
out
하는 것은 비동기 함수의 매개 변수 목록에 대한 컴파일 시간 오류입니다ref
.ref struct
비동기 메서드의 return_type 작업 유형이어야 void
합니다. 결과 값을 생성하는 비동기 메서드의 경우 작업 유형은 제네릭이어야 합니다. 결과 값을 생성하지 않는 비동기 메서드의 경우 작업 유형은 제네릭이 아니어야 합니다. 이러한 형식은 이 사양에서 각각과 «TaskType»<T>
같이 «TaskType»
참조됩니다. 생성된 System.Threading.Tasks.Task
표준 라이브러리 형식 System.Threading.Tasks.Task<TResult>
및 형식은 작업 유형뿐만 아니라 특성을 통해 작업 작성기 형식과 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
입니다. 이러한 형식은 이 사양에서 다음과 같이 «TaskBuilderType»<T>
참조됩니다 «TaskBuilderType»
. 작업 형식에는 최대 하나의 형식 매개 변수가 있을 수 있으며 제네릭 형식에 중첩될 수 없습니다.
작업 유형을 반환하는 비동기 메서드는 작업 반환이라고 합니다.
작업 유형은 정확한 정의에 따라 달라질 수 있지만 언어의 관점에서 작업 유형은 불완전하거나 성공했거나 오류가 발생한 상태 중 하나입니다.
오류가 발생한 작업은 관련 예외를 기록합니다. 성공하면 «TaskType»<T>
형식T
의 결과가 기록됩니다. 작업 형식은 대기 가능하므로 작업은 await 식의 피연산자일 수 있습니다(§12.9.8).
예: 작업 유형
MyTask<T>
은 작업 작성기 유형 및 awaiter 형식MyTaskMethodBuilder<T>
Awaiter<T>
과 연결됩니다.using System.Runtime.CompilerServices; [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))] class MyTask<T> { public Awaiter<T> GetAwaiter() { ... } } class Awaiter<T> : INotifyCompletion { public void OnCompleted(Action completion) { ... } public bool IsCompleted { get; } public T GetResult() { ... } }
끝 예제
작업 작성기 형식은 특정 작업 유형(§15.15.2)에 해당하는 클래스 또는 구조체 형식입니다. 작업 작성기 유형은 해당 작업 유형의 선언된 접근성과 정확히 일치해야 합니다.
참고: 작업 유형이 선언된
internal
경우 해당 작성기 형식도 선언되고internal
동일한 어셈블리에 정의되어야 합니다. 작업 유형이 다른 형식 내에 중첩된 경우 작업 부이더 형식도 동일한 형식으로 중첩되어야 합니다. 끝 메모
비동기 함수는 본문에서 await 식(§12.9.8)을 통해 평가를 일시 중단하는 기능을 줍니다. 나중에 다시 시작 대리자를 통해 일시 중단 대기 식의 지점에서 평가를 재개할 수 있습니다. 다시 시작 대리자는 형식 System.Action
이며 호출될 때 비동기 함수 호출의 평가는 중단된 await 식에서 다시 시작됩니다.
비동기 함수 호출의 현재 호출자는 함수 호출이 일시 중단된 적이 없는 경우 원래 호출자이거나, 그렇지 않으면 다시 시작 대리자의 가장 최근 호출자입니다.
15.15.2 작업 유형 작성기 패턴
작업 작성기 형식에는 최대 하나의 형식 매개 변수가 있을 수 있으며 제네릭 형식에 중첩될 수 없습니다. 작업 작성기 유형에는 선언된 SetResult
접근성을 가진 다음 멤버(제네릭이 아닌 작업 작성기 형식의 public
경우 매개 변수가 없음)가 있어야 합니다.
class «TaskBuilderType»<T>
{
public static «TaskBuilderType»<T> Create();
public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine;
public void SetStateMachine(IAsyncStateMachine stateMachine);
public void SetException(Exception exception);
public void SetResult(T result);
public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine;
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
public «TaskType»<T> Task { get; }
}
컴파일러는 «TaskBuilderType»을 사용하여 비동기 함수의 평가를 일시 중단하고 다시 시작하기 위한 의미 체계를 구현하는 코드를 생성해야 합니다. 컴파일러는 다음과 같이 «TaskBuilderType»을 사용해야 합니다.
-
«TaskBuilderType».Create()
이 목록에 이름이 지정된builder
«TaskBuilderType»의 인스턴스를 만들기 위해 호출됩니다. -
builder.Start(ref stateMachine)
는 컴파일러에서 생성된 상태 컴퓨터 인스턴스stateMachine
와 작성기를 연결하기 위해 호출됩니다.- 빌더는 상태 머신을
stateMachine.MoveNext()
Start()
진행하기 위해 반환된 후 호출Start()
해야 합니다.
- 빌더는 상태 머신을
- 반환 후
Start()
메서드는async
비동기 메서드에서 반환할 태스크에 대해 호출builder.Task
합니다. - 호출할
stateMachine.MoveNext()
때마다 상태 컴퓨터가 진행됩니다. - 상태 컴퓨터가 성공적으로
builder.SetResult()
완료되면 메서드 반환 값(있는 경우)을 사용하여 호출됩니다. - 그렇지 않으면 상태 컴퓨터
e
에서 예외builder.SetException(e)
가 throw되면 호출됩니다. - 상태 컴퓨터가 식
await expr
에expr.GetAwaiter()
도달하면 호출됩니다. - awaiter가 구현
ICriticalNotifyCompletion
되고IsCompleted
false이면 상태 컴퓨터가 호출됩니다builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitUnsafeOnCompleted()
는 awaiter가awaiter.UnsafeOnCompleted(action)
완료되면 해당 호출을 사용하여 호출Action
stateMachine.MoveNext()
해야 합니다.
-
- 그렇지 않으면 상태 컴퓨터가 호출합니다
builder.AwaitOnCompleted(ref awaiter, ref stateMachine)
.-
AwaitOnCompleted()
는 awaiter가awaiter.OnCompleted(action)
완료되면 해당 호출을 사용하여 호출Action
stateMachine.MoveNext()
해야 합니다.
-
-
SetStateMachine(IAsyncStateMachine)
는 컴파일러 생성IAsyncStateMachine
구현에 의해 호출되어 상태 컴퓨터 인스턴스와 연결된 작성기의 인스턴스를 식별할 수 있으며, 특히 상태 컴퓨터가 값 형식으로 구현되는 경우를 나타냅니다.- 작성기에서 호출
stateMachine.SetStateMachine(stateMachine)
하는 경우 연결된 작성기 인스턴스를builder.SetStateMachine(stateMachine)
.stateMachine
- 작성기에서 호출
참고: 둘 다
SetResult(T result)
의«TaskType»<T> Task { get; }
경우 매개 변수와 인수는 각각 ID로 변환할 수T
있어야 합니다. 이렇게 하면 작업 유형 작성기에서 튜플과 같은 형식을 지원할 수 있습니다. 여기서 동일하지 않은 두 형식은 ID 변환이 가능합니다. 끝 메모
15.15.3 작업 반환 비동기 함수 평가
작업 반환 비동기 함수를 호출하면 반환된 작업 유형의 인스턴스가 생성됩니다. 이를 비동기 함수의 반환 작업 이라고 합니다. 작업은 처음에 불완전한 상태입니다.
그런 다음 비동기 함수 본문은 대기 식에 도달하여 일시 중단되거나 종료될 때까지 평가되며, 이때 반환 작업과 함께 지점 컨트롤이 호출자에게 반환됩니다.
비동기 함수의 본문이 종료되면 반환 작업이 불완전한 상태에서 이동합니다.
- 함수 본문이 반환 문 또는 본문의 끝에 도달한 결과로 종료되는 경우 모든 결과 값이 반환 작업에 기록되어 성공 상태로 전환됩니다.
- 함수 본문이 catch
OperationCanceledException
되지 않아 종료되면 취소된 상태로 전환되는 반환 작업에 예외가 기록됩니다 . - 함수 본문이 다른 catch되지 않은 예외(§13.10.6)의 결과로 종료되는 경우 예외는 오류 상태로 전환 되는 반환 작업에 기록됩니다 .
15.15.4 void 반환 비동기 함수 평가
비동기 함수의 반환 형식인 경우 평가는 void
다음과 같은 방식으로 위와 다릅니다. 작업이 반환되지 않으므로 함수는 대신 완료 및 예외를 현재 스레드의 동기화 컨텍스트에 전달합니다. 동기화 컨텍스트의 정확한 정의는 구현에 따라 달라지지만 현재 스레드가 실행되고 있는 "위치"의 표현입니다. 반환되는 비동기 함수의 void
평가가 시작되거나, 성공적으로 완료되거나, catch되지 않은 예외가 throw될 때 동기화 컨텍스트에 알림이 표시됩니다.
이렇게 하면 컨텍스트에서 실행 중인 반환되는 비동기 함수 수를 void
추적하고 해당 함수에서 나오는 예외를 전파하는 방법을 결정할 수 있습니다.
ECMA C# draft specification