7가지 기본 개념
7.1 애플리케이션 시작
프로그램은 다른 애플리케이션의 일부로 사용할 클래스 라이브러리 또는 직접 시작할 수 있는 애플리케이션으로 컴파일될 수 있습니다. 이 컴파일 모드를 결정하는 메커니즘은 구현 정의이며 이 사양의 외부입니다.
애플리케이션으로 컴파일된 프로그램에는 다음 요구 사항을 충족하여 진입점으로 한정되는 하나 이상의 메서드가 포함되어야 합니다.
- 이름을
Main
갖습니다. - 될 것이다
static
. - 제네릭하지 않습니다.
- 제네릭이 아닌 형식으로 선언되어야 합니다. 메서드를 선언하는 형식이 중첩 형식인 경우 해당 바깥쪽 형식 중 어느 것도 제네릭일 수 없습니다.
- 메서드의 반환 형식이
System.Threading.Tasks.Task
또는System.Threading.Tasks.Task<int>
일 경우async
한정자를 가질 수 있습니다. - 반환 형식은 ,
void
,int
또는System.Threading.Tasks.Task
.이어야System.Threading.Tasks.Task<int>
합니다. - 구현 없이는 부분 메서드(§15.6.9)가 아니어야 합니다.
- 매개 변수 목록은 비어 있거나 형식
string[]
의 단일 값 매개 변수가 있어야 합니다.
참고: 한정자가 있는
async
메서드는 진입점으로 한정하려면 위에 지정된 두 반환 형식 중 하나만 있어야 합니다.async void
메서드, 또는async
,ValueTask
나ValueTask<int>
와 같은 다른 대기 가능 형식을 반환하는 메서드는 진입점으로 한정되지 않습니다. 끝 메모
프로그램 내에서 진입점으로 한정된 메서드가 두 개 이상인 경우 외부 메커니즘을 사용하여 애플리케이션의 실제 진입점으로 간주되는 메서드를 지정할 수 있습니다. 반환 형식이 int
또는 void
인 한정 메서드가 발견되면, 반환 형식이 System.Threading.Tasks.Task
또는 System.Threading.Tasks.Task<int>
인 한정 메서드는 진입점 메서드로 간주되지 않습니다. 프로그램이 정확히 하나의 진입점 없이 애플리케이션으로 컴파일되는 것은 컴파일 시간 오류입니다. 클래스 라이브러리로 컴파일된 프로그램에는 애플리케이션 진입점으로 한정되는 메서드가 포함될 수 있지만 결과 라이브러리에는 진입점이 없습니다.
일반적으로 메서드의 선언된 접근성(§7.5.2)은 선언에 지정된 액세스 한정자(§15.3.6)에 의해 결정되며, 마찬가지로 형식의 선언된 접근성은 선언에 지정된 액세스 한정자에 의해 결정됩니다. 지정된 형식의 지정된 메서드를 호출할 수 있게 하려면 형식과 멤버 모두에 액세스할 수 있어야 합니다. 그러나 애플리케이션 진입점은 특별한 경우입니다. 특히 실행 환경은 선언된 접근성과 바깥쪽 형식 선언의 선언된 접근성에 관계없이 애플리케이션의 진입점에 액세스할 수 있습니다.
진입점 메서드의 반환 형식이 System.Threading.Tasks.Task
또는 System.Threading.Tasks.Task<int>
경우 컴파일러는 해당 Main
메서드를 호출하는 동기 진입점 메서드를 합성해야 합니다. 메서드 Main
를 기반으로 하는 합성된 메서드는 매개 변수와 반환 형식을 갖습니다.
- 합성된 메서드의 매개 변수 목록은 메서드의
Main
매개 변수 목록과 동일합니다. - 메서드의
Main
반환 형식이System.Threading.Tasks.Task
면 합성된 메서드의 반환 형식은void
- 메서드의
Main
반환 형식이System.Threading.Tasks.Task<int>
면 합성된 메서드의 반환 형식은int
합성된 메서드의 실행은 다음과 같이 진행됩니다.
- 합성된 메서드는 메서드를 호출하여
Main
메서드에string[]
이러한 매개 변수가 있는 경우Main
해당 매개 변수 값을 인수로 전달합니다. - 메서드가
Main
예외를 throw하는 경우 예외는 합성된 메서드에 의해 전파됩니다. - 그렇지 않으면 합성된 진입점은 반환된 작업이 완료될 때까지 기다렸다가 매개 변수가 없는 인스턴스 메서드 또는 §C.3에 설명된 확장 메서드를 사용하여
GetAwaiter().GetResult()
작업을 호출합니다. 작업이 실패하면GetResult()
에서 예외가 발생하고, 이 예외는 합성된 메서드에 의해 전파됩니다. -
Main
반환 형식이 있는 메서드의 경우, 작업이int
성공적으로 완료되면,GetResult()
에서 반환된 값이 합성된 메서드에서 반환됩니다.
애플리케이션의 유효 진입점은 프로그램 내에서 선언된 진입점이거나 위에서 설명한 대로 필요한 경우 합성된 메서드입니다. 따라서 유효 진입점의 반환 형식은 항상 void
또는 int
.
애플리케이션이 실행되면 새 애플리케이션 도메인 이 만들어집니다. 애플리케이션의 여러 인스턴스화가 동시에 동일한 컴퓨터에 있을 수 있으며 각각 자체 애플리케이션 도메인이 있습니다. 애플리케이션 도메인은 애플리케이션 상태의 컨테이너 역할을 하여 애플리케이션 격리를 가능하게 합니다. 애플리케이션 도메인은 애플리케이션 및 애플리케이션에서 사용하는 클래스 라이브러리에 정의된 형식에 대한 컨테이너 및 경계 역할을 합니다. 한 애플리케이션 도메인에 로드된 형식은 다른 애플리케이션 도메인에 로드된 동일한 형식과 구별되며 개체 인스턴스는 애플리케이션 도메인 간에 직접 공유되지 않습니다. 예를 들어 각 애플리케이션 도메인에는 이러한 형식에 대한 정적 변수의 자체 복사본이 있으며, 형식에 대한 정적 생성자는 애플리케이션 도메인당 최대 한 번 실행됩니다. 구현은 애플리케이션 도메인을 만들고 소멸하기 위한 구현 정의 정책 또는 메커니즘을 자유롭게 제공할 수 있습니다.
애플리케이션 시작은 실행 환경에서 애플리케이션의 유효 진입점을 호출할 때 발생합니다. 유효 진입점이 매개 변수를 선언하는 경우 애플리케이션을 시작하는 동안 구현은 해당 매개 변수의 초기 값이 문자열 배열에 대한 null이 아닌 참조인지 확인해야 합니다. 이 배열은 애플리케이션을 시작하기 전에 호스트 환경에서 구현 정의 값을 제공하는 애플리케이션 매개 변수라고 하는 문자열에 대한 null이 아닌 참조로 구성되어야 합니다. 호스트된 환경의 다른 위치에서 애플리케이션을 시작하기 전에 결정된 애플리케이션 정보를 제공하려는 의도입니다.
참고: 명령줄을 지원하는 시스템에서 애플리케이션 매개 변수는 일반적으로 명령줄 인수라고 하는 것과 일치합니다. 끝 메모
유효 진입점의 반환 형식이 int
면 실행 환경에 의한 메서드 호출의 반환 값이 애플리케이션 종료(§7.2)에 사용됩니다.
위에 나열된 상황 외에 진입점 메서드는 모든 측면에서 진입점이 아닌 것과 같이 동작합니다. 특히 일반 메서드 호출과 같이 애플리케이션 수명 동안 다른 지점에서 진입점이 호출되는 경우 메서드의 특수 처리는 없습니다. 매개 변수가 있는 경우 초기 값이 null
있거나 null 참조가 포함된 배열을 참조하는 값이 아닐null
수 있습니다. 마찬가지로 진입점의 반환 값은 실행 환경에서 호출하는 것 외에는 특별한 의미가 없습니다.
7.2 애플리케이션 종료
애플리케이션 종료 는 실행 환경에 대한 제어를 반환합니다.
애플리케이션의 유효 진입점 메서드의 반환 형식이 int
예외 없이 실행이 완료되는 경우 반환된 값 int
은 애플리케이션의 종료 상태 코드로 사용됩니다. 이 코드의 목적은 실행 환경에 대한 성공 또는 실패 통신을 허용하는 것입니다. 유효 진입점 메서드의 반환 형식이 void
예외 없이 실행이 완료되면 종료 상태 코드가 됩니다 0
.
예외(§21.4)로 인해 유효 진입점 메서드가 종료되는 경우 종료 코드는 구현으로 정의됩니다. 또한 구현은 종료 코드를 지정하기 위한 대체 API를 제공할 수 있습니다.
종료자(§15.13)가 애플리케이션 종료의 일부로 실행되는지 여부는 구현에서 정의됩니다.
참고: .NET Framework 구현은, 라이브러리 메서드
GC.SuppressFinalize
의 호출과 같은 방식으로 정리가 억제되지 않는 한, 가비지 수집되지 않은 모든 개체에 대한 종료자(§15.13)를 호출하기 위해 모든 합리적인 노력을 기울입니다. 끝 메모
7.3 선언
C# 프로그램의 선언은 프로그램의 구성 요소를 정의합니다. C# 프로그램은 네임스페이스를 사용하여 구성됩니다. 형식 선언 및 중첩된 네임스페이스 선언을 포함할 수 있는 네임스페이스 선언(§14)을 사용하여 도입되었습니다. 형식 선언(§14.7)은 클래스(§15), 구조체(§16), 인터페이스(§18), 열거형(§19) 및 대리자(§20)를 정의하는 데 사용됩니다. 형식 선언에서 허용되는 멤버의 종류는 형식 선언의 형식에 따라 달라집니다. 예를 들어 클래스 선언에는 상수(§15.4), 필드(§15.5), 메서드(§15.6), 속성(§15.7), 이벤트(§15.8), 인덱서(§15.9), 연산자(§15.10), 인스턴스 생성자(§15.11), 정적 생성자(§15.12), 종료자(§15.13) 및 중첩 형식(§15.3.9)에 대한 선언이 포함될 수 있습니다.
선언은 선언이 속한 선언 공간에 이름을 정의합니다. 다음 경우를 제외하고 선언 공간에 이름이 같은 멤버를 도입하는 두 개 이상의 선언이 있는 것은 컴파일 시간 오류입니다.
- 이름이 같은 두 개 이상의 네임스페이스 선언이 동일한 선언 공간에서 허용됩니다. 이러한 네임스페이스 선언은 단일 논리 네임스페이스를 형성하고 단일 선언 공간을 공유하기 위해 집계됩니다.
- 별도의 프로그램이지만 동일한 네임스페이스 선언 공간에 있는 선언은 동일한 이름을 공유할 수 있습니다.
참고: 그러나 이러한 선언은 동일한 애플리케이션에 포함된 경우 모호성을 발생시킬 수 있습니다. 끝 메모
- 이름이 같지만 고유 서명이 있는 두 개 이상의 메서드가 동일한 선언 공간(§7.6)에서 허용됩니다.
- 이름이 같지만 형식 매개 변수의 고유 번호를 가진 두 개 이상의 형식 선언은 동일한 선언 공간(§7.8.2)에서 허용됩니다.
- 동일한 선언 공간에 부분 한정자가 있는 두 개 이상의 형식 선언은 동일한 이름, 동일한 수의 형식 매개 변수 및 동일한 분류(클래스, 구조체 또는 인터페이스)를 공유할 수 있습니다. 이 경우 형식 선언은 단일 형식에 기여하며 자체 집계되어 단일 선언 공간(§15.2.7)을 형성합니다.
- 형식 선언에 하나 이상의 형식 매개 변수(§7.8.2)가 있는 한 동일한 선언 공간에 있는 네임스페이스 선언 및 형식 선언은 동일한 이름을 공유할 수 있습니다.
다음에 설명된 대로 여러 가지 유형의 선언 공간이 있습니다.
- 프로그램의 모든 컴파일 단위에 존재하는 namespace_member_declaration가 namespace_declaration에 의해 감싸여 있지 않을 경우, 해당 선언들은 전역 선언 공간이라는 단일 결합 선언 공간의 멤버입니다.
- 프로그램의 모든 컴파일 단위 내에서, namespace_declaration 내의 namespace_member_declaration 중에서 완전히 지정된 네임스페이스 이름이 동일한 경우, 단일 결합 선언 공간의 멤버가 됩니다.
- 각 compilation_unit 및 namespace_body 별칭 선언 공간이 있습니다. compilation_unit 또는 namespace_body의 각 extern_alias_directive 및 using_alias_directive는 별칭 선언 공간 (§14.5.2)에 멤버를 제공합니다.
- 각 비 부분 클래스, 구조체 또는 인터페이스 선언은 새 선언 공간을 만듭니다. 각 부분 클래스, 구조체 또는 인터페이스 선언은 동일한 프로그램의 일치하는 모든 파트가 공유하는 선언 공간에 기여합니다(§16.2.4). 이름은 class_member_declaration, struct_member_declaration, interface_member_declaration 또는 type_parameter 선언을 통해 이 선언 공간에 추가됩니다. 오버로드된 인스턴스 생성자 선언 및 정적 생성자 선언을 제외하고 클래스 또는 구조체는 클래스 또는 구조체와 이름이 같은 멤버 선언을 포함할 수 없습니다. 클래스, 구조체 또는 인터페이스는 오버로드된 메서드 및 인덱서의 선언을 허용합니다. 또한 클래스 또는 구조체는 오버로드된 인스턴스 생성자 및 연산자의 선언을 허용합니다. 예를 들어 클래스, 구조체 또는 인터페이스는 이러한 메서드 선언이 시그니처(§7.6)와 다른 경우 이름이 같은 여러 메서드 선언을 포함할 수 있습니다. 기본 클래스는 클래스의 선언 공간에 기여하지 않으며 기본 인터페이스는 인터페이스의 선언 공간에 기여하지 않습니다. 따라서 파생 클래스 또는 인터페이스는 상속된 멤버와 이름이 같은 멤버를 선언할 수 있습니다. 이러한 멤버는 상속된 멤버를 숨깁니다.
- 각 대리자 선언은 새 선언 공간을 만듭니다. 이름은 매개 변수(fixed_parameter 및 parameter_array)와 type_parameter통해 이 선언 공간에 도입됩니다.
- 각 열거형 선언은 새 선언 공간을 만듭니다. 이름은 enum_member_declarations을 통해 이 선언 공간에 도입됩니다.
- 각 메서드 선언, 속성 선언, 속성 접근자 선언, 인덱서 선언, 인덱서 접근자 선언, 연산자 선언, 인스턴스 생성자 선언, 익명 함수 및 로컬 함수는 지역 변수 선언 공간이라는 새 선언 공간을 만듭니다. 이름은 매개 변수(fixed_parameter 및 parameter_array)와 type_parameter통해 이 선언 공간에 도입됩니다. 속성 또는 인덱서의 set 접근자는 이름을
value
매개 변수로 도입합니다. 함수 멤버, 무명 함수 또는 로컬 함수의 본문(있는 경우)은 지역 변수 선언 공간 내에 중첩된 것으로 간주됩니다. 지역 변수 선언 공간과 중첩된 지역 변수 선언 공간에 이름이 같은 요소가 있는 경우 중첩된 로컬 이름의 범위 내에서 중첩된 로컬 이름으로 외부 로컬 이름이 숨겨집니다(§7.7.1). - 추가 지역 변수 선언 공간은 멤버 선언, 익명 함수 및 로컬 함수 내에서 발생할 수 있습니다. 이름은 패턴, declaration_expression, declaration_statement 및 exception_specifier을 통해 이러한 선언 공간에 도입됩니다. 지역 변수 선언 공간은 중첩될 수 있지만 지역 변수 선언 공간과 중첩된 지역 변수 선언 공간에 동일한 이름의 요소가 포함되는 것은 오류입니다. 따라서 중첩된 선언 공간 내에서는 매개 변수, 형식 매개 변수, 지역 변수, 로컬 함수 또는 상수와 이름이 같은 지역 변수, 로컬 함수 또는 상수를 바깥쪽 선언 공간에서 선언할 수 없습니다. 두 선언 공간 모두 다른 선언 공간을 포함하지 않는 한 두 선언 공간에 동일한 이름의 요소가 포함될 수 있습니다. 로컬 선언 공간은 다음 구문에 의해 생성됩니다.
- 필드 및 속성 선언의 각 variable_initializer 다른 지역 변수 선언 공간 내에 중첩되지 않은 고유한 지역 변수 선언 공간을 도입합니다.
- 함수 멤버, 무명 함수 또는 로컬 함수의 본문(있는 경우)은 함수의 지역 변수 선언 공간 내에 중첩된 것으로 간주되는 지역 변수 선언 공간을 만듭니다.
- 각 constructor_initializer 인스턴스 생성자 선언 내에 중첩된 지역 변수 선언 공간을 만듭니다. 생성자 본문에 대한 지역 변수 선언 공간은 차례로 이 지역 변수 선언 공간 내에 중첩됩니다.
- 각 블록, switch_block, specific_catch_clause, iteration_statement 및 using_statement 중첩된 지역 변수 선언 공간을 만듭니다.
- statement_list
에 직접 포함되지 않은 각 embedded_statement는 중첩된 지역 변수 선언 공간을 만듭니다. - 각 switch_section 중첩된 지역 변수 선언 공간을 만듭니다. 그러나 switch_section의 statement_list 내에서 직접 선언된 변수는 (하지만 statement_list 안의 중첩된 지역 변수 선언 공간에 있지 않은 경우) switch_section 대신에 바깥쪽 switch_block의 지역 변수 선언 공간에 직접 추가됩니다.
- query_expression 구문 변환(§12.20.3)은 하나 이상의 람다 식을 도입할 수 있습니다. 익명 함수로서 각 함수는 위에서 설명한 대로 지역 변수 선언 공간을 만듭니다.
- 각 블록 또는 switch_block 레이블에 대한 별도의 선언 공간을 만듭니다. 이름은 labeled_statement를 통해 이 선언 공간에 도입되고, goto_statement를 통해 참조됩니다. 블록의 레이블 선언 공간에는 중첩된 블록이 포함됩니다. 따라서 중첩된 블록 내에서는 바깥쪽 블록의 레이블과 이름이 같은 레이블을 선언할 수 없습니다.
참고: switch_section 내에서 직접 선언된 변수가 switch_section 대신 switch_block 지역 변수 선언 공간에 추가된다는 사실은 놀라운 코드로 이어질 수 있습니다. 아래 예제에서 지역 변수
y
는 case 0의 스위치 섹션에 표시되는 선언에도 불구하고 기본 사례에 대한 switch 섹션 내의 범위에 있습니다. 지역 변수z
는 선언이 발생하는 스위치 섹션의 지역 변수 선언 공간에 도입되므로 기본 사례에 대한 switch 섹션 내의 범위에 없습니다.int x = 1; switch (x) { case 0: int y; break; case var z when z < 10: break; default: y = 10; // Valid: y is in scope Console.WriteLine(x + y); // Invalid: z is not scope Console.WriteLine(x + z); break; }
끝 메모
이름이 선언되는 텍스트 순서는 일반적으로 의미가 없습니다. 특히 텍스트 순서는 네임스페이스, 상수, 메서드, 속성, 이벤트, 인덱서, 연산자, 인스턴스 생성자, 종료자, 정적 생성자 및 형식의 선언 및 사용에 중요하지 않습니다. 선언 순서는 다음과 같은 방법으로 중요합니다.
- 필드 선언에 대한 선언 순서는 이니셜라이저(있는 경우)가 실행되는 순서를 결정합니다(§15.5.6.2, §15.5.6.3).
- 지역 변수는 사용 전에 정의해야 합니다(§7.7).
- 열거형 멤버 선언(§19.4)의 선언 순서는 constant_expression 값을 생략할 때 중요합니다.
예: 네임스페이스의 선언 공간은 "개방형"이며, 동일하게 정규화된 이름을 가진 두 네임스페이스 선언은 동일한 선언 공간에 기여합니다. 예를 들어
namespace Megacorp.Data { class Customer { ... } } namespace Megacorp.Data { class Order { ... } }
위의 두 네임스페이스 선언은 동일한 선언 공간에 기여합니다. 이 경우 정규화된 이름과
Megacorp.Data.Customer
Megacorp.Data.Order
2개의 클래스를 선언합니다. 두 선언이 동일한 선언 공간에 기여하기 때문에 각 선언에 동일한 이름의 클래스 선언이 포함된 경우 컴파일 시간 오류가 발생했습니다.끝 예제
참고: 위에서 지정한 대로 블록의 선언 공간에는 중첩된 블록이 포함됩니다. 따라서 다음 예제에서
F
메서드와G
메서드는 외부 블록에서i
이라는 이름이 선언되어 있기 때문에 내부 블록에서 동일한 이름을 사용할 수 없어 컴파일 시간 오류가 발생합니다. 그러나H
메서드와I
메서드는 별도의 중첩되지 않은 블록에i
두 개가 선언되었기 때문에 유효합니다.class A { void F() { int i = 0; if (true) { int i = 1; } } void G() { if (true) { int i = 0; } int i = 1; } void H() { if (true) { int i = 0; } if (true) { int i = 1; } } void I() { for (int i = 0; i < 10; i++) { H(); } for (int i = 0; i < 10; i++) { H(); } } }
끝 메모
7.4 회원
7.4.1 일반
네임스페이스와 형식에는 멤버가 있습니다.
참고: 엔터티의 멤버는 일반적으로 엔터티에 대한 참조로 시작하는 정규화된 이름과 "
.
" 토큰, 멤버 이름을 사용하여 사용할 수 있습니다. 끝 메모
형식의 멤버는 형식 선언에서 선언되거나 형식의 기본 클래스에서 상속됩니다 . 형식이 기본 클래스에서 상속되는 경우 인스턴스 생성자, 종료자 및 정적 생성자를 제외한 기본 클래스의 모든 멤버는 파생 형식의 멤버가 됩니다. 기본 클래스 멤버의 선언된 접근성은 멤버가 상속되는지 여부를 제어하지 않습니다. 상속은 인스턴스 생성자, 정적 생성자 또는 종료자가 아닌 멤버로 확장됩니다.
그러나 상속된 멤버는 예를 들어 선언된 접근성(§7.5.2)으로 인해 파생 형식에서 액세스할 수 없을 수 있습니다. 끝 메모
7.4.2 네임스페이스 멤버
바깥쪽 네임스페이스가 없는 네임스페이스 및 형식은 전역 네임스페이스의 멤버입니다. 이는 전역 선언 공간에 선언된 이름에 직접 해당합니다.
네임스페이스 내에 선언된 네임스페이스 및 형식은 해당 네임스페이스의 멤버입니다. 이는 네임스페이스의 선언 공간에 선언된 이름에 직접 해당합니다.
네임스페이스에는 액세스 제한이 없습니다. 프라이빗, 보호된 네임스페이스 또는 내부 네임스페이스를 선언할 수 없으며 네임스페이스 이름은 항상 공개적으로 액세스할 수 있습니다.
7.4.3 구조체 멤버
구조체의 멤버는 구조체에 선언된 멤버와 구조체의 직접 기본 클래스 및 간접 기본 System.ValueType
클래스 object
에서 상속된 멤버입니다.
단순 형식의 멤버는 단순 형식(§8.3.5)으로 별칭이 지정된 구조체 형식의 멤버에 직접 해당합니다.
7.4.4 열거형 멤버
열거형의 멤버는 열거형에 선언된 상수와 열거형의 직접 기본 클래스 및 간접 기본 System.Enum
클래스 System.ValueType
및에서 상속된 멤버입니다object
.
7.4.5 클래스 멤버
클래스의 멤버는 클래스에서 선언된 멤버 및 기본 클래스에서 상속된 멤버입니다(기본 클래스가 없는 클래스 object
제외). 기본 클래스에서 상속된 멤버에는 기본 클래스의 상수, 필드, 메서드, 속성, 이벤트, 인덱서, 연산자 및 형식이 포함되지만 기본 클래스의 인스턴스 생성자, 종료자 및 정적 생성자는 포함되지 않습니다. 기본 클래스 멤버는 접근성과 관계없이 상속됩니다.
클래스 선언에는 상수, 필드, 메서드, 속성, 이벤트, 인덱서, 연산자, 인스턴스 생성자, 종료자, 정적 생성자 및 형식의 선언이 포함될 수 있습니다.
object
(§8.2.3) 및 string
(§8.2.5)의 멤버는 이들이 참조하는 클래스 형식의 멤버에 직접 해당합니다.
7.4.6 인터페이스 멤버
인터페이스의 멤버는 인터페이스 및 인터페이스의 모든 기본 인터페이스에서 선언된 멤버입니다.
참고: 클래스
object
의 멤버는 엄밀히 말하면 모든 인터페이스의 멤버가 아닙니다(§18.4). 그러나 클래스object
의 멤버는 모든 인터페이스 형식(§12.5)의 멤버 조회를 통해 사용할 수 있습니다. 끝 메모
7.4.7 배열 멤버
배열의 멤버는 클래스 System.Array
에서 상속된 멤버입니다.
7.4.8 대리자 구성원
대리자는 클래스 System.Delegate
에서 멤버를 상속합니다. 또한 선언에 지정된 동일한 반환 형식 및 매개 변수 목록을 가지고 있는 Invoke
라는 이름의 메서드(§20.2)를 포함합니다. 이 메서드의 호출은 동일한 대리자 인스턴스의 대리자 호출(§20.6)과 동일하게 동작해야 합니다.
구현은 상속을 통해 또는 대리자 자체에서 직접 추가 멤버를 제공할 수 있습니다.
7.5 멤버 액세스
7.5.1 일반
멤버 선언을 사용하면 멤버 액세스를 제어할 수 있습니다. 멤버의 접근성은 멤버의 선언된 접근성(§7.5.2)과 즉시 포함된 형식의 접근성(있는 경우)에 의해 설정됩니다.
특정 멤버에 대한 액세스가 허용되면 멤버에 액세스할 수 있다고 합니다. 반대로 특정 멤버에 대한 액세스가 허용되지 않으면 멤버에 액세스할 수 없다고 합니다. 액세스가 이루어지는 텍스트 위치가 멤버의 접근성 도메인(§7.5.3)에 포함된 경우 멤버에 대한 액세스가 허용됩니다.
7.5.2 선언된 접근성
멤버의 선언된 접근성은 다음 중 하나일 수 있습니다.
- 공용- 멤버 선언에 한정자를
public
포함하여 선택합니다. 직관적인public
의미는 "액세스가 제한되지 않음"입니다. - 보호됨- 멤버 선언에 한정자를
protected
포함하여 선택됩니다. 직관적인 의미protected
는 "포함하는 클래스 또는 포함하는 클래스에서 파생된 형식으로 제한되는 액세스"입니다. - 멤버 선언에
internal
수식자를 포함하여 내부적으로 선택됩니다. 직관적인 의미internal
는 "이 어셈블리로 제한되는 액세스"입니다. - `protected internal`은 멤버 선언에
protected
수정자와internal
수정자를 모두 포함하여 선택됩니다. 직관적인 의미protected internal
는 "포함하는 클래스에서 파생된 형식뿐만 아니라 이 어셈블리 내에서 액세스할 수 있습니다."입니다. - 멤버 선언에
private
과protected
한정자를 모두 포함하여 선택되는 private protected입니다.private protected
의 직관적인 의미는 "포함하는 클래스 및 포함하는 클래스에서 파생된 형식을 통해 이 어셈블리 내에서 액세스할 수 있습니다." - Private- 멤버 선언에 한정자를
private
포함하여 선택합니다.private
의 직관적인 의미는 "포함하는 타입에 제한된 접근"입니다.
멤버 선언이 발생하는 컨텍스트에 따라 특정 형식의 선언된 접근성만 허용됩니다. 또한 멤버 선언에 액세스 한정자가 포함되지 않은 경우 선언이 수행되는 컨텍스트에 따라 기본 선언된 접근성이 결정됩니다.
- 네임스페이스는 암시적으로 접근성을 갖고 있습니다. 네임스페이스 선언에는 액세스 한정자가 허용되지 않습니다.
- 컴파일 단위 또는 네임스페이스에서 직접 선언된 형식(다른 형식 내에 선언되지 않은)은
public
또는internal
접근성을 가질 수 있으며, 기본적으로internal
접근성을 가집니다. - 클래스 멤버는 허용된 선언 접근성 종류를 가질 수 있으며, 기본적으로
private
의 선언된 접근성을 갖습니다.참고: 클래스의 멤버로 선언된 형식은 허용된 종류의 선언된 접근성을 가질 수 있는 반면, 네임스페이스의 멤버로 선언된 형식에는 접근성만 있거나
public
선언된 접근성만internal
있을 수 있습니다. 끝 메모 - ko-KR: 구조체는 암시적으로 봉인되기 때문에, 구조체 멤버는
public
,internal
,private
의 선언된 접근성을 가질 수 있으며, 기본적으로private
의 선언된 접근성을 가집니다. 해당 구조체에서 상속되지 않은struct
에 새로 도입된 구조체 멤버는protected
,protected internal
,private protected
로 선언된 접근성을 가질 수 없습니다.참고: 구조체의 멤버로 선언된 형식은
public
,internal
, 또는private
로 선언된 접근성을 가질 수 있지만, 네임스페이스의 멤버로 선언된 형식은public
또는internal
로 선언된 접근성만 가질 수 있습니다. 끝 메모 - 인터페이스 멤버는
public
암시적으로 접근성을 선언했습니다. 인터페이스 멤버 선언에서는 액세스 한정자를 사용할 수 없습니다. - 열거형 멤버는
public
암시적으로 접근성을 선언했습니다. 열거형 멤버 선언에는 액세스 한정자가 허용되지 않습니다.
7.5.3 접근성 도메인
멤버의 접근성 도메인은 멤버에 대한 액세스가 허용되는 프로그램 텍스트의 (연결되지 않은) 섹션으로 구성됩니다. 멤버의 접근성 도메인을 정의하기 위해 멤버는 형식 내에서 선언되지 않은 경우 최상위 수준이라고 하며 멤버는 다른 형식 내에서 선언된 경우 중첩되었다고 합니다. 또한 프로그램의 프로그램 텍스트는 프로그램의 모든 컴파일 단위에 포함된 모든 텍스트로 정의되며 형식의 프로그램 텍스트는 해당 형식의 type_declaration포함된 모든 텍스트(형식 내에 중첩된 형식 포함)로 정의됩니다.
미리 정의된 형식(예: object
또는int
double
)의 접근성 도메인은 무제한입니다.
프로그램에서 선언된 최상위 언바운드 형식 T
(§8.4.4)의 접근성 도메인은 다음과 같이 정의됩니다.
- 선언된 접근성
T
이 public인 경우,T
의 접근성 도메인은P
의 프로그램 텍스트와P
를 참조하는 모든 프로그램입니다. - 선언된 접근성이
T
와 같으면,T
의 접근성 도메인은P
프로그램 텍스트입니다.
참고: 이러한 정의에서 최상위 언바운드 형식의 접근성 도메인은 항상 해당 형식이 선언된 프로그램의 프로그램 텍스트 이상입니다. 끝 메모
생성된 형식에 대한 접근성 도메인은 바인딩되지 않은 제네릭 형식 T<A₁, ..., Aₑ>
의 접근성 도메인과 형식 T
인수 A₁, ..., Aₑ
의 접근성 도메인의 교집합입니다.
프로그램 P
내의 형식 T
에 선언된 중첩 멤버 M
의 접근성 도메인은 다음과 같이 정의됩니다(M
자체가 형식일 수 있음을 주의하십시오).
-
M
에 대해 선언된 액세스 가능성이public
인 경우M
의 액세스 가능 도메인은T
의 액세스 가능 도메인입니다. - 선언된 접근성이
M
인 경우,D
는P
의 프로그램 텍스트와P
외부에 선언된T
에서 파생된 모든 형식의 프로그램 텍스트의 결합으로 설정됩니다. 접근성 도메인M
는T
의 접근성 도메인과D
간의 교차점입니다. - 선언된 접근성이
private protected
인 경우,D
가P
의 프로그램 텍스트와T
의 프로그램 텍스트 및T
에서 파생된 모든 형식의 교집합이 되도록 하십시오. 접근성 도메인M
은T
의 접근성 도메인과D
와의 교집합입니다. - 선언된 접근성이
M
인 경우,D
를T
의 프로그램 텍스트와T
로부터 파생된 모든 형식의 프로그램 텍스트의 합집합으로 설정합니다. 접근성 도메인M
은T
의 접근성 도메인과D
의 교차점입니다. -
M
에 대해 선언된 액세스 가능성이internal
인 경우M
의 액세스 가능 도메인은T
의 프로그램 텍스트를 가진P
의 액세스 가능 도메인의 교집합 부분입니다. -
M
에 대해 선언된 액세스 가능성이private
인 경우M
의 액세스 가능 도메인은T
의 프로그램 텍스트입니다.
참고: 이러한 정의에서 중첩 멤버의 접근성 도메인은 항상 최소한 멤버가 선언된 형식의 프로그램 텍스트입니다. 또한 멤버의 접근성 도메인이 멤버가 선언된 형식의 접근성 도메인보다 더 포괄적이지는 않습니다. 끝 메모
참고: 직관적인 용어로 형식 또는 멤버
M
에 액세스할 때 다음 단계를 평가하여 액세스가 허용되는지 확인합니다.
- 먼저 컴파일 단위 또는 네임스페이스가 아닌 형식 내에서 선언된 경우
M
해당 형식에 액세스할 수 없는 경우 컴파일 시간 오류가 발생합니다.M
가public
이면 액세스가 허용됩니다.- 그렇지 않으면
M
이protected internal
일 경우,M
가 선언된 프로그램 내에서 발생하거나,M
가 선언된 클래스에서 파생된 클래스 내에서, 파생된 클래스 유형(§7.5.4)을 통해 발생하면 액세스가 허용됩니다.- 그렇지 않은 경우,
M
가protected
일 때,M
가 선언된 클래스 내에서 발생하거나,M
가 선언된 클래스로부터 파생된 클래스 내에서 파생 클래스 유형을 통해 발생하는 경우(§7.5.4)에 액세스가 허용됩니다.- 그렇지 않으면
M
가internal
인 경우,M
이(가) 선언된 프로그램 내에서 발생하면 액세스가 허용됩니다.- 그렇지 않으면,
M
이private
인 경우,M
이 선언된 형식 내에서 발생할 때에 한해 액세스가 허용됩니다.- 그렇지 않으면 형식 또는 멤버에 액세스할 수 없으며 컴파일 시간 오류가 발생합니다. 끝 메모
예제: 다음 코드에서
public class A { public static int X; internal static int Y; private static int Z; } internal class B { public static int X; internal static int Y; private static int Z; public class C { public static int X; internal static int Y; private static int Z; } private class D { public static int X; internal static int Y; private static int Z; } }
클래스 및 멤버에는 다음과 같은 접근성 도메인이 있습니다.
- 액세스 가능성 도메인은
A
A.X
무제한입니다.A.Y
,B
,B.X
,B.Y
,B.C
,B.C.X
및B.C.Y
의 접근성 도메인은 포함 프로그램의 프로그램 텍스트입니다.A.Z
의 접근성 도메인은A
의 프로그램 텍스트입니다.B
의 접근성 도메인은B.Z
와B.D
을 포함한 프로그램 텍스트이며,B.C
와B.D
의 프로그램 텍스트도 포함합니다.B.C.Z
의 접근성 도메인은B.C
의 프로그램 텍스트입니다.B
의 프로그램 텍스트,B.C
및B.D
의 프로그램 텍스트를 포함하여B.D.X
및B.D.Y
의 접근성 도메인은B
의 프로그램 텍스트입니다.B.D.Z
의 접근성 도메인은B.D
의 프로그램 텍스트입니다. 예제에서 알 수 있듯이 멤버의 접근성 도메인은 포함하는 형식보다 크지 않습니다. 모든X
멤버가 퍼블릭으로 선언된 접근성을 가지고 있을지라도,A.X
을 제외한 모든 멤버는 포함하는 형식에 의해 제한된 접근성 도메인을 가지고 있습니다.끝 예제
§7.4에 설명된 대로 인스턴스 생성자, 종료자 및 정적 생성자를 제외한 기본 클래스의 모든 멤버는 파생 형식에 의해 상속됩니다. 여기에는 기본 클래스의 프라이빗 멤버도 포함됩니다. 그러나 프라이빗 멤버의 접근성 도메인에는 멤버가 선언된 형식의 프로그램 텍스트만 포함됩니다.
예제: 다음 코드에서
class A { int x; static void F(B b) { b.x = 1; // Ok } } class B : A { static void F(B b) { b.x = 1; // Error, x not accessible } }
클래스는
B
클래스에서 프라이빗 멤버x
를 상속합니다A
. 멤버는 비공개이므로A
내에서만 접근할 수 있습니다. 따라서A.F
메서드에서는b.x
에 대한 액세스가 성공하지만B.F
메서드에서는 실패합니다.끝 예제
7.5.4 보호된 액세스
protected
선언된 클래스의 프로그램 텍스트 외부에 액세스하거나 private protected
인스턴스 멤버가 선언된 프로그램의 프로그램 텍스트 외부에서 인스턴스 멤버에 액세스하는 경우 protected internal
해당 멤버가 선언된 클래스에서 파생되는 클래스 선언 내에서 액세스가 수행됩니다. 또한 해당 파생 클래스 형식의 인스턴스 또는 해당 클래스 형식에서 생성된 클래스 형식을 통해 액세스가 수행되어야 합니다. 이 제한은 멤버가 동일한 기본 클래스에서 상속되는 경우에도 파생 클래스가 다른 파생 클래스의 보호된 멤버에 액세스하지 못하도록 합니다.
B
은(는) 보호된 인스턴스 멤버 M
을(를) 선언하는 기본 클래스이고, D
은(는) B
에서 파생된 클래스입니다.
class_bodyD
내에서 M
에 대한 액세스는 다음 형식을 사용할 수 있습니다.
- 자격이 없는 type_name 또는 primary_expression의 형식
M
. -
형식의
E.M
기본 표현식은E
의 형식이T
이거나T
로부터 파생된 클래스인 경우, 또는T
클래스D
이나(c6/)D
로부터 구성된 클래스 유형인 경우에 제공됩니다. -
기본_표현식의 형태
base.M
. -
primary_expression의 형태인
base[
argument_list]
.
이러한 형태의 액세스 외에도 파생 클래스는 constructor_initializer 기본 클래스의 보호된 인스턴스 생성자(§15.11.2)에 액세스할 수 있습니다.
예제: 다음 코드에서
public class A { protected int x; static void F(A a, B b) { a.x = 1; // Ok b.x = 1; // Ok } } public class B : A { static void F(A a, B b) { a.x = 1; // Error, must access through instance of B b.x = 1; // Ok } }
내에서
A
는A
와B
의 인스턴스를 통해x
에 액세스할 수 있습니다. 두 경우 모두 액세스는A
의 인스턴스나A
에서 파생된 클래스를 통해 이루어지기 때문입니다. 그러나B
내에서는A
가B
에서 파생되지 않았기 때문에A
의 인스턴스를 통해x
에 액세스할 수 없습니다.끝 예제
예제:
class C<T> { protected T x; } class D<T> : C<T> { static void F() { D<T> dt = new D<T>(); D<int> di = new D<int>(); D<string> ds = new D<string>(); dt.x = default(T); di.x = 123; ds.x = "test"; } }
여기서 세 가지
x
할당은 모두 제네릭 형식에서 생성된 클래스 형식의 인스턴스를 통해 수행되므로 허용됩니다.끝 예제
참고: 제네릭 클래스에 선언된 보호된 멤버의 접근성 도메인(§7.5.3)에는 해당 제네릭 클래스에서 생성된 모든 형식에서 파생된 모든 클래스 선언의 프로그램 텍스트가 포함됩니다. 예제:
class C<T> { protected static T x; } class D : C<string> { static void Main() { C<int>.x = 5; } }
클래스
D
가C<string>
에서 파생되더라도D
의C<int>.x
멤버에 대한 참조protected
는 유효합니다. 끝 메모
7.5.5 접근성 제약 조건
C# 언어에는 멤버 또는 다른 형식만큼 최소한 형식이 접근 가능해야 하는 여러 구조체가 있습니다. 접근성 도메인이 접근성 도메인 T
M
의 상위 집합인 경우 형식 T
은 최소한 멤버 또는 형식 M
만큼 액세스할 수 있다고 합니다. 즉, T
가 M
가 액세스 가능한 모든 컨텍스트에서 액세스 가능하다면, T
는 최소한 M
만큼 액세스 가능합니다.
다음과 같은 접근성 제약 조건이 있습니다.
- 클래스 형식의 직접 기본 클래스는 적어도 클래스 형식 자체만큼 액세스할 수 있어야 합니다.
- 인터페이스 형식의 명시적 기본 인터페이스는 적어도 인터페이스 형식 자체만큼 액세스할 수 있어야 합니다.
- 대리자 형식의 반환 형식 및 매개 변수 형식은 적어도 대리자 형식 자체만큼 액세스할 수 있어야 합니다.
- 상수의 형식은 상수 자체만큼 액세스 가능해야 합니다.
- 필드의 형식은 적어도 필드 자체만큼 액세스할 수 있어야 합니다.
- 메서드의 반환 형식 및 매개 변수 형식은 적어도 메서드 자체만큼 액세스할 수 있어야 합니다.
- 속성의 유형은 적어도 속성 자체만큼 접근할 수 있어야 합니다.
- 이벤트의 유형은 적어도 이벤트 자체만큼 액세스할 수 있어야 합니다.
- 인덱서의 형식 및 매개 변수 형식은 적어도 인덱서 자체만큼 액세스할 수 있어야 합니다.
- 연산자의 반환 형식 및 매개 변수 형식은 적어도 연산자 자체만큼 액세스할 수 있어야 합니다.
- 인스턴스 생성자의 매개 변수 형식은 적어도 인스턴스 생성자 자체만큼 액세스할 수 있어야 합니다.
- 형식 매개 변수의 인터페이스 또는 클래스 형식 제약 조건은 적어도 제약 조건을 선언하는 멤버만큼 액세스할 수 있어야 합니다.
예제: 다음 코드에서
class A {...} public class B: A {...}
클래스
B
는B
가A
만큼의 접근 수준을 가지지 못하여 컴파일 시간 오류가 발생합니다.끝 예제
예: 마찬가지로 다음 코드에서
class A {...} public class B { A F() {...} internal A G() {...} public A H() {...} }
B
의H
메서드는 반환 유형A
가 메서드만큼 액세스 가능한 수준이 아니기 때문에 컴파일 타임 오류가 발생합니다.끝 예제
7.6 서명 및 오버로딩
메서드, 인스턴스 생성자, 인덱서 및 연산자는 시그니처로 특징지어집니다.
- 메서드의 서명은 메서드 이름, 형식 매개 변수 수, 각 매개 변수의 형식 및 매개 변수 전달 모드로 구성되며 왼쪽에서 오른쪽으로 순서대로 고려됩니다. 이러한 목적을 위해 매개 변수 형식에서 발생하는 메서드의 모든 형식 매개 변수는 해당 이름이 아니라 메서드의 형식 매개 변수 목록에서 서수 위치로 식별됩니다. 메서드의 서명에는 특히 반환 형식, 매개 변수 이름, 형식 매개 변수 이름, 형식 매개 변수 제약 조건,
params
매개 변수 한정자 또는this
매개 변수가 필요한지 선택적 매개 변수가 포함되지 않습니다. - 인스턴스 생성자의 서명은 왼쪽에서 오른쪽으로 순서대로 고려되는 각 매개 변수의 형식 및 매개 변수 전달 모드로 구성됩니다. 인스턴스 생성자의 서명에는 특히 가장 오른쪽의 매개 변수에 대해 지정될 수 있는
params
수정자나 매개 변수가 필수인지 선택사항인지는 포함되지 않습니다. - 인덱서의 서명은 왼쪽에서 오른쪽 순서로 고려되는 각 매개 변수의 형식으로 구성됩니다. 인덱서의 서명에는 특히 요소 형식이 포함되지 않으며 가장 적합한 매개 변수에 대해 지정될 수 있는 한정자도 포함되지 않으며 매개 변수가 필요한지 선택 사항인지 여부도 포함되지
params
않습니다. - 연산자의 서명은 연산자의 이름과 각 매개 변수의 형식으로 구성되며 왼쪽에서 오른쪽으로 순서대로 고려됩니다. 특히 연산자의 서명에는 결과 형식이 포함되지 않습니다.
- 변환 연산자의 서명은 원본 형식과 대상 형식으로 구성됩니다. 변환 연산자의 암시적 또는 명시적 분류는 서명의 일부가 아닙니다.
- 동일한 멤버 종류(메서드, 인스턴스 생성자, 인덱서 또는 연산자)의 두 시그니처는 이름, 형식 매개 변수 수, 매개 변수 수 및 매개 변수 전달 모드가 같고 해당 매개 변수 형식(§10.2.2) 간에 ID 변환이 존재하는 경우 동일한 서명으로 간주됩니다.
서명은 클래스, 구조체 및 인터페이스에서 멤버를 오버로드하기 위한 사용 메커니즘입니다.
- 메서드의 오버로드를 사용하면 해당 클래스, 구조체 또는 인터페이스 내에서 시그니처가 고유할 경우 클래스, 구조체 또는 인터페이스에서 동일한 이름의 여러 메서드를 선언할 수 있습니다.
- 인스턴스 생성자의 오버로드를 사용하면 해당 시그니처가 해당 클래스 또는 구조체 내에서 고유할 경우 클래스 또는 구조체가 여러 인스턴스 생성자를 선언할 수 있습니다.
- 인덱서의 오버로드를 사용하면 해당 시그니처가 해당 클래스, 구조체 또는 인터페이스 내에서 고유할 경우 클래스, 구조체 또는 인터페이스에서 여러 인덱서를 선언할 수 있습니다.
- 연산자의 오버로드를 사용하면 해당 시그니처가 해당 클래스 또는 구조체 내에서 고유할 경우 클래스 또는 구조체가 동일한 이름을 가진 여러 연산자를 선언할 수 있습니다.
in
, out
, 그리고 ref
매개 변수 한정자는 서명의 일부로 간주되지만, 단일 형식으로 선언된 멤버는 서명에서 in
, out
, 그리고 ref
만으로는 다를 수 없습니다. 두 멤버가 동일한 형식 내에서 선언되고 out
또는 in
한정자가 있는 두 메서드의 모든 매개 변수가 ref
한정자로 변경되었을 때 시그니처가 같다면, 컴파일 시간 오류가 발생합니다. 서명 일치(예: 숨기기 또는 재정의)in
의 다른 용도로, out
ref
서명의 일부로 간주되며 서로 일치하지 않습니다.
참고: 이 제한은 C# 프로그램을 CLI(공용 언어 인프라)에서 쉽게 번역할 수 있도록 하기 위한 것이며, 이는 단독으로 다른
in
out
ref
메서드를 정의하는 방법을 제공하지 않습니다. 끝 메모
형식 object
이며 dynamic
서명을 비교할 때 구분되지 않습니다. 따라서 시그니처가 object
를 dynamic
로 교체하여서만 달라지는 단일 형식에 선언된 멤버는 허용되지 않습니다.
예제: 다음 예제에서는 해당 서명과 함께 오버로드된 메서드 선언 집합을 보여 줍니다.
interface ITest { void F(); // F() void F(int x); // F(int) void F(ref int x); // F(ref int) void F(out int x); // F(out int) error void F(object o); // F(object) void F(dynamic d); // error. void F(int x, int y); // F(int, int) int F(string s); // F(string) int F(int x); // F(int) error void F(string[] a); // F(string[]) void F(params string[] a); // F(string[]) error void F<S>(S s); // F<0>(0) void F<T>(T t); // F<0>(0) error void F<S,T>(S s); // F<0,1>(0) void F<T,S>(S s); // F<0,1>(1) ok }
모든
in
,out
및ref
매개 변수 한정자(§15.6.2)는 서명의 일부입니다. 따라서 ,F(int)
,F(in int)
F(out int)
및F(ref int)
모두 고유한 서명입니다. 그러나F(in int)
,F(out int)
, 및F(ref int)
는 시그니처가in
,out
, 및ref
로만 다르기 때문에 동일한 인터페이스 내에서 선언할 수 없습니다. 또한 반환 형식과params
한정자는 서명의 일부가 아니므로 반환 형식 또는 한정자의 포함 또는 제외params
에 따라 오버로드할 수 없습니다. 따라서 위에서 식별된 메서드F(int)
F(params string[])
의 선언으로 인해 컴파일 시간 오류가 발생합니다. 끝 예제
7.7 범위
7.7.1 일반
이름의 범위는 이름을 한정하지 않고 이름으로 선언된 엔터티를 참조할 수 있는 프로그램 텍스트 영역입니다. 범위를 중첩할 수 있으며 내부 범위는 외부 범위에서 이름의 의미를 다시 지정할 수 있습니다. (그러나 중첩된 블록 내에서 지역 변수 또는 지역 상수와 이름이 같은 지역 변수 또는 지역 상수를 바깥쪽 블록에 선언할 수 없다는 것을 §7.3에 의해 부과된 제한을 제거하지는 않습니다.) 그런 다음 외부 범위의 이름은 내부 범위에서 다루는 프로그램 텍스트 영역에 숨겨지게 되며, 외부 이름에 대한 액세스는 이름을 한정해야만 가능합니다.
namespace_declaration가 없는 namespace_member_declaration
( §14.6 )로 선언된 네임스페이스 멤버의 범위는 전체 프로그램 텍스트입니다.정규화된 이름이
인 네임스페이스 선언 안에 있는 namespace_member_declaration에 의해 선언된 네임스페이스 멤버의 범위는, 정규화된 이름이 이거나 로 시작하고 그 뒤에 마침표가 있는 모든 네임스페이스 선언의 네임스페이스 본문 입니다. extern_alias_directive (§14.4)에 의해 정의된 이름의 범위는 이를 직접 포함하는 compilation_unit 또는 namespace_body의 using_directive, global_attributes, 그리고 namespace_member_declaration에 걸쳐 확장됩니다. extern_alias_directive 기본 선언 공간에 새 멤버를 제공하지 않습니다. 즉, extern_alias_directive는 전이적이지 않으며, 오히려 이것이 발생하는 compilation_unit 또는 namespace_body에만 영향을 줍니다.
컴파일 유닛(compilation_unit) 또는 네임스페이스 본문(namespace_body) 내에 발생하는 using_directive에 의해 정의되거나 가져온 이름의 범위는 그 글로벌 속성(global_attributes) 및 네임스페이스 멤버 선언(namespace_member_declaration) 위로 확장됩니다. using_directive는 특정 compilation_unit 또는 namespace_body 내에서 0개 이상의 네임스페이스 또는 형식 이름을 사용할 수 있게 하지만, 기본 선언 공간에 새로운 멤버를 추가하지는 않습니다. 즉, using_directive는 전이적이지 않고 발생하는 compilation_unit 또는 namespace_body에만 영향을 줍니다.
class_declarationtype_parameter_list 선언한 형식 매개 변수의 범위(§15.2)는 해당 class_declarationclass_base, type_parameter_constraints_clause및 class_body.
참고: 클래스의 멤버와 달리 이 범위는 파생 클래스로 확장되지 않습니다. 끝 메모
type_parameter_list에 의해 선언된 형식 매개 변수의 범위는 §16.2에 명시된 struct_interfaces, type_parameter_constraints_clause 및 struct_body 안의 struct_declaration입니다.
인터페이스 선언(§18.2)의 type_parameter_list에 의해 선언된 형식 매개변수의 범위는 해당 interface_declaration의 interface_base, type_parameter_constraints_clause 및 interface_body입니다.
type_parameter_list로 delegate_declaration에 선언된 형식 매개 변수의 범위(§20.2)는 해당 return_type, parameter_list, 그리고 type_parameter_constraints_clause의 범위입니다.
type_parameter_list로
선언된 형식 매개변수의 범위는 method_declaration (§15.6.1)입니다. 선언된 멤버의 범위는
§15.3.1 에 따른class_member_declaration 이며,선언이 발생하는 class_body 의 범위입니다. 또한 클래스 멤버의 범위는 멤버의 접근성 도메인(§7.5.3)에 포함된 파생 클래스의 class_body까지 확장됩니다.struct_member_declaration에 의해 선언된 멤버의 범위는 선언이 발생하는 struct_body(§16.3)입니다.
enum_member_declaration에 의해 선언된 멤버의 범위 (§19.4)는 그 선언이 이루어지는 enum_body입니다.
method_declaration의 범위(§15.6) 내에 선언된 매개 변수는 해당 method_declaration의 method_body 또는 ref_method_body에 속합니다.
indexer_declaration에서 선언된 매개 변수의 범위(§15.9)는 해당 indexer_declaration의 indexer_body입니다.
operator_declaration에서 선언된 매개 변수의 범위는 해당 operator_declaration의 operator_body입니다.
매개변수가 `constructor_declaration`에서 선언될 때 그 범위는 해당 `constructor_initializer`와 `block` 내에 있습니다 (`§15.11`).
lambda_expression (§12.19)에 선언된 매개 변수의 범위는 해당 lambda_expression의 lambda_expression_body입니다.
anonymous_method_expression 선언된 매개 변수의 범위(§12.19)는 해당 anonymous_method_expression 블록입니다.
labeled_statement 선언된 레이블의 범위(§13.5)는 선언이 발생하는 블록입니다.
local_variable_declaration 선언된 지역 변수의 범위(§13.6.2)는 선언이 발생하는 블록입니다.
문장 ( §13.8.3 )의switch_block 에 선언된 지역 변수의 범위는switch_block 이다.지역 변수의 범위는
for
문(§13.9.4)의 for_initializer, for_condition, for_iterator, embedded_statement 내에서만 유효합니다.local_constant_declaration 선언된 로컬 상수의 범위(§13.6.3)는 선언이 발생하는 블록입니다. constant_declarator 앞에 있는 텍스트 위치에서 로컬 상수의 참조를 참조하는 것은 컴파일 시간 오류입니다.
foreach_statement, using_statement, lock_statement 또는 query_expression의 일부로 선언된 변수의 범위는 해당 구문의 확장에 따라 결정됩니다.
네임스페이스, 클래스, 구조체 또는 열거형 멤버의 범위 내에서 멤버 선언 앞에 오는 텍스트 위치에서 멤버를 참조할 수 있습니다.
예제:
class A { void F() { i = 1; } int i = 0; }
여기서는
F
가 선언되기 전에i
를 참조하는 것이 유효합니다.끝 예제
지역 변수의 범위 내에서 선언자 앞에 있는 텍스트 위치에서 지역 변수를 참조하는 것은 컴파일 시간 오류입니다.
예제:
class A { int i = 0; void F() { i = 1; // Error, use precedes declaration int i; i = 2; } void G() { int j = (j = 1); // Valid } void H() { int a = 1, b = ++a; // Valid } }
F
위의 메서드에서 첫 번째i
할당은 특히 외부 범위에 선언된 필드를 참조하지 않습니다. 대신 지역 변수를 참조하고 변수 선언 앞에 텍스트로 표시되므로 컴파일 시간 오류가 발생합니다.G
메서드에서는j
선언을 위한 이니셜라이저에서j
의 사용이 선언자보다 이전에 사용되지 않기 때문에 유효합니다.H
메서드에서 후속 선언자는 동일한 local_variable_declaration 내에서 이전 선언자에 선언된 지역 변수를 올바르게 참조합니다.끝 예제
참고: 지역 변수 및 지역 상수에 대한 범위 지정 규칙은 식 컨텍스트에서 사용되는 이름의 의미가 블록 내에서 항상 동일하도록 설계되었습니다. 지역 변수의 범위가 선언에서 블록 끝까지만 확장되는 경우 위의 예제에서 첫 번째 할당은 인스턴스 변수에 할당되고 두 번째 할당은 지역 변수에 할당되며, 블록의 문이 나중에 다시 정렬될 경우 컴파일 시간 오류가 발생할 수 있습니다.)
블록 내 이름의 의미는 이름이 사용되는 컨텍스트에 따라 다를 수 있습니다. 예제에서
class A {} class Test { static void Main() { string A = "hello, world"; string s = A; // expression context Type t = typeof(A); // type context Console.WriteLine(s); // writes "hello, world" Console.WriteLine(t); // writes "A" } }
이름은
A
식 컨텍스트에서 지역 변수A
를 참조하고 형식 컨텍스트에서 클래스A
를 참조하는 데 사용됩니다.끝 메모
7.7.2 이름 숨기기
7.7.2.1 일반
엔터티의 범위는 일반적으로 엔터티의 선언 공간보다 더 많은 프로그램 텍스트를 포함합니다. 특히 엔터티의 범위에는 동일한 이름의 엔터티를 포함하는 새 선언 공간을 도입하는 선언이 포함될 수 있습니다. 이러한 선언으로 인해 원래 엔터티가 숨겨집니다. 반대로 엔터티는 숨겨지지 않을 때 볼 수 있다고 합니다.
이름 숨기기는 범위가 중첩을 통해 겹치고 범위가 상속을 통해 겹치는 경우에 발생합니다. 두 가지 유형의 숨기기 특성은 다음 하위 클래스에 설명되어 있습니다.
7.7.2.2 중첩을 통해 숨기기
네임스페이스 내에서 네임스페이스 또는 형식을 중첩한 결과, 클래스 또는 구조체 내의 형식 중첩, 로컬 함수 또는 람다의 결과, 매개 변수, 지역 변수 및 로컬 상수 선언의 결과로 중첩을 통해 이름이 숨김이 발생할 수 있습니다.
예제: 다음 코드에서
class A { int i = 0; void F() { int i = 1; void M1() { float i = 1.0f; Func<double, double> doubler = (double i) => i * 2.0; } } void G() { i = 1; } }
메서드 내에서
F
인스턴스 변수는 지역 변수i
i
에 의해 숨겨지지만 메서드G
내에서i
는 여전히 인스턴스 변수를 참조합니다. 로컬 함수M1
내부에서float i
이 (가장 가까운) 외부i
를 숨깁니다. 람다 매개 변수i
는float i
람다 본문 내부를 숨깁니다.끝 예제
내부 범위의 이름이 외부 범위의 이름을 숨기면 해당 이름의 오버로드된 모든 항목을 숨깁니다.
예제: 다음 코드에서
class Outer { static void F(int i) {} static void F(string s) {} class Inner { static void F(long l) {} void G() { F(1); // Invokes Outer.Inner.F F("Hello"); // Error } } }
호출
F(1)
은Inner
에 선언된F
을(를) 호출합니다. 왜냐하면 모든 외부에 있는F
가 내부 선언에 의해 숨겨지기 때문입니다. 같은 이유로 호출F("Hello")
시 컴파일 시간 오류가 발생합니다.끝 예제
7.7.2.3 상속을 통해 숨기기
상속을 통해 숨기는 이름은 클래스 또는 구조체가 기본 클래스에서 상속된 이름을 다시 표시할 때 발생합니다. 이 유형의 이름 숨기기는 다음 형식 중 하나를 사용합니다.
- 클래스 또는 구조체에 도입된 상수, 필드, 속성, 이벤트 또는 형식은 이름이 같은 모든 기본 클래스 멤버를 숨깁니다.
- 클래스 또는 구조체에 도입된 메서드는 이름이 같은 메서드가 아닌 기본 클래스 멤버와 시그니처가 동일한 모든 기본 클래스 메서드(§7.6)를 숨깁니다.
- 클래스 또는 구조체에 도입된 인덱서는 동일한 서명(§7.6)을 가진 모든 기본 클래스 인덱서를 숨깁니다.
연산자 선언(§15.10)을 제어하는 규칙을 사용하면 파생 클래스에서 기본 클래스의 연산자와 동일한 서명을 가진 연산자를 선언할 수 없습니다. 따라서 연산자는 서로를 숨기지 않습니다.
외부 범위에서 이름을 숨기는 것과 달리 상속된 범위에서 표시되는 이름을 숨기면 경고가 보고됩니다.
예제: 다음 코드에서
class Base { public void F() {} } class Derived : Base { public void F() {} // Warning, hiding an inherited name }
Derived
에서F
선언이 경고를 발생시킵니다. 상속된 이름을 숨기는 것은 기본 클래스의 별도의 진화를 배제하기 때문에 특히 오류가 아닙니다. 예를 들어 위의 상황은 이후 버전의Base
클래스에 없는 메서드를 도입F
했기 때문일 수 있습니다.끝 예제
상속된 이름을 숨김으로 인한 경고는 한정자를 사용하여 new
제거할 수 있습니다.
예제:
class Base { public void F() {} } class Derived : Base { public new void F() {} }
new
수정자는F
의Derived
이 "new"이며, 확실히 상속된 멤버를 숨기려는 것임을 나타냅니다.끝 예제
새 멤버의 선언은 새 멤버의 범위 내에서만 상속된 멤버를 숨깁니다.
예제:
class Base { public static void F() {} } class Derived : Base { private new static void F() {} // Hides Base.F in Derived only } class MoreDerived : Derived { static void G() { F(); // Invokes Base.F } }
위의 예제에서,
F
의 선언은Base
로부터 상속된F
를 숨깁니다. 하지만Derived
의 새로운F
는 프라이빗 액세스 권한이 있으므로, 해당 범위가MoreDerived
로 확장되지 않습니다. 따라서F()
MoreDerived.G
호출은 유효하며Base.F
를 호출합니다.끝 예제
7.8 네임스페이스 및 형식 이름
7.8.1 일반
C# 프로그램의 여러 컨텍스트에서는 namespace_name 또는 type_name 지정해야 합니다.
namespace_name
: namespace_or_type_name
;
type_name
: namespace_or_type_name
;
namespace_or_type_name
: identifier type_argument_list?
| namespace_or_type_name '.' identifier type_argument_list?
| qualified_alias_member
;
namespace_name은 네임스페이스를 참조하는 namespace_or_type_name입니다.
아래에 설명된 대로 해결된 후, `namespace_name`의 `namespace_or_type_name`는 네임스페이스를 참조해야 하며, 그렇지 않으면 컴파일 시간 오류가 발생합니다. 형식 인수(§8.4.2)는 namespace_name에 있을 수 없습니다. 형식 인수는 형식에만 사용할 수 있습니다.
type_name은 namespace_or_type_name이 참조하는 형식입니다. 아래에 설명된 대로 해결한 후, namespace_or_type_name의 type_name이 형식을 참조해야 하며, 그렇지 않으면 컴파일 타임 오류가 발생합니다.
namespace_or_type_name이 qualified_alias_member인 경우, 그 의미는 §14.8.1에 설명되어 있습니다. 그렇지 않으면 namespace_or_type_name 다음 네 가지 형식 중 하나가 있습니다.
I
I<A₁, ..., Aₓ>
N.I
N.I<A₁, ..., Aₓ>
여기서 I
는 단일 식별자이며, N
는 namespace_or_type_name이고, <A₁, ..., Aₓ>
는 선택적 type_argument_list입니다.
type_argument_list가 지정되지 않은 경우, 이는 0으로 간주x
합니다.
namespace_or_type_name 의미는 다음과 같이 결정됩니다.
- namespace_or_type_name이 qualified_alias_member인 경우, 그 의미는 §14.8.1에 명시된 대로입니다.
- 그렇지 않은 경우 namespace_or_type_name가
I
형식이거나I<A₁, ..., Aₓ>
형식인 경우:-
x
가 0이고 namespace_or_type_name이 제네릭 메서드 선언(§15.6) 내에 있지만 메서드사의 attributes 외부에 나타나는 경우, 해당 선언에 이름이I
인 형식 매개 변수(§15.2.3)가 포함되어 있다면, namespace_or_type_name은 그 형식 매개 변수를 참조합니다. - 그렇지 않은 경우 namespace_or_type_name 형식 선언 내에 나타나는 경우 각 인스턴스 형식
T
(§15.3.2)에 대해 해당 형식 선언의 인스턴스 형식으로 시작하고 각 바깥쪽 클래스 또는 구조체 선언의 인스턴스 형식으로 계속 진행합니다(있는 경우).-
x
가 0이고,T
의 선언에I
라는 이름의 형식 매개 변수가 포함되어 있으면, namespace_or_type_name은 해당 형식 매개 변수를 참조합니다. - 그렇지 않은 경우, namespace_or_type_name이 형식 선언의 본문 내에 나타나고,
T
또는 해당 기본 형식들 중 하나가I
및x
형식 매개 변수를 가진 중첩된 접근 가능한 형식을 포함하는 경우, namespace_or_type_name는 주어진 형식 인수로 구성된 해당 형식을 참조합니다. 이러한 형식이 두 개 이상 있는 경우 더 많은 파생 형식 내에서 선언된 형식이 선택됩니다.
참고: 형식이 아닌 멤버(상수, 필드, 메서드, 속성, 인덱서, 연산자, 인스턴스 생성자, 종료자 및 정적 생성자) 및 형식 매개 변수 수가 다른 형식 멤버는 namespace_or_type_name 의미를 결정할 때 무시됩니다. 끝 메모
-
- 그렇지 않은 경우, namespace_or_type_name이 발생하는 네임스페이스부터 시작하여, 각 바깥쪽 네임스페이스(있는 경우)를 거쳐, 전역 네임스페이스에서 끝나는 순서로 각 네임스페이스를 평가하여 엔터티가 위치할 때까지 다음 단계가 실행됩니다.
-
x
가 0이고,I
이/가N
의 네임스페이스 이름이면 다음과 같습니다:-
namespace_or_type_name이 발생하는 위치가
N
에 대한 네임스페이스 선언 내에 포함되어 있고, 그 네임스페이스 선언이 extern_alias_directive 또는 using_alias_directive를 통해 이름I
을 네임스페이스 또는 형식과 연결하는 경우, namespace_or_type_name이 모호해져서 컴파일 시간 오류가 발생합니다. - 그렇지 않으면 namespace_or_type_name은
N
에 있는I
라는 네임스페이스를 참조합니다.
-
namespace_or_type_name이 발생하는 위치가
- 그렇지 않으면,
N
에 이름I
과x
형식 매개 변수를 갖는 액세스 가능한 형식이 포함된 경우, 다음을 수행합니다.-
x
이 0이고N
의 네임스페이스 선언에 의해 둘러싸인 위치에서 namespace_or_type_name이 발생하고, 해당 네임스페이스 선언에 네임스페이스나 형식과 이름I
을 연결하는 extern_alias_directive 또는 using_alias_directive가 포함되어 있다면, namespace_or_type_name은 모호하며 컴파일 시간 오류가 발생합니다. - 그렇지 않으면 namespace_or_type_name 지정된 형식 인수를 사용하여 생성된 형식을 참조합니다.
-
- 그렇지 않으면 namespace_or_type_name이 발생하는 위치가 다음에 대한
N
네임스페이스 선언으로 묶인 경우-
x
가 0이고 네임스페이스 선언에 extern_alias_directive 또는 using_alias_directive가 포함되어 있다면, 그것이I
와 가져온 네임스페이스 또는 형식을 연결하는 경우 namespace_or_type_name은 해당 네임스페이스 또는 형식을 참조합니다. - 그렇지 않은 경우, 네임스페이스 선언의 using_namespace_directive로 가져온 네임스페이스에 이름이
I
이고x
형식 매개 변수를 가진 형식이 정확히 하나만 있으면, namespace_or_type_name은 지정된 형식 인수로 생성된 해당 형식을 참조합니다. - 그렇지 않은 경우, 네임스페이스 선언의 using_namespace_directive로 가져온 네임스페이스에 이름
I
및x
형식 매개 변수가 있는 두 개 이상의 형식이 포함되어 있다면, namespace_or_type_name은 모호해지며 오류가 발생합니다.
-
-
- 그렇지 않으면 namespace_or_type_name 정의되지 않고 컴파일 시간 오류가 발생합니다.
-
- 그렇지 않으면 namespace_or_type_name의 형식은
N.I
또는N.I<A₁, ..., Aₓ>
입니다.N
는 먼저 namespace_or_type_name으로 해석됩니다. 해결되지 않으면N
컴파일 시간 오류가 발생합니다. 그렇지 않으면N.I
N.I<A₁, ..., Aₓ>
다음과 같이 해결됩니다.-
x
가 0이고N
가 네임스페이스를 참조하며,N
가 이름이I
인 중첩된 네임스페이스를 포함하는 경우, namespace_or_type_name은 그 중첩된 네임스페이스를 참조합니다. - 그렇지 않으면,
N
가 네임스페이스를 참조하고N
가I
라는 이름과x
형식 매개 변수를 가진 접근 가능한 형식을 포함하는 경우, namespace_or_type_name은 지정된 형식 인수로 구성된 해당 형식을 참조합니다. - 그렇지 않은 경우 (생성될 수 있는) 클래스 또는 구조체 형식을 참조하거나
N
해당 기본 클래스에 이름N
및I
형식 매개 변수가 있는 중첩된 액세스 가능 형식이 포함된 경우x
namespace_or_type_name 지정된 형식 인수로 생성된 해당 형식을 참조합니다. 이러한 형식이 두 개 이상 있는 경우 더 많은 파생 형식 내에서 선언된 형식이 선택됩니다.참고:
N.I
의 의미가N
의 기본 클래스 사양을 해결하는 과정의 일부로 결정되는 경우,N
의 직접 기본 클래스는object
로 간주됩니다 (§15.2.4.2). 끝 메모 - 그렇지 않으면
N.I
는 잘못된 namespace_or_type_name으로 컴파일 시간 오류가 발생합니다.
-
namespace_or_type_name 경우에만 정적 클래스(§15.2.2.4)를 참조할 수 있습니다.
-
namespace_or_type_name은 namespace_or_type_name의 형식
T.I
내에 있으며, -
namespace_or_type_name은(는)
T
(§12.8.18)의 형태인typeof(T)
입니다.
7.8.2 정규화되지 않은 이름
모든 네임스페이스 선언 및 형식 선언에는 다음과 같이 정규화되지 않은 이름이 결정됩니다.
- 네임스페이스 선언의 경우 정규화되지 않은 이름은 선언에 지정된 정규화된 식별자입니다.
- type_parameter_list 없는 형식 선언의 경우 정규화되지 않은 이름은 선언에 지정된 식별자입니다.
- K개의 타입 매개변수가 있는 형식 선언의 경우, 선언에서 지정된 정규화되지 않은 이름은 식별자이며, 이는 K 타입 매개변수를 위한 generic_dimension_specifier(§12.8.18)으로 이어집니다.
7.8.3 완전히 정규화된 이름
모든 네임스페이스 및 형식 선언에는 프로그램 내의 다른 모든 항목 간에 네임스페이스 또는 형식 선언을 고유하게 식별하는 정규화된 이름이 있습니다. 정규화되지 않은 이름의 네임스페이스 또는 형식 선언의 정규화된 이름은 N
다음과 같이 결정됩니다.
-
N
이(가) 전역 네임스페이스의 멤버인 경우, 그 완전한 이름은N
입니다. - 그렇지 않으면,
S.N
의 정규화된 이름은S
이라는 네임스페이스 또는 형식 선언 내에 선언된N
의 정규화된 이름입니다.
즉, N
의 정규화된 이름은 전역 네임스페이스에서 시작하여 식별자와 generic_dimension_specifier을 따라 N
에 이르는 전체 계층적 경로입니다. 네임스페이스 또는 형식의 모든 멤버는 고유한 이름을 가지므로 네임스페이스 또는 형식 선언의 정규화된 이름이 항상 고유합니다. 동일한 정규화된 이름이 두 개의 고유 엔터티를 참조하는 것은 컴파일 시간 오류입니다. 특히 다음 사항에 주의하십시오.
- 네임스페이스 선언과 형식 선언 모두에 동일한 정규화된 이름을 갖는 것은 오류입니다.
- 두 종류의 형식 선언이 동일한 정규화된 이름을 갖는 것은 오류입니다(예: 구조체 및 클래스 선언에 정규화된 이름이 동일한 경우).
- 부분 한정자가 없는 형식 선언이 다른 형식 선언(§15.2.7)과 동일한 정규화된 이름을 갖는 것은 오류입니다.
예제: 아래 예제에서는 연결된 정규화된 이름과 함께 여러 네임스페이스 및 형식 선언을 보여 줍니다.
class A {} // A namespace X // X { class B // X.B { class C {} // X.B.C } namespace Y // X.Y { class D {} // X.Y.D } } namespace X.Y // X.Y { class E {} // X.Y.E class G<T> // X.Y.G<> { class H {} // X.Y.G<>.H } class G<S,T> // X.Y.G<,> { class H<U> {} // X.Y.G<,>.H<> } }
끝 예제
7.9 자동 메모리 관리
C#에서는 자동 메모리 관리를 사용하여 개발자가 개체가 차지하는 메모리를 수동으로 할당하고 해제할 수 있습니다. 자동 메모리 관리 정책은 가비지 수집기에서 구현됩니다. 개체의 메모리 관리 수명 주기는 다음과 같습니다.
- 개체가 만들어지면 메모리가 할당되고, 생성자가 실행되고, 개체가 라이브로 간주됩니다.
- 종료자 실행 이외의 가능한 실행 연속으로 개체나 해당 인스턴스 필드에 액세스할 수 없는 경우 개체는 더 이상 사용되지 않는 것으로 간주되며 종료에 적합합니다.
참고: C# 컴파일러와 가비지 수집기는 나중에 사용할 수 있는 개체에 대한 참조를 결정하기 위해 코드를 분석하도록 선택할 수 있습니다. 예를 들어 범위에 있는 지역 변수가 개체에 대한 유일한 기존 참조이지만 해당 지역 변수가 프로시저의 현재 실행 지점에서 실행 가능한 연속에서 참조되지 않는 경우 가비지 수집기는 개체를 더 이상 사용하지 않는 것으로 처리할 수 있습니다(하지만 필요하지는 않음). 끝 메모
- 개체가 종료될 수 있는 상태가 되면, 구체적으로 정해지지 않은 시점에 개체의 종료자(§15.13)(있는 경우)가 실행됩니다. 일반적인 상황에서는 개체에 대한 종료 메서드는 한 번만 실행되지만, 구현 정의 API가 이 동작을 재정의할 수 있습니다.
- 개체의 종료자가 실행되면 종료자 실행을 포함하여 가능한 실행 연속으로 개체나 인스턴스 필드 모두에 액세스할 수 없는 경우 개체는 액세스할 수 없는 것으로 간주되고 개체는 컬렉션에 적합해집니다.
참고: 이전에 액세스할 수 없었던 개체는 종료자로 인해 다시 액세스할 수 있습니다. 이에 대한 예는 다음과 같습니다. 끝 메모
- 마지막으로 개체를 수집할 수 있게 되면 가비지 수집기에서 해당 개체와 연결된 메모리를 해제합니다.
가비지 수집기는 개체 사용에 대한 정보를 유지 관리하며 이 정보를 사용하여 메모리 관리 결정(예: 메모리에서 새로 만든 개체를 찾을 위치, 개체 재배치 시기, 개체가 더 이상 사용 중이거나 액세스할 수 없는 경우)을 결정합니다.
가비지 수집기가 있다고 가정하는 다른 언어와 마찬가지로 C#은 가비지 수집기가 광범위한 메모리 관리 정책을 구현할 수 있도록 설계되었습니다. C#은 해당 범위 내의 시간 제약 조건이나 종료자가 실행되는 순서를 지정하지 않습니다. 종료자가 애플리케이션 종료의 일부로 실행되는지 여부는 구현 정의(§7.2)입니다.
가비지 수집기의 동작은 클래스 System.GC
의 정적 메서드를 통해 어느 정도 제어할 수 있습니다. 이 클래스는 컬렉션 발생, 종료자 실행(또는 실행 안 됨) 등을 요청하는 데 사용할 수 있습니다.
예: 가비지 수집기는 개체를 수집하고 종료자를 실행할 시기를 결정할 때 넓은 위도를 허용하므로 준수 구현은 다음 코드에 표시된 것과 다른 출력을 생성할 수 있습니다. 프로그램
class A { ~A() { Console.WriteLine("Finalize instance of A"); } } class B { object Ref; public B(object o) { Ref = o; } ~B() { Console.WriteLine("Finalize instance of B"); } } class Test { static void Main() { B b = new B(new A()); b = null; GC.Collect(); GC.WaitForPendingFinalizers(); } }
클래스
A
의 인스턴스와 클래스B
의 인스턴스를 만듭니다. 이러한 개체는 변수b
에 값null
이 할당될 때 가비지 수집에 적합합니다. 이 시간 이후에는 사용자가 작성한 코드에서 액세스할 수 없기 때문에 이러한 개체는 가비지 수집에 적합합니다. 출력 결과는 둘 중 하나일 수 있습니다.Finalize instance of A Finalize instance of B
또는
Finalize instance of B Finalize instance of A
언어는 개체가 가비지 수집되는 순서에 제약 조건을 적용하지 않으므로
미묘한 경우 "완료 가능"과 "수집 적격"을 구분하는 것이 중요할 수 있습니다. 예를 들면 다음과 같습니다.
class A { ~A() { Console.WriteLine("Finalize instance of A"); } public void F() { Console.WriteLine("A.F"); Test.RefA = this; } } class B { public A Ref; ~B() { Console.WriteLine("Finalize instance of B"); Ref.F(); } } class Test { public static A RefA; public static B RefB; static void Main() { RefB = new B(); RefA = new A(); RefB.Ref = RefA; RefB = null; RefA = null; // A and B now eligible for finalization GC.Collect(); GC.WaitForPendingFinalizers(); // B now eligible for collection, but A is not if (RefA != null) { Console.WriteLine("RefA is not null"); } } }
위의 프로그램에서 가비지 수집기가
A
의 종료자를B
의 종료자보다 먼저 실행하도록 선택하는 경우, 이 프로그램의 출력은 다음과 같을 수 있습니다.Finalize instance of A Finalize instance of B A.F RefA is not null
A
인스턴스가 사용되지 않고A
의 종료자가 실행되었지만,A
의 메서드(이 경우F
)는 여전히 다른 종료자에서 호출될 수 있습니다. 또한 종료자를 실행하면 개체가 메인라인 프로그램에서 다시 사용할 수 있게 될 수 있습니다. 이 경우,B
의 종료자가 실행되면서 이전에 사용되지 않았던A
의 인스턴스가 라이브 참조Test.RefA
를 통해 접근 가능해졌습니다. 호출WaitForPendingFinalizers
후 인스턴스B
는 컬렉션에 적합하지만 참조A
로 인해 인스턴스Test.RefA
가 그렇지 않습니다.끝 예제
7.10 실행 순서
C# 프로그램의 실행은 각 실행 스레드의 부작용이 중요한 실행 지점에서 유지되도록 진행됩니다.
부작용은 휘발성 필드의 읽기 또는 쓰기, 비휘발성 변수에 대한 쓰기, 외부 리소스에 대한 쓰기 및 예외 throw로 정의됩니다. 이러한 부작용의 순서를 유지해야 하는 중요한 실행 지점은 휘발성 필드(§15.5.4lock
), 스레드 생성 및 종료에 대한 참조입니다. 실행 환경은 다음 제약 조건에 따라 C# 프로그램의 실행 순서를 자유롭게 변경할 수 있습니다.
- 데이터 의존성은 실행 스레드 내에서 유지됩니다. 즉, 각 변수의 값은 스레드의 모든 문이 원래 프로그램 순서로 실행된 것처럼 계산됩니다.
- 초기화 순서 규칙은 유지됩니다(§15.5.5, §15.5.6).
- 부작용 발생 순서는 휘발성 변수의 읽기 및 쓰기(§15.5.4)와 관련하여 유지됩니다. 또한 해당 식의 값이 사용되지 않고 필요한 부작용이 생성되지 않는다고 추론할 수 있는 경우 실행 환경에서 식의 일부를 평가할 필요가 없습니다(메서드를 호출하거나 휘발성 필드에 액세스하여 발생하는 작업 포함). 비동기 이벤트(예: 다른 스레드에서 throw된 예외)로 인해 프로그램 실행이 중단되는 경우 관찰 가능한 부작용이 원래 프로그램 순서로 표시되는 것은 아닙니다.
ECMA C# draft specification