by 부디 쿠니아완(Budi Kurniawan)
C#은 마이크로소프트가 새로 개발한 프로그래밍 언어이며, C/C++ 분야에서 "첫 번째 컴포넌트 지향 언어"라고 홍보하고 있다. 이러한 주장에도 불구하고, 많은 사람들은 마이크로소프트가 자바와 대적하기 위해 C#을 만들었다고 생각한다. 과연 이러한 생각이 옳은 것인가?
이 글에서는 C#이 자바 이상의 기능을 가지고 있다는 것을 보여주려 한다. C#에 대해 좀더 배우기를 원하는 자바 프로그래머라면, 반드시 이 기사를 읽어 보기 바란다.
C#과 C++, 자바
현재 선전하고 있는 내용을 고려하면, 마이크로소프트의 앤더스 헤즐버그(Anders Hejlberg)와 스코트 윌터무스(Scott Wiltamuth)가 작성한 C# 언어 명세를 C++나 자바와 비교하는 것은 항상 재미있는 일이다. 최근 IT 관련 신문의 헤드라인을 장식하고 있는 문구를 생각해 보면, C#이 C++보다 자바와 더 비슷하다는 것을 이미 알고 있는 것은 놀라운 일이 아니다. 이러한 말을 처음 들어 본 사람을 위해, [표 1]을 마련했다. 결론적으로, C#과 자바는 샴 쌍둥이(역주: 몸이 붙어있는 쌍둥이)는 아니지만, C#에서 가장 중요한 특성은 C++보다 자바에 더 가깝다.
[표1] C#과 C++, 자바를 비교했을 때, 가장 중요한 특성
개략적으로 주요 특성을 정리해 보았다. 이것을 훑어보고, C#과 자바의 중요한 차이점을 알아보기 바란다.
언어 명세 비교
기초 타입(Primitives)
C#에 있는 기초 타입을 값 타입(value type)이라고 하며, 미리 정의된(predefined) 값 타입이 자바에서보다 많다. 예를 들어서 C#에는
uint(unsigned integer)가 있다. [표 2]에는 C#의 미리 정의된 타입을 모두 정리해 놓았다.
[표 2] C#의 값 타입
상수
자바에서의
static final 변경자(modifier)는 잊어라. C#에서는 const 키워드를 사용하면 상수(constant)를 선언할 수 있다.
그리고 C#의 설계자는
readonly 키워드를 추가했으며, 상수값이 컴파일할 때 결정될 수 없는 경우 사용할 수 있다. 이 읽기 전용 필드는 초기자(initializer)나 클래스 생성자(constructor)를 통해서만 설정이 가능하다.
public 클래스의 시작점
자바에서 public 클래스의 시작점(entry point)은
main이라는 public 정적(static) 메소드이다. 이는 스트링 객체의 배열을 인자(argument)로서 수용하고, 아무런 값도 반환하지 않는다. C#에서는 주요 메소드가
Main(대문자 M을 쓴다)이라는 public 정적 메소드이다. 이도 역시 다음 사인(signature)에 따라 스트링 객체의 배열을 수용하고, 아무런 값도 반환하지 않는다.
public static void Main(String[] args)
|
하지만 C#에서는 추가된 사항이 있다.
Main 메소드에 아무 것도 전달하지 않는다면,
Main의 오버로드(overload)를 사용할 수 있다. 따라서, 다음의
Main 메소드도 유효한 시작점이다.
public static void Main()
|
게다가,
Main 메소드는 int를 반환할 수 있다. 예를 들면, 다음 코드의
Main 메소드는 1을 반환한다.
using System;
public class Hello {
public static int Main() {
Console.WriteLine("Done");
return 1;
}
}
|
자바에서는 main 메소드를 오버로드할 수 없다.
swich문
자바에서는
switch 문이 정수에서만 사용될 수 있었지만, C#에서는 스트링 변수와도 함께 사용될 수 있다. 다음은 C# 에서 switch문을 스트링 변수와 함께 사용하는 예제이다.
using System;
public class Hello {
public static void Main(String[] args) {
switch (args[0]) {
case "boss":
Console.WriteLine("Good morning, Sir. We are ready to serve you.");
break;
case "employee":
Console.WriteLine("Good morning. You can start working now.");
break;
default:
Console.WriteLine("Good morning. How are you today?");
break;
}
}
}
|
자바와는 달리, C#의 switch문에서는 각
case 블록 끝부분에 break가 있거나 switch문의 또다른
case 레이블에
goto가 있어야 하는 코드를 읽을 때에는 실패를 허용하지 않는다.
foreach문은 컬렉션에 있는 각 요소에 따라 문장을 실행하면서, 컬렉션의 요소를 열거한다. 다음 코드를 살펴보자.
using System;
public class Hello {
public static void Main(String[] args) {
foreach (String arg in args)
Console.WriteLine(arg);
}
}
|
만약 다음과 같이 실행 가능한 것을 호출할 때 인자를 넘겨주면,
Hello Peter Kevin Richard
|
아래와 같은 결과를 얻을 수 있다.
uint나
ulong과 같은 unsigned 변수 타입이 있다. 따라서 C#에서는 우측 시프트(shift) 연산자(
>>)가 unsigned 변수 타입과 signed 변수 타입(
int,
long 등)에서 다르게 작용한다. 우측 시프트가 수행되면,
uint나
ulong이 낮은 순서의 비트를 버리고, 높은 순서의 비어있는 비트 포지션을 0으로 설정한다. 하지만
int나
long 변수가 있을 때에는 변수 값이 양수일 때에만
>>가 낮은 순서의 비트를 버리고, 높은 순서의 비어있는 비트 포지션을 0으로 만든다.
자바에서는 unsigned 변수가 없다. 따라서 우측 시프트에서 음수 비트를 포함하기 위해서 사용할 때에는
>>> 연산자를 사용하며, 그 밖의 경우에는
>>를 사용한다.
goto
자바에서는
goto 키워드를 사용하지 않는다. C#에서 goto를 사용하면 특정 레이블을 불러온다. 하지만 C#에서는
goto를 특별하게 처리한다. 예를 들어,
goto는 문장 블록에 들어가기 위해 사용될 수 없다. C#의
goto 대신 자바에서는
break나
continue 레이블 문을 사용한다.
배열 선언
자바에서는 여러 가지 방법으로 배열을 선언할 수 있다. 예를 들면, 다음 코드의 결과는 동일하다.
int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };
|
하지만 C#에서는 첫 번째 것만 유효하다. []가 변수명 뒤에 올 수 없기 때문이다.
패키지 만들기
C#에 있는 패키지를 네임스페이스(namespace)라고 부른다. C#에 있는 네임스페이스를 임포트(import)하려면 "using"이라는 단어를 사용해야 한다. 다음 코드는 System 네임스페이스를 임포트한다.
하지만 자바에서와는 달리, C#에서는 네임스페이스를 위한 별칭(alias)이나 네임스페이스의 클래스를 사용할 수 있다.
using TheConsole = System.Console;
public class Hello {
public static void Main() {
TheConsole.WriteLine("Using an alias");
}
}
|
자바 패키지는 닷넷의 네임스페이스와 개념적으로는 동일하지만, 다르게 구현된다. 자바에서는 패키지 이름이
.java 파일이 반드시 존재하는 디렉토리 구조를 결정하는 물리적인 기능도 할 수 있다. C#에서는 물리적인 패키지와 논리적인 이름과는 완전히 분리되어 있다. 그래서 네임스페이스를 위한 이름은 물리적인 패키지와 아무런 연관이 없다. 또한 C#에서는 각 소스 파일은 다수의 네임스페이스를 제공하며, 다수의 public 클래스를 얻을 수 있다.
닷넷에서의 물리적인 패키지를 어셈블리(assembly)라고 부른다. 각 어셈블리에는 포함된 파일 목록이 있고, 어떤 타입과 자원이 어셈블리 외부에 노출되어 있는지 어셈블리에서 통제하며, 그러한 타입과 자원에서 파일로 레퍼런스의 맵을 표시한다. 어셈블리에서는 자신을 포함하고 있으며, 하나의 어셈블리는 하나의 파일에 포함되거나 여러 파일에 포함될 수 있다. 이 패키지 메커니즘은 "악명 높은 DLL 지옥"과 같은 DLL파일 문제를 해결해 준다.
디폴트 패키지
자바에서는
java.lang 패키지가 디폴트 패키지이다. 이는 임포트될 필요 없이 자동으로 포함된다. 텍스트를 콘솔에서 출력하려면, 다음 코드를 입력하면 된다.
System.out.println("Hello world from Java");
|
C#에서는 디폴트 패키지가 없다. 텍스트를 콘솔로 출력하려면, 시스템의 네임스페이스에 있는 콘솔 객체의 WriteLine 메소드를 사용하면 된다. 하지만, 패키지를 항상 따로 임포트해야 한다. 다음 예제를 보자.
using System;
public class Hello {
public static void Main() {
Console.WriteLine("Hello world from C#");
}
}
|
객체지향
자바와 C#은 둘 다 객체지향적인 언어이며, 객체지향 프로그래밍이라는 점에서는 비슷하다.
- 상속(inheritance): 두 언어 모두 클래스의 단일 상속을 지원하지만, 하나의 클래스로 다중 인터페이스를 구현할 수 있다. 모든 클래스는 공통 기초 클래스로부터 상속된다.
- 캡슐화(encapsulation)와 가시성(visibility): C#과 자바에서는 클래스 멤버를 어느 정도까지 가시성 있게 할 것인지 정할 수 있다. C#에 있는 내부 접근 변경자(modifier)를 제외하면 접근성이 매우 비슷하다.
- 다형성(Polymorphism): 자바와 C# 모두 매우 유사한 방식으로 다형성을 지원한다.
접근성
클래스의 각 멤버는 일정 형태의 접근성이 있다. C#에 있는 접근 변경자는
internal을 추가하여, 자바와 유사하다. 요약하면, 아래와 같이 다섯 가지 형태의 접근성이 있다.
- public은 모든 코드에서 사용할 수 있다.
- protected는 파생된(derive) 클래스에서만 접근할 수 있다.
- internal은 동일한 어셈블리 내에서만 접근할수 있다.
- protected internal은 동일한 어셈블리 내의 파생된 클래스에서만 접근할 수 있다.
- private는 클래스 자체에서만 접근할 수 있다.
클래스 확장
자바에서 상속을 수행할 때에는 "extends"라는 키워드를 사용하였을 것이다. 하지만 C#에서는 클래스를 확장할 때 C++ 스타일을 적용하였다. 예를 들면, 다음은
Control이라는 상위 클래스를 확장하여
Button이라는 새로운 클래스를 만드는 방법이다.
public class Button: Control {
.
.
.
}
|
최종 클래스
C#에는
final 키워드가 없기 때문에, 클래스를 확장하려면 다음 예제와 같이
sealed 키워드를 사용한다.
sealed class FinalClass {
.
.
.
}
|
인터페이스
C#에서 인터페이스의 개념은 자바와 매우 비슷하다.
interface라는 키워드가 있으며, 인터페이스는 하나 이상의 인터페이스로 확장될 수 있다. 관습적으로, 인터페이스 이름은 대문자 I로 시작한다. 다음 코드는 C#에 있는 인터페이스의 예이다. 이는 자바의 인터페이스와 구분이 안될 정도로 유사하다.
interface IShape {
void Draw();
}
|
인터페이스를 확장하는 구문은 클래스를 확장하는 것과 비슷하다. 예를 들어, 아래에 있는
IRectangularShape 인터페이스는
IShape 인터페이스를 확장한다.
interface IRectangularShape: IShape {
int GetWidth();
}
|
인터페이스를 확장하고 있다면, 다음 상위 인터페이스 목록은 다음 코드와 같이 컴마(,)로 구분된다.
interface INewInterface: IParent1, IParent2 {
}
|
하지만 자바와는 달리 C#의 인터페이스는 필드를 포함할 수 없다. C#에서도 디폴트에 의해 인터페이스에 있는 모든 메소드는 public이라는 점에 주목하자. 자바와는 달리, 변경자
public이 메소드 사인에 나타날 수 있으며(하지만 꼭 필요한 것은 아니다), 인터페이스 메소드를 public으로 지정할 수 없다. 예를 들어서, 다음 인터페이스는 C#에서 컴파일 에러를 생성한다.
interface IShape {
public void Draw();
}
|
is와 as 연산자
C#의
is 연산자는 자바의
instanceof 연산자와 동일하다. 둘 다 객체의 인스턴스가 특정 타입이 아니더라도 테스트하기 위해 사용할 수 있다. C#에 있는
as 연산자와 대응되는 것이 자바에는 없다. 그것은
is 연산자와 비슷하지만, 타입이 타당할 경우, 테스트된 객체 레퍼런스를 질문에 있는 타입으로 변환한다는 점에서 더 공격적이다. 타입이 타당하지 않다면, 변수 레퍼런스는 널(null)로 설정될 것이다.
as가 어떻게 작동하는지 이해하려면, 다음 코드에서
is가 어떻게 사용되는지 살펴보자. 다음 코드에는
Ishape라는 인터페이스가 있고, 클래스(
Rectangle과
Circle)이 둘 다
IShape를 구현한다.
using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
if (shape is Rectangle) {
Rectangle rectangle = (Rectangle) shape;
Console.WriteLine("Width : " + rectangle.GetWidth());
}
if (shape is Circle) {
Circle circle = (Circle) shape;
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}
|
코드가 컴파일된 후에, 사용자는
Main 메소드의
args[0]에 있는 모양으로서 "retangle"이나 "circle" 둘 중 아무거나 입력해도 된다. 만약 "circle"이라고 입력했을 경우,
shape는
Circle 객체에 예시된다. 한편, 사용자가 "retangle"이라고 입력하면,
shape는
Rectangle에 예시된다. "shape"는 그 다음에
is 연산자를 사용하는 객체 타입에 대해 테스트된다. 그것이 retangle이라면,
shape는
Rectangle 객체에 캐스트되고,
GetWidth 메소드가 호출된다. Circle이라면,
shape는
Circle 객체에 캐스트되고,
GetRadius 메소드가 호출된다.
as 연산자를 사용하면, 위에 있는 예제 코드는 다음과 같이 수정된다.
using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
Rectangle rectangle = shape as Rectangle;
if (rectangle != null) {
Console.WriteLine("Width : " + rectangle.GetWidth());
}
else {
Circle circle = shape as Circle;
if (circle != null)
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}
|
위 코드의 볼드체 부분에서
as는 객체 타입을 테스트하지 않고
shape를
Rectangle로 변경하는 데에 사용되었다. 만약
shape가 정말
Rectangle이라면,
shape는
Rectangle 객체로서
rectangle에 캐스트되고,
GetWidth 메소드가 호출된다. 만약 변환에 실패할 경우, 두 번째 시도가 수행된다. 이번에는
shape가
Circle 객체로서
circle에 캐스트된다.
shape가 정말로
Circle 객체라면,
circle은
Circle 객체에 참조를 제공하고,
GetRadius 메소드가 호출된다.
라이브러리
C#에는 고유한 클래스 라이브러리가 없다. 하지만 VB.NET이나 Jscript.NET.과 같이 닷넷 언어에서 사용되는 닷넷 클래스 라이브러리를 공유한다. StringBuilder 클래스에 대해서는 주목할 필요가 있다. 이것은 String 클래스를 보충해주는 역할을 한다. StringBuilder 클래스는 자바의 StringBuffer와 비슷하다.
가비지 컬렉션(Garbage Collection)
C++를 통해 우리는 수동으로 메모리를 관리하는 것이 얼마나 비효율적이고 시간낭비인지 알게 되었다. C++에서 객체를 만들 때에는, 그것을 수동으로 제거해야 했다. 코드가 복잡해질수록, 이 작업은 점점 어려워졌다. 자바에는 사용하지 않는 객체를 모아서 메모리의 부담을 가볍게 하는 가비지 컬렉션 메소드가 있기 때문에, 이 작업이 쉬워졌다. C#도 자바의 선례를 따랐다. 하지만, 만약 새로운 객체지향 프로그래밍(OOP, Object-Oriented Programming) 언어를 개발하고 있다면, 이는 당연한 결과일 것이다. C#에는 여전히 C++에서와 같이 메모리를 수동으로 관리하는 방식이 남아 있다. 빠른 속도가 요구될 때 수동으로 메모리를 관리하는데, 이는 자바에서는 금기시되는 사항이다.
예외 처리
C#의 에러 처리 메커니즘이 자바와 비슷하다 해도 놀랄 것이 없다. C#에서는 모든 예외가 Exception이라는 이름의 클래스에서 파생된다(이 말이 친숙하게 들릴 것이다). 그리고
try문과도 친숙할 것이다. Exception 클래스는 닷넷 시스템 네임스페이스의 일부이다.
자바에는 없는 것
자바가 이미 성숙된 다음에 C#이 출현했기 때문에, 자바에 (아직) 없는 것이 C#에는 있다는 것이 놀랄 만한 일은 아니다.
열거자(enumerators)
열거자(enumerator)는 관련 상수의 집합이다. 정확하게 말하면, enum 타입 선언은 상징적 상수의 관련 그룹에 대한 타입 이름을 정의한다. 예를 들어서, Fruit라는 이름으로 열거자를 만들고, 변수의 가능 값을 열거자에 명시되어 있는 대로 제한하기 위해 변수의 값 타입으로 사용할 수 있다.
public class Demo {
public enum Fruit {
Apple, Banana, Cherry, Durian
}
public void Process(Fruit fruit) {
switch (fruit) {
case Fruit.Apple:
...
break;
case Fruit.Banana:
...
break;
case Fruit.Cherry:
...
break;
case Fruit.Durian:
...
break;
}
}
}
|
위에서 예로 든 Process 메소드에서, int를 myVar 변수의 값 타입으로 사용할 수 있다. 하지만 enum Fruit을 사용하면 산출 가능한 값을 Apple, Banana, Cherry, Durian으로 제한한다.
int와 비교할 때 enum은 더 읽기 편하다.
구조체(struct)
구조체(struct)는 클래스와 비슷하다. 하지만 클래스가 레퍼런스 타입으로서 힙(heap)에서 만들어지는 반면, 구조체는 스택(stack)이나 인라인(in-line)에서 저장되는 값 타입이다. 따라서 주의깊게 사용하면, 구조체는 클래스보다 더 빠를 수 있다. 구조체는 인터페이스를 구현하고, 같은 종류의 멤버를 클래스로서 가지지만, 상속을 지원하지 않는다. 하지만 단순하게 클래스를 구조체로 대체하는 것은 위험할 수 있다. 구조체는 값에 의해 넘겨지기 때문에, 값이 새로운 장소에 복사되어야 한다. 따라서 "용량이 큰" 구조체는 넘겨줄 때 시간이 오래 걸린다. 클래스의 경우, 레퍼런스만이 넘겨진다. 다음은 구조체의 예이다. 이것이 클래스와 얼마나 비슷한지 살펴보자.
struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
|
프로퍼티
C# 클래스는 필드 뿐 아니라 프로퍼티(property)도 가진다. 프로퍼티는 객체나 클래스와 관련하여 이름이 붙여진 속성(attribute)이다. 프로퍼티는 필드가 자연스럽게 확장된 것으로, 둘 다 관련 타입과 함께 이름이 붙여지며, 필드에 접근하는 구문과 프로퍼티가 동일하다. 하지만 필드와는 달리, 프로퍼티는 저장 위치를 나타내지 않는다. 그 대신 프로퍼티는 값을 읽거나 쓰기 위해 수행되는 문장을 지시하는 접근자(accessor)를 가진다. 따라서 프로퍼티는 객체의 속성을 읽거나 쓰는 것과 결합하는 메커니즘을 공급하며, 그러한 속성의 연산을 허용한다.
C#에서는 프로퍼티 선언 구문을 사용하여 프로퍼티가 정의된다. 구문의 첫 번째 부분은 필드 선언과 매우 비슷하다. 두 번째 부분에서는 get/set 접근자를 포함한다. 아래에 있는 예제에서,
PropertyDemo 클래스는
Prop 프로퍼티를 정의한다.
public class PropertyDemo {
private string prop;
public string Prop {
get {
return prop;
}
set {
prop = value;
}
}
}
|
PropertyDemo 클래스에 있는
Prop 프로퍼티와 같이, 쓰거나 읽을 수 있는 프로퍼티는 get과 set 접근자를 포함한다. get 접근자는 프러퍼티 값을 읽을 때에 호출되며, set 접근자는 프로퍼티에 값을 쓸 때에 호출된다. set 접근자에서는 프로퍼티의 새로운 값이 묵시적 변수의 매개변수에 주어진다. 프로퍼티는 필드와 같은 방식으로 쓰거나 읽을 수 있다. 예를 들어, 다음 코드에서는 PropertyDemo 클래스를 예시하고, Prop 프로퍼티를 쓰거나 읽는다.
PropertyDemo pd = new PropertyDemo();
pd.Prop = "123"; // set
string s = pd.Prop; // get
|
레퍼런스에 의한 기초 타입의 매개변수 전달
자바에서는 기초 타입을 메소드에 매개변수로서 넘겨줄 때, 매개변수는 항상 값으로 전달된다. 즉, 매개변수의 새로운 복사본이 그 메소드에 만들어진다. C#에서는 기초 타입(값 타입)을 레퍼런스에 의해 전달할 수 있다. 이렇게 하면, 메소드는 그것에 전달된 동일한 변수를 사용한다. 즉, 전달된 값 타입의 값을 바꾸면, 원래의 변수 값도 변한다. 값 타입을 C#에 있는 레퍼런스에 의해 전달하려면,
ref라는 키워드를 사용한다.
i의 값이 ProcessNumber 메소드에 전달된 후 변경되는지 주목하자.
using System;
public class PassByReference {
public static void Main(String[] args) {
int i = 8;
ProcessNumber(ref i);
Console.WriteLine(i);
}
public static void ProcessNumber(ref int j) {
j = 16;
}
}
|
out이라는 키워드도 있다. 이것은 ref와 비슷하며, 값 타입이 레퍼런스에 의해 전달되는 것을 허용한다. 하지만 매개변수로서 전달되는 변수는 전달되기 전에는 알려진 값을 가질 수 없다. 위에 있는 예에서 int
i가 ProcessNumber 메소드로 전달되기 전에 초기화되지 않으면 에러 메시지를 생성할 것이다. 만약
ref 대신에
out을 사용하면, 다음의 수정된 예제와 같이 초기화되지 않은 값을 전달할 수 있을 것이다.
using System;
public class PassByReference {
public static void Main(String[] args) {
int i;
ProcessNumber(out i);
Console.WriteLine(i);
}
public static void ProcessNumber(out int j) {
j = 16;
}
}
|
i가 메소드로 전달되기 전에 초기화되지 않았지만, 여기서는
PassByReference 클래스가 컴파일이 잘 된다.
C#에서는 포인터를 사용할 수 있다.
포인터를 잘 다루며 수동 메모리 관리가 좋다고 생각하는 개발자들은 불안전하고 쉽지도 않은 "이전의" 포인터를 사용하여 수행을 높일 수 있다. C#에서는 "불안전한" 코드를 작성할 수 있는 환경을 제공한다. 이러한 코드는 포인터 타입을 직접적으로 다룰 수 있으며, 객체를 고정하여, 일시적으로 가비지 컬렉터가 그것을 옮기지 못하도록 한다. 이 "불안전한" 코드의 성격은 개발자나 사용자의 관점에서는 "안전"하다. 불안전한 코드는 변경자의 코드에 안전하지 않다고 명백히 표시되기 때문에, 개발자가 의도하지 않고서 안전하지 않은 특성을 사용하지는 않을 것이다. 또한, C# 컴파일러와 실행 환경이 함께 작업하여, 코드가 안전한지의 여부를 가려낸다.
using System;
class UsePointer {
unsafe static void PointerDemo(byte[] arr) {
.
.
.
}
}
|
C#에서는 속도가 매우 중요하거나, COM 객체나 C 코드가 DLL에서 사용되는 것 같이, 객체가 소프트웨어와 인터페이스로 접속해야 할 때 불안전한 코드가 사용된다.
위임자(delegates)
위임자(delegate)는 C++ 등의 언어에 있는 함수 포인터이다. 하지만 함수 포인터와는 달리, C#에 있는 위임자는 객체지향적이며, 모든 타입에서 사용할 수 있으며, 보안에 문제가 없다. 그리고 함수 포인터가 정적 함수에 참조 사항을 달기 위해서만 사용할 수 있는 반면, 위임자로는 정적 메소드와 인스턴스 메소드 모두에 참조를 제공할 수 있다. 위임자는 호출 가능한 메소드를 캡슐화(encapsulate)하기 위해 사용된다. 위임자는 그 다음에 두 번째 메소드로 전달될 수 있다. 그리면 두 번째 메소드에서 첫 번째 메소드를 호출할 수 있게 된다.
위임자는 공통 기초 클래스인
System.Delegate에서 파생된 레퍼런스 타입이다. Delegate를 정의하고 사용할 때에는 선언, instantiation, 호출이라는 세 단계를 거친다. 위임자는
delegate 선언 구문을 사용하여 선언된다. 인자를 취하지 않고 void를 반환하는 위임자는 다음 코드를 통해 선언될 수 있다.
delegate void TheDelegate();
|
위임자 인스턴스는
new 키워드를 사용하고, 위임자에 의해 명시된 사인에 인스턴스나 클래스 메소드의 레퍼런스를 제공하면서 초기화될 수 있다. 위임자가 초기화되면, 메소드 호출 구문을 사용하여 호출될 수 있다.
박싱(boxing)과 언박싱(unboxing)
객체지향 언어에서는, 일반적으로 객체를 가지고 작업한다. 하지만 스피드 문제 때문에 기초 타입이 공급된다. 따라서 객체와 값을 모두 다루게 될 것이며, 두 가지가 잘 조화되도록 해야 한다.
C#과 닷넷 실행 환경에서는 이러한 "커뮤니케이션" 문제를 박싱(boxing, 포장하기)과 언박싱(unboxing, 포장풀기)을 사용하여 해결할 수 있다. 박싱이란 값 타입이 레퍼런스 타입으로 보이도록 하는 과정이며, 값 타입(기초 타입)이 객체를 요구하거나 사용할 수 있는 위치에서 사용될 때 자동으로 일어난다.
값 타입(value-type의 값을 박싱한다는 것은 객체 인스턴스를 할당하고,
값 타입의 값을 그 인스턴스에 복사하는 것이다.
언박싱은 박싱과 반대라고 생각하면 된다. 언박싱은 레퍼런스 타입을 값 타입으로 전환한다. 그리고 객체 인스턴스가 주어진
값 타입을 박싱한 값인지 체크한 다음, 그 값을 인스턴스로부터 복사한다. 하지만 자바에서는 이와는 달리, 클래스 래퍼(wrapper)를 각 기초 타입에 제공한다. 즉,
int에는
Integer 클래스를,
byte에는
Byte 클래스를 제공한다.
요약
이 글에서는 C#과 자바를 비교해 보았다. 두 가지는 상당히 비슷하지만, C#이 자바의 복제품이라고 말할 수는 없을 것이다. 객체지향 또는 매개 언어라는 개념은 새롭지 않다. 관리되고 안전한 환경에서 운영되어야 하는 새로운 객체지향 언어를 설계한다면, C#과 유사하지 않겠는가?
부디 쿠니아완은 오스트레일리아 시드니에서 프리랜서로 활동 중인 컨설턴트이다.