| 패러다임 | 프로그래밍 패러다임: 구조적 프로그래밍, 명령형 프로그래밍, 객체 지향 프로그래밍, 사건 기반 프로그래밍, 비동기 메서드 호출, 함수형 프로그래밍, 제네릭 프로그래밍, 반영, 병행 컴퓨팅 |
|---|---|
| 설계자 | 마이크로소프트 |
| 개발자 | 마이크로소프트 |
| 발표일 | 2000년 |
| 최근 버전 | 14.0[1] |
| 최근 버전 출시일 | 2025년 11월 11일(5개월 전)(2025년 11월 11일) |
| 플랫폼 | 공통 언어 기반(CLI) |
| 라이선스 | CLR: MIT 라이선스 모노 컴파일러: GPLv3&MIT 라이선스 라이브러리: LGPLv2 |
| 파일 확장자 | .cs |
| 웹사이트 | docs |
| 비주얼 C 샤프, 닷넷 프레임워크, 모노, 던GNU | |
| Cω, 스페이스 샤프, 폴리포닉 C 샤프, 엔터헤드 C# | |
| C++, 에펠, 자바, 모델-3, 오브젝트 파스칼, ML, VB, 아이콘, 하스켈, 러스트, J#, Cω, F# | |
| 체펠, D, J#, 해키, 자바, 코틀린, 몽키 X, 네메레, 옥시즌, 러스트, 스위프트, 발라 | |
C#(한국어: 시 샤프 또는 C 샵)는 여러 패러다임을 지원하는 범용 고급 프로그래밍 언어다. C#은 정적 타이핑,: 4 강한 타이핑, 어휘 범위, 명령형 프로그래밍, 선언형 프로그래밍, 함수형 프로그래밍, 제네릭 프로그래밍,: 22 객체 지향 프로그래밍(클래스 기반), 컴포넌트 기반 프로그래밍 규율을 포함한다.[3]
C# 프로그래밍 언어의 주요 설계자는 마이크로소프트의 아네르스 하일스베르, 스콧 윌타무스, 피터 골데다.[3] 2000년 7월에 처음으로 널리 배포되었으며[3] 이후 2002년 Ecma(ECMA-334), 2003년 ISO/IEC(ISO/IEC 23270 및 20619[a])에서 국제 표준으로 승인되었다. 마이크로소프트는 C#을 닷넷 프레임워크 및 마이크로소프트 비주얼 스튜디오와 함께 도입했는데, 엄밀히 말하면 이들은 모두 소스 비공개였다. 당시 마이크로소프트에는 오픈 소스 제품이 없었다. 4년 후인 2004년, 모노라는 자유-오픈 소스 소프트웨어 프로젝트가 시작되어 C# 언어를 위한 교차 플랫폼 컴파일러와 런타임 환경을 제공했다. 10년 후 마이크로소프트는 비주얼 스튜디오 코드(코드 편집기), Roslyn(컴파일러), 통합 닷넷 플랫폼(소프트웨어 프레임워크)을 출시했으며, 이들은 모두 C#을 지원하고 무료이며 오픈 소스인 교차 플랫폼이다. 모노 또한 마이크로소프트에 합류했으나 닷넷으로 통합되지는 않았다.
닷넷 프레임워크 개발 기간 동안 기본 클래스 라이브러리는 원래 Simple Managed C (SMC)라는 이름의 관리 코드 컴파일러 시스템을 사용하여 작성되었다.[9][10] 1999년 1월, 아네르스 하일스베르는 당시 "C와 유사한 객체 지향 언어"를 뜻하는 COOL이라는 이름의 새 언어를 구축하기 위해 팀을 구성했다.[11]
마이크로소프트는 "COOL"을 언어의 최종 이름으로 유지하는 것을 고려했으나 상표상의 이유로 포기했다. 닷넷 프로젝트가 공개적으로 발표된 2000년 7월 PDC 무렵 언어 이름은 C#으로 변경되었으며, 클래스 라이브러리와 ASP.NET 런타임이 C#으로 포팅되었다.
하일스베르는 마이크로소프트에서 C#의 주요 설계자이자 수석 아키텍트였으며, 이전에는 터보 파스칼, 엠바카데로 델파이(이전의 코드기어 델파이, 인프라이즈 델파이, 볼랜드 델파이) 및 비주얼 J++ 설계에 참여했다. 그는 인터뷰와 기술 문서에서 대부분의 주요 프로그래밍 언어(예: C++, 자바, 델파이, 스몰토크)의 결함이 공통 언어 런타임(CLR)의 토대가 되었고, 이는 다시 C# 언어의 설계로 이어졌다고 밝혔다.[12]
1994년에 자바 프로그래밍 언어를 만든 제임스 고슬링과 자바의 창시자인 썬 마이크로시스템즈의 공동 창업자 빌 조이는 C#을 자바의 "모방품"이라고 불렀다. 고슬링은 더 나아가 "[C#은] 신뢰성, 생산성, 보안성이 삭제된 일종의 자바다"라고 말했다.[13][14]
2000년 7월 하일스베르는 C#이 "자바 복제품이 아니며" 설계 면에서 "C++에 훨씬 더 가깝다"고 언급했다.[15]
2005년 11월 C# 2.0이 출시된 이후 C#과 자바는 점점 더 다른 궤적으로 진화하여 완전히 다른 두 언어가 되었다. 첫 번째 큰 차이점 중 하나는 두 언어에 제네릭이 추가된 것이었는데, 구현 방식이 크게 달랐다. C#은 구체화를 사용하여 다른 클래스처럼 사용할 수 있는 "일급" 제네릭 객체를 제공하며, 클래스 로드 시점에 코드 생성이 수행된다.[16]
C#은 또한 함수형 스타일 프로그래밍을 수용하기 위해 여러 주요 기능을 추가했으며, 이는 C# 3.0과 함께 출시된 LINQ 확장과 이를 지원하는 람다 식, 확장 메서드, 익명 타입 프레임워크에서 정점을 찍었다.[17] 이러한 기능들은 C# 프로그래머가 애플리케이션에 유리할 때 클로저와 같은 함수형 프로그래밍 기법을 사용할 수 있게 해준다. LINQ 확장과 함수형 도입은 개발자가 데이터베이스 쿼리, XML 파일 파싱 또는 데이터 구조 검색과 같은 일반적인 작업에서 상용구 코드의 양을 줄이고 실제 프로그램 로직에 집중하게 함으로써 가독성과 유지보수성을 향상시키는 데 도움을 준다.[18]
C#에는 이전에 앤디(Andy, 아네르스 하일스베르의 이름을 딴)라는 마스코트가 있었다. 2004년 1월 29일에 은퇴했다.[19]
C#은 원래 ISO/IEC JTC 1/SC 22 소위원회에 검토를 위해 제출되었으며,[20] ISO/IEC 23270:2003으로 등록되었다가[21] 철회 후 ISO/IEC 23270:2006으로 승인되었다.[22] 23270:2006은 23270:2018로 대체되어 승인되었다.[23]
마이크로소프트는 1988년 증분 컴파일을 위해 설계된 C 언어 변종에 C#이라는 이름을 처음 사용했다.[24] 해당 프로젝트는 완료되지 않았으나 그 이름은 나중에 재사용되었다.
"C 샤프"라는 이름은 음표의 음높이를 반음 높여야 함을 나타내는 음악 기호인 올림표(sharp)에서 영감을 얻었다.[25] 이는 변수를 평가한 후 1을 증가시켜야 함을 나타내는 "++"가 붙은 C++ 언어의 이름과 유사하다. 샤프 기호는 또한 4개의 "+" 기호가 결합된 형태(2x2 격자)와 닮아 있어, 이 언어가 C++의 증분형임을 암시한다.[26]
표시 장치(표준 글꼴, 브라우저 등)의 기술적 제한과 대부분의 자판 배열에 올림표 기호(U+266F ♯ MUSIC SHARP SIGN (♯))가 없기 때문에, 프로그래밍 언어의 표기 시에는 올림표와 유사한 번호 기호(U+0023 # NUMBER SIGN (#))를 선택했다.[27] 이 관례는 ECMA-334 C# 언어 사양에 반영되어 있다.[3]
"샤프" 접미사는 기존 언어의 변종인 여러 마이크로소프트 닷넷 호환 언어에서 사용되어 왔다. 여기에는 J 샤프(마이크로소프트가 설계한 자바 1.1 기반 닷넷 언어), A 샤프(에이다 기반), 함수형 프로그래밍 언어인 F 샤프 등이 포함된다.[28] 에펠 포 닷넷(Eiffel for .NET)의 초기 구현은 Eiffel#으로 불렸으나,[29] 이제 완전한 에펠 언어가 지원되므로 이 이름은 은퇴했다. 이 접미사는 GTK와 다른 그놈 라이브러리의 닷넷 래퍼인 Gtk#, 코코아 래퍼인 Cocoa#와 같은 라이브러리에도 사용되었다.
표준 텍스트 개발(C# 6부터 시작)은 깃허브에서 이루어진다. C# 7은 Ecma에 제출되어 2023년 12월에 승인되었다. 2024년 1월 현재, 승인된 언어 제안을 참고하여 C# 8 표준이 개발 중이다.
C, C++, 자바와 구별되는 C#의 주목할 만한 기능은 다음과 같다:
설계상 C#은 기초가 되는 공통 언어 기반(CLI)을 가장 직접적으로 반영하는 프로그래밍 언어다. 내장 타입의 대부분은 CLI 프레임워크에 의해 구현된 값 타입에 대응한다. 그러나 언어 사양은 컴파일러의 코드 생성 요구 사항을 명시하지 않는다. 즉, C# 컴파일러가 반드시 공통 언어 런타임(CLR)을 대상으로 하거나, 공통 중간 언어(CIL)를 생성하거나, 기타 특정 형식을 생성해야 한다고 규정하지 않는다. 일부 C# 컴파일러는 Objective-C, C, C++, 어셈블리어, 포트란의 전통적인 컴파일러처럼 기계어를 생성할 수도 있다.[60][61]
C#은 var 키워드를 사용한 강력한 암시적 타입 변수 선언을 지원하며,: 470 new[] 키워드와 컬렉션 이니셜라이저를 사용한 암시적 타입 배열을 지원한다.: 80 : 58
C#의 타입 시스템은 두 계열로 나뉜다. 내장된 숫자 타입이나 사용자 정의 구조체와 같은 값 타입은 매개변수로 사용될 때 자동으로 복사본으로 전달되며, 배열, 클래스 인스턴스, 문자열을 포함하는 참조 타입은 해당 객체에 대한 포인터만 전달한다. 문자열은 동등 연산자의 특수한 처리와 불변성 덕분에 실질적으로 값처럼 동작한다. 프로그래머는 문자열을 case 레이블로 사용할 수도 있다. 필요한 경우 값 타입은 자동으로 박싱된다.[62]
C#은 엄격한 불리언 자료형 bool을 지원한다. while이나 if처럼 조건을 받는 문장은 true 불리언 값으로 평가되는 타입의 표현식을 요구한다. C++에도 불리언 타입이 있지만 정수와 자유롭게 상호 변환이 가능하며 if (a)와 같은 표현식은 a가 불리언으로 변환 가능하기만 하면 되므로 a가 정수나 포인터일 수 있다. C#은 "정수가 참 또는 거짓을 의미하는" 접근 방식을 불허하는데, 이는 프로그래머가 정확히 bool을 반환하는 표현식을 사용하도록 강제함으로써 if (a = b)(동등 비교 == 대신 할당 = 사용)와 같은 특정 유형의 프로그래밍 실수를 방지할 수 있기 때문이다.
C#은 C++보다 타입 안전성이 높다. 기본적으로 허용되는 유일한 암시적 변환은 정수 범위 확장과 같이 안전하다고 간주되는 것뿐이다. 이는 컴파일 시점, Just-in-time 컴파일 중, 그리고 일부 경우 실행 시점에 강제된다. 불리언과 정수 사이, 또는 열거형 멤버와 정수 사이(암시적으로 모든 열거형 타입으로 변환될 수 있는 리터럴 0 제외)에서는 암시적 변환이 발생하지 않는다. 사용자 정의 변환은 기본적으로 암시적인 C++의 복사 생성자 및 변환 연산자와 달리 명시적(explicit) 또는 암시적(implicit)으로 명확히 표시되어야 한다.
C#은 제네릭 타입에서 공변성 및 반공변성을 명시적으로 지원한다.: 144 : 23 가상 메서드의 반환 타입 의미론을 통해 어느 정도의 반공변성을 지원하는 C++와는 대조적이다.
C# 언어는 전역 변수나 함수를 허용하지 않는다. 모든 메서드와 멤버는 클래스 내부에 선언되어야 한다. 공개 클래스의 정적 멤버가 전역 변수와 함수를 대체할 수 있다.
지역 변수는 C나 C++와 달리 자신을 감싸는 블록의 변수를 가릴 수 없지만, 타입 수준의 이름은 가릴 수 있다.
메타프로그래밍은 여러 가지 방식으로 달성할 수 있다.
- 리플렉션은 닷넷 API를 통해 지원되며, 타입 메타데이터 조사 및 동적 메서드 호출과 같은 시나리오를 가능하게 한다.
- 표현식 트리[63]는 코드를 각 노드가 조사하거나 실행 가능한 표현식인 추상 구문 트리로 나타낸다. 이를 통해 런타임에 실행 코드를 동적으로 수정할 수 있다. 표현식 트리는 언어에 일부 동형성을 부여한다.
- C# 용어로 특성(Attribute)은 타입, 멤버 또는 어셈블리 전체에 부착할 수 있는 메타데이터로, 자바의 애너테이션과 동일하다. 특성은 리플렉션을 통해 컴파일러와 코드 모두에서 접근 가능하며, 동작을 조정할 수 있게 해준다.[64] 많은 기본 특성들이 GCC나 VisualC++의 플랫폼 의존적인 전처리기 지시문 기능을 복제한다.
System.Reflection.Emit네임스페이스[65]는 런타임에 메타데이터와 CIL(타입, 어셈블리 등)을 방출하는 클래스들을 포함한다.- 닷넷 컴파일러 플랫폼(Roslyn)은 언어 컴파일 서비스에 대한 API 접근을 제공하여 닷넷 애플리케이션 내에서 C# 코드를 컴파일할 수 있게 한다. 코드의 구문(낱말 분석) 분석, 의미 분석, CIL로의 동적 컴파일 및 코드 방출을 위한 API를 노출한다.[66]
- Roslyn C# 컴파일러의 기능인 소스 생성기[67]는 컴파일 시점의 메타프로그래밍을 가능하게 한다. 컴파일 과정 중에 개발자는 컴파일러의 API를 사용하여 컴파일 중인 코드를 조사하고 추가로 생성된 C# 소스 코드를 전달하여 함께 컴파일할 수 있다.
C#의 메서드는 필드(클래스 변수 또는 인스턴스 변수)처럼 단순히 값을 보유하는 기능이 아니라, 함수로서 호출될 수 있는 클래스의 멤버다.[68] C++ 및 ANSI C와 같은 구문적으로 유사한 언어들과 마찬가지로, 메서드의 시그니처는 선택적인 접근성 키워드(예: private), 반환 타입의 명시적 지정(예: int 또는 값이 반환되지 않을 경우 void 키워드), 메서드 이름, 그리고 마지막으로 쉼표로 구분된 매개변수 사양의 괄호 시퀀스(각각 타입, 공식 이름 및 선택적으로 기본값 포함)로 구성된 선언이다. 대부분의 다른 언어와 달리, 참조 호출(call-by-reference) 매개변수는 함수 정의와 호출부 모두에서 표시되어야 한다. 프로그래머는 ref와 out 중에서 선택할 수 있는데, 후자는 반환 시 확정된 값을 갖게 될 초기화되지 않은 변수를 전달할 수 있게 한다.[69] 또한 프로그래머는 마지막 매개변수에 params 키워드를 적용하여 가변 크기 인수 목록을 지정할 수 있다.[70] 단순히 필드의 값을 반환하거나 할당하여 가져오거나 설정하는 특정 종류의 메서드는 명시적으로 전체 시그니처를 요구하지 않지만, 일반적인 경우 클래스 정의에는 메서드의 전체 시그니처 선언이 포함된다.[71]
C++와 비슷하고 자바와는 다르게, C# 프로그래머는 메서드가 하위 클래스에 의해 재정의될 수 있도록 하려면 범위 수정자 키워드인 virtual을 사용해야 한다. 또한 재정의할 때는 반드시 override 키워드를 명시적으로 지정해야 한다.[72] 이는 함수 재정의와 새로운 오버로딩(즉, 이전 구현을 가리는 것) 사이의 혼동을 피하기 위함이다. 후자를 수행하려면 프로그래머는 new 키워드를 지정해야 한다.[73] sealed 키워드는 개별 메서드나 클래스 전체에 대해 추가적인 재정의를 금지하는 데 사용될 수 있다.[74]
C#의 확장 메서드를 사용하면 프로그래머가 정적 메서드를 마치 클래스의 메서드 테이블에 있는 메서드처럼 사용할 수 있으며, 특정 종류의 객체(및 해당 파생 클래스의 인스턴스)에 존재해야 한다고 생각하는 인스턴스 메서드를 가상으로 추가할 수 있다.: 103–105 : 202–203
dynamic 타입은 런타임 메서드 바인딩을 허용하여 자바스크립트와 유사한 메서드 호출 및 런타임 객체 구성을 가능하게 한다.: 114–118
C#은 delegate 키워드를 통해 타입 안전한 함수 포인터를 지원한다. Qt 프레임워크의 의사 C++ 신호와 슬롯처럼, C#은 발행-구독 스타일 이벤트를 둘러싼 의미론을 가지고 있으며 이를 위해 델리게이트를 사용한다. 필드와 달리 event 변수는 인터페이스의 일부가 될 수 있는데, 기술적으로 호출될 델리게이트를 추가하고 제거하는 두 개의 기본 함수로 구성되기 때문이다.
C#은 [MethodImpl(MethodImplOptions.Synchronized)] 특성을 통해 자바와 유사한 synchronized 메서드 호출을 제공하며, lock 키워드를 통해 상호 배제 잠금을 지원한다.
C#은 프로퍼티가 있는 클래스를 지원한다. 프로퍼티는 지원 필드가 있는 단순한 접근자 함수이거나 임의의 getter 및 setter 함수를 구현할 수 있다. setter가 없으면 읽기 전용 프로퍼티가 된다. 필드와 마찬가지로 클래스 및 인스턴스 프로퍼티가 있을 수 있다. 기반이 되는 메서드는 다른 메서드와 마찬가지로 virtual이거나 abstract일 수 있다.[71]
C# 3.0부터는 자동 구현 프로퍼티라는 신택틱 슈거가 도입되어,[75] 접근자(getter)와 설정자(setter)가 클래스의 단일 필드에 대한 작업을 캡슐화한다.
C# namespace는 자바의 package나 C++의 namespace와 동일한 수준의 코드 격리를 제공하며, 규칙과 기능이 package와 매우 유사하다. 네임스페이스는 "using" 구문을 사용하여 가져올 수 있다.[76]
C#에서 메모리 주소 포인터는 안전하지 않음(unsafe)으로 명시적으로 표시된 블록 내에서만 사용할 수 있으며,[77] 안전하지 않은 코드가 포함된 프로그램은 실행을 위해 적절한 권한이 필요하다. 대부분의 객체 접근은 항상 "살아있는" 객체를 가리키거나 잘 정의된 null 값을 갖는 안전한 객체 참조를 통해 이루어진다. "죽은" 객체(쓰레기 수집된 객체)나 임의의 메모리 블록에 대한 참조를 얻는 것은 불가능하다. 안전하지 않은 포인터는 클래스 인스턴스, 배열 또는 문자열과 같이 쓰레기 수집 대상 객체에 대한 참조를 포함하지 않는 비관리 값 타입의 인스턴스를 가리킬 수 있다. 안전하지 않음으로 표시되지 않은 코드라도 System.IntPtr 타입을 통해 포인터를 저장하고 조작할 수는 있지만 역참조할 수는 없다.
관리되는 메모리는 명시적으로 해제할 수 없으며, 대신 자동으로 쓰레기 수집된다. 쓰레기 수집은 대부분의 경우 더 이상 필요하지 않은 메모리를 해제해야 하는 프로퍼티의 책임을 덜어줌으로써 메모리 누수 문제를 해결한다. 필요 이상으로 객체에 대한 참조를 오래 유지하는 코드는 여전히 필요 이상의 메모리를 사용할 수 있지만, 객체에 대한 마지막 참조가 해제되면 해당 메모리는 쓰레기 수집이 가능해진다.
프로그래머가 사용할 수 있는 다양한 표준 예외가 제공된다. 표준 라이브러리의 메서드는 특정 상황에서 규칙적으로 시스템 예외를 발생시키며, 발생하는 예외의 범위는 대개 문서화되어 있다. 클래스에 대해 사용자 정의 예외 클래스를 정의하여 필요에 따라 특정 상황에 대한 처리를 마련할 수 있다.[78]
예외 처리를 위한 구문은 다음과 같다:
try { // 실행할 코드 } catch (Exception ex) { // 오류 발생 시 수행할 작업 } finally { // 오류 발생 여부와 상관없이 항상 실행 }
대부분의 경우 "try"와 "catch" 함수가 모든 C# 버전에서 사용 가능하기 때문에 이를 "try-catch" 코드 블록이라고 부른다.
try { // 실행 코드 } catch (Exception ex) { // 예시 return 0; } finally { return 1; }
계획에 따라 "finally" 부분은 생략할 수 있다. 오류 세부 정보를 조사할 필요가 없다면 (Exception ex) 매개변수도 생략 가능하다. 또한 서로 다른 종류의 예외를 처리하는 여러 개의 "catch" 부분을 둘 수 있다.[79]
(자바와 달리) C#에는 검사된 예외(Checked exceptions)가 없다. 이는 확장성 및 버전 관리 문제에 근거한 의도적인 설계 결정이다.[80]
C++와 달리 C#은 다중 상속을 지원하지 않지만, 클래스는 원하는 만큼의 "인터페이스"(완전 추상 클래스)를 구현할 수 있다. 이는 복잡성을 피하고 CLI 전반의 아키텍처 요구 사항을 단순화하기 위한 수석 아키텍트의 설계 결정이었다.
이름이 같고 매개변수 타입과 순서가 동일한(즉, 시그니처가 같은) 메서드를 포함하는 여러 인터페이스를 구현할 때, 자바와 유사하게 C#은 단일 메서드가 모든 인터페이스를 포함하도록 하거나 필요한 경우 각 인터페이스에 대해 특정 메서드를 가질 수 있도록 허용한다.
C#은 또한 함수 오버로드(일명 임의 다형성)를 제공한다. 즉, 이름은 같지만 시그니처로 구분 가능한 메서드를 가질 수 있다.[81] 자바와 달리 C#은 연산자 오버로딩도 지원한다.[82]
2.0 버전부터 C#은 매개변수 다형성을 제공한다. 즉, List<T>와 같이 임의의 또는 제약된 타입 매개변수를 가진 클래스를 가질 수 있는데, 이는 T 타입의 요소만 포함할 수 있는 가변 크기 배열이다. 프로그래머가 타입 매개변수에 대해 지정할 수 있는 몇 가지 제약 사항이 있다. 특정 타입 X(또는 그 파생 타입)여야 함, 특정 인터페이스를 구현해야 함, 참조 타입이어야 함, 값 타입이어야 함, 공개 매개변수 없는 생성자를 구현해야 함 등이 있다. 이러한 제약 사항 대부분은 결합될 수 있으며, 인터페이스는 원하는 수만큼 지정할 수 있다.[83][84]
LINQ (Language Integrated Query)
[편집]C#은 닷넷 프레임워크를 통해 LINQ를 활용할 수 있는 능력을 갖추고 있다. 개발자는 객체에 IEnumerable<T> 인터페이스가 구현되어 있다면 다양한 데이터 소스를 쿼리할 수 있다. 여기에는 XML 문서, ADO.NET 데이터 세트, SQL 데이터베이스가 포함된다.[85]
C#에서 LINQ를 사용하면 IntelliSense 지원, 강력한 필터링 기능, 컴파일 오류 검사 기능이 있는 타입 안전성, 다양한 소스에 대한 데이터 쿼리의 일관성 등의 장점이 있다.[86] C#과 LINQ에서 활용될 수 있는 여러 언어 구조로는 쿼리 식, 람다 식, 익명 타입, 암시적 타입 변수, 확장 메서드, 객체 이니셜라이저 등이 있다.[87]
LINQ에는 쿼리 구문과 메서드 구문의 두 가지 구문이 있다. 그러나 컴파일러는 컴파일 시점에 항상 쿼리 구문을 메서드 구문으로 변환한다.[88]
using System.Linq; var numbers = new int[] { 5, 10, 8, 3, 6, 12 }; // 쿼리 구문 (SELECT num FROM numbers WHERE num % 2 = 0 ORDER BY num) var numQuery1 = from num in numbers where num % 2 == 0 orderby num select num; // 메서드 구문 var numQuery2 = numbers .Where(num => num % 2 == 0) .OrderBy(n => n);
C#은 주로 명령형 언어이지만, 시간이 흐름에 따라 지속적으로 함수형 기능을 추가해 왔다.[89][90] 예시는 다음과 같다:
C#은 통합된 타입 시스템을 가지고 있다. 이 통합 타입 시스템은 공통 타입 시스템(CTS)이라고 불린다.: Part 2, Chapter 4: The Type System
통합 타입 시스템은 정수와 같은 원시 타입을 포함한 모든 타입이 System.Object 클래스의 하위 클래스임을 의미한다. 예를 들어 모든 타입은 ToString() 메서드를 상속받는다.
CTS는 자료형을 두 가지 범주로 나눈다:
- 참조 타입
- 값 타입
값 타입의 인스턴스는 참조 정체성이나 참조 비교 의미론을 갖지 않는다. 값 타입에 대한 동등 및 부등 비교는 해당 연산자가 오버로드되지 않는 한 인스턴스 내의 실제 데이터 값을 비교한다. 값 타입은 System.ValueType에서 파생되며 항상 기본값을 갖고 항상 생성 및 복사될 수 있다. 값 타입의 다른 제한 사항으로는 서로 파생될 수 없고(인터페이스 구현은 가능), 명시적인 매개변수 없는 기본 생성자를 가질 수 없다는 점이 있는데, 이는 포함된 모든 데이터를 타입 의존적 기본값(0, null 등)으로 초기화하는 암시적 생성자를 이미 가지고 있기 때문이다. 값 타입의 예로는 int(부호 있는 32비트 정수), float(32비트 IEEE 부동 소수점 수), char(16비트 유니코드 코드 단위), decimal(통화 금액 처리에 유용한 고정 소수점 수), System.DateTime(나노초 정밀도로 특정 시점을 식별)과 같은 모든 원시 타입이 있다. 다른 예로는 enum(열거형)과 struct(사용자 정의 구조체)가 있다.
대조적으로 참조 타입은 참조 정체성의 개념을 갖는다. 즉, 참조 타입의 각 인스턴스는 두 인스턴스 내의 데이터가 동일하더라도 본질적으로 다른 인스턴스와 구별된다. 이는 해당 연산자가 오버로드되지 않는 한(System.String의 경우처럼), 구조적 동등성이 아닌 참조 동등성을 테스트하는 참조 타입의 기본 동등 및 부등 비교에 반영된다. 참조 타입의 인스턴스 생성, 기존 인스턴스 복사, 두 인스턴스에 대한 값 비교 수행과 같은 일부 작업이 항상 가능한 것은 아니다. 그럼에도 불구하고 특정 참조 타입은 공개 생성자를 노출하거나 대응하는 인터페이스(ICloneable 또는 IComparable 등)를 구현함으로써 이러한 서비스를 제공할 수 있다. 참조 타입의 예로는 object(다른 모든 C# 클래스의 궁극적인 기본 클래스), System.String(유니코드 문자열), System.Array(모든 C# 배열의 기본 클래스)가 있다.
두 타입 범주 모두 사용자 정의 타입으로 확장 가능하다.
박싱은 값 타입 객체를 대응하는 참조 타입의 값으로 변환하는 작업이다. C#에서 박싱은 암시적이다.
언박싱은 (이전에 박싱된) 참조 타입의 값을 값 타입의 값으로 변환하는 작업이다. C#에서 언박싱은 명시적인 형 변환을 요구한다. 박싱된 T 타입 객체는 T(또는 널러블 T)로만 언박싱될 수 있다.[98]
예시:
int foo = 42; // 값 타입. object bar = foo; // foo가 bar로 박싱됨. int foo2 = (int)bar; // 다시 값 타입으로 언박싱됨.
다음은 C# 9에서 도입된 최상위 문 기능을 사용한 매우 단순한 C# 프로그램인 클래식 "Hello World" 예제다.[100]
Console.WriteLine("Hello, World!");
C# 8 이하 버전으로 작성된 코드의 경우, 프로그램의 진입점 로직은 타입 내부의 Main 메서드에 작성되어야 한다.
using System; class Program { static void Main() { Console.WriteLine("Hello, World!"); } }
이 코드는 콘솔 창에 다음 텍스트를 표시한다:
Hello, World!
각 줄은 목적이 있다:
위 줄은 System 네임스페이스의 모든 타입을 가져온다. 예를 들어 소스 코드 나중에 사용되는 Console 클래스는 System 네임스페이스에 정의되어 있으므로, (네임스페이스를 포함한) 타입의 전체 이름을 제공하지 않고도 사용할 수 있다.
// 클래식 "Hello World" 프로그램의 한 버전
이 줄은 주석으로, 프로그래머를 위해 코드를 설명하고 기록한다.
위는 Program 클래스에 대한 클래스 정의다. 중괄호 쌍 사이에 오는 모든 내용은 해당 클래스를 설명한다.
중괄호는 코드 블록의 경계를 구분한다. 이 첫 번째 인스턴스에서 중괄호는 Program 클래스의 시작과 끝을 표시한다.
이는 프로그램 실행이 시작되는 클래스 멤버 메서드를 선언한다. 닷넷 런타임은 Main 메서드를 호출한다. 자바와 달리 Main 메서드에 public 키워드가 필요하지 않다. 이 키워드는 컴파일러에게 어떤 클래스에서든 어디서나 메서드를 호출할 수 있음을 알리는 역할을 한다.[101] static void Main(string[] args)를 쓰는 것은 private static void Main(string[] args)를 쓰는 것과 동일하다. static 키워드는 Program의 인스턴스 없이도 메서드에 접근할 수 있게 한다. 각 콘솔 애플리케이션의 Main 진입점은 반드시 static으로 선언되어야 한다. 그렇지 않으면 프로그램은 Program의 인스턴스를 요구하게 되지만, 어떤 인스턴스든 프로그램을 요구하게 된다. 이러한 해결 불가능한 순환 의존성을 피하기 위해, 콘솔 애플리케이션을 처리하는 C# 컴파일러는 static Main 메서드가 없으면 오류를 보고한다. void 키워드는 Main이 반환 값을 가지지 않음을 선언한다. (다만 앞서 언급했듯이 C# 9에서 도입된 최상위 문을 사용하여 짧은 프로그램을 작성할 수도 있다.)
Console.WriteLine("Hello, World!");
이 줄은 출력을 기록한다. Console은 System 네임스페이스에 있는 정적 클래스다. 이는 콘솔 애플리케이션을 위한 표준 입출력 및 오류 스트림에 대한 인터페이스를 제공한다. 프로그램은 Console 메서드인 WriteLine을 호출하여 인수인 문자열 "Hello, World!"를 한 줄로 콘솔에 표시한다.
닷넷 2.0 및 C# 2.0과 함께 커뮤니티는 닷넷 1.x보다 더 유연한 컬렉션을 갖게 되었다. 제네릭이 없었을 때 개발자들은 요소를 불특정 종류의 객체로 저장하기 위해 ArrayList와 같은 컬렉션을 사용해야 했으며, 이는 포함된 항목을 박싱/언박싱/타입 검사할 때 성능 오버헤드를 발생시켰다.
제네릭은 개발자가 타입 안전한 데이터 구조를 만들 수 있게 해주는 거대한 새 기능을 닷넷에 도입했다. 이러한 변화는 특히 레거시 시스템을 전환하는 맥락에서 중요한데, 제네릭으로 업데이트함으로써 구식 데이터 구조를 더 효율적이고 타입 안전한 대안으로 교체하여 성능과 유지보수성을 크게 향상시킬 수 있기 때문이다.[102]
예시
public class DataStore<T> { private T[] items = new T[10]; private int count = 0; public void Add(T item) { items[count++] = item; } public T Get(int index) { return items[index]; } }
2001년 8월, 마이크로소프트, 휴렛 팩커드, 인텔은 C# 및 공통 언어 기반(CLI)에 대한 사양을 표준 기구인 Ecma 인터내셔널에 공동 제출했다. 2001년 12월 ECMA는 ECMA-334 C# 언어 사양을 발표했다. C#은 2003년에 ISO/IEC 표준이 되었다(ISO/IEC 23270:2003 - 정보 기술 — 프로그래밍 언어 — C#). ECMA는 이전에 2002년 12월에 C#의 2판으로 동등한 사양을 채택한 바 있다. 2005년 6월 ECMA는 C# 사양의 3판을 승인하고 ECMA-334를 업데이트했다. 추가된 내용으로는 부분 클래스, 익명 메서드, 널러블 타입, 제네릭(C++ 템플릿과 다소 유사함)이 포함되었다. 2005년 7월 ECMA는 후자의 패스트트랙 프로세스를 통해 ISO/IEC JTC 1/SC 22에 표준 및 관련 TR을 제출했다. 이 프로세스는 보통 6~9개월이 소요된다.
C# 언어 정의와 CLI는 특허 청구로부터 합리적이고 비차별적인 라이선스 보호를 제공하는 ISO/IEC 및 Ecma 표준에 따라 표준화되어 있다.
마이크로소프트는 처음에 오픈 사양 약속이 적용되는 프레임워크 부분에 대해 비영리 프로젝트의 오픈 소스 개발자들을 특허 침해로 고소하지 않기로 합의했다.[103] 마이크로소프트는 또한 노벨의 유료 고객을 대상으로 노벨 제품과 관련된 특허를 집행하지 않기로 합의했으나,[104] C#, 닷넷 또는 노벨의 닷넷 구현체(모노 프로젝트)를 명시적으로 언급하지 않는 제품 목록은 예외로 두었다.[105] 그러나 노벨은 모노가 마이크로소프트의 특허를 침해하지 않는다는 입장을 고수했다.[106] 마이크로소프트는 또한 노벨을 통해 입수한 경우에 한해 모노에 의존하는 문라이트 브라우저 플러그인과 관련된 특허권을 행사하지 않기로 구체적인 합의를 했다.[107]
10년 후, 마이크로소프트는 C#을 위한 무료, 오픈 소스 및 교차 플랫폼 도구인 비주얼 스튜디오 코드, 닷넷 코어, Roslyn을 개발하기 시작했다. 모노는 마이크로소프트의 자회사인 자마린의 프로젝트로서 마이크로소프트에 합류했다.