C# 2.0의 구문 살펴보기 Part 3: Generic 클래스

C# 2.0은 지금 소개하는 Generic이라는 기술을 통하여 Object 형식을 다루는 방법을 새롭게 제공합니다. 더 이상 복잡한 조건문을 메서드 안에 배열할 필요가 없으며 이 개념을 통하여 새로운 디자인 패턴을 만들 수 있게 되었습니다. 이 글을 작성하는 현 시점에서는 아직까지 Generic의 정확한 번역 용어가 채택되지 않았으므로 원래의 뜻을 유지하기 위하여 Generic으로 표기하고 있으므로 양해 바랍니다.

1. Generic 클래스의 문법 구조
Generic 클래스를 선언하는 방법은 기존의 클래스와 유사합니다. 하지만 기존의 클래스 선언에는 없는 몇 가지 추가 구문이 명시되어야만 합니다. C# 구문을 기준으로 설명하도록 하지요.

Visual Studio 개발자 여러분들을 위한 안내: Visual Basic .NET, J#, Visual C++, JScript .NET 및 기타 언어를 사용중이신 프로그래머분들께서는 MSDN Library 베타를 참조하십시오. J#에서 이 Generic을 사용할 경우 기존의 Java, J++, J# 1.x 문법과 호환되지 않는다는 점을 꼭 염두에 두시기 바랍니다. Visual C++ 8.x 버전의 컴파일러에서 Managed Extensions for C++의 키워드가 새롭게 바뀌었습니다. 그에 따라, Managed Extensions for C++ 1.x 버전으로 작성된 소스 코드에 대한 업데이트 또는 유지 보수가 필요할 것으로 예상됩니다. 그리고 이번 업데이트를 기준으로 하여 C++의 Template과도 일정 부분은 연동이 가능할 것으로 예상됩니다. (일부 형식 제한 지정과 같은 문법은 표준에 어긋나므로 주의하십시오.)


[public | private | internal | …] [sealed | abstract] class [Class Name]
      <[Argument Type I], [Argument Type II], …, [ArgumentType n]> : [Parent Class], [Interface I], …
{
  // Class Body
}

클래스의 이름과 상속 관계를 지정하는 영역 사이에 부등식 기호 ‘<‘, ‘>’ 안에 사용자 정의 형식을 지정합니다. 이 사용자 정의 형식은 임의로 이름을 붙일 수 있으나 클래스 이름의 명명법을 그대로 따르므로 클래스 이름의 명명법에 어긋나는 형태의 이름은 사용할 수 없습니다.

Generic 인수에는 단순히 클래스 형식을 지정할 수도 있지만, 구조체 형식, 인터페이스 형식, 대리자 형식, 중첩 Generic 형식의 지정도 가능합니다. 다시 말해, 사용하기에 따라서는 이 Generic 인수 자체가 복잡한 인수 트리 구조를 생성할 수도 있다는 뜻입니다. 하지만 한 세대 이상 파고드는 예는 그리 많지 않으므로 어렵게 생각하실 필요는 없습니다.

이 강좌를 처음 접하시는 프로그래머 분들 중에는 비교적 근래에 C++에서 도입된 개념인 Template을 떠올리신 분들도 계시리라 생각됩니다. 예상하신 그대로, Generic은 C++의 Template를 C#에 맞게 첨삭한 클론입니다. 하지만 Template에는 없거나 다소 불편한 개념을 수정한 것이 .NET 플랫폼의 Generic의 특징이라고 할 수 있습니다.

2. Generic 클래스에서의 사용자 정의 형식 활용 방법
Generic 클래스 안에서 사용자 정의 형식을 활용하시는 방법은 단순합니다. 사용자 정의 형식은 사실 System.Object 형식을 상속한 것과 다를 바가 없습니다. 그리고 Generic 클래스 인수 안에서 명시된 적이 있는 사용자 정의 형식은 중첩된 서브 클래스에서도 유효합니다.

3. Generic 클래스의 인스턴스 만들기
다음의 예제 코드를 살펴보도록 하지요.


using System;

namespace Test2
{
  public class Generic1 <FirstType> : System.MarshalByRefObject
  {
       private FirstType m_firstType;

      public Generic1(FirstType firstType)
       {
           this.m_firstType = firstType;
       }

      public FirstType FirstTypeInstance
       {
           get
           {
               return this.m_firstType;
           }
       }

      public override int GetHashCode()
       {
           return this.m_firstType.GetHashCode();
       }

      public override string ToString()
       {
           return this.m_firstType.ToString();
       }
  }

  public sealed class MainObject
  {
       [MTAThread()]
       public static void Main(string[] arguments)
       {
           Generic1<string> s = new Generic1<string> (“Test”);
           Console.WriteLine(“{0}”, s);
           Console.ReadLine();
       }
  }
}

Generic이 지정된 클래스는 형식 선언을 할 때 부터 인수를 지정되는 모습을 볼 수 있습니다. Generic1<string> 이라는 형식의 의미는 string (System.String) 형식만을 받아들이는 Generic1 클래스의 단편을 뜻합니다. 그리고 Generic1<string> 이라는 선언 자체는 하나의 형식 키워드가 되기 때문에 생성자를 호출할 때에도 다시 한번 Generic1<string> 이라는 이름을 사용하게 됩니다.

4. Generic 클래스의 상속
Generic 클래스의 상속에 관하여 새롭게 소개되는 용어가 하나 있습니다. 의미는 우리가 익히 알고 있으나 용어 자체는 새로울 것입니다. 기존의 클래스를 C# 2.0에서는 Concrete 클래스라고 새롭게 명명하였는데, 상속 관계를 설명할 때에는 반드시 알아두셔야 하는 개념입니다.

Generic 클래스의 특성을 유지하면서 상속을 할 수 있을 때에는 자식 클래스도 Generic 클래스로 인정됩니다. 하지만 Generic 클래스의 특성을 유지할 수 없는 상속을 하여 Generic 클래스의 특성을 소멸시킨 상속을 한 자식 클래스는 Concrete 클래스로 취급됩니다. 봉인된 Concrete 클래스가 아닐 경우 얼마든지 새로운 상속 관계를 형성할 수는 있지만 부모에 관한 모든 Generic 정보에 대한 접근 권한은 잃어버립니다.

4.1. Generic 클래스의 인수를 줄이는 상속
다음의 구문을 살펴보도록 합시다.


public class Parent1 <A, B, C> { … }
public class Child1 <A, B> : Parent1 <A, B, int> { … }

어떻습니까? Child1 클래스에서는 A와 B 형식만을 받아들이도록 되어있지만 Parent1 클래스의 형식 인자 목록을 충족하도록 선언하였습니다. 이것이 Generic 클래스의 인수를 줄이는 상속의 한 예입니다. 여기서는 특정 위치에 있는 하나의 인수를 제거하였지만, 원하는 위치에 원하는 수 만큼의 인수를 제거할 수 있습니다.

4.2. Generic 클래스의 인수를 늘이는 상속
다음의 구문을 살펴보도록 합시다.


public class Parent2 <A, B, C> { … }
public class Child2 <A, B, C, D> : Parent2 <A, B, D> { /* 인수 C를 활용하는 코드 */ }

4.1 섹션과 마찬가지로 Parent2의 인수 목록을 Child2가 모두 만족하는 상속을 하였습니다. 남아있는 C 형식에 관한 활용에만 집중하면 Child2 클래스의 작업은 끝이 나는 것입니다.

4.3. Generic 클래스의 인수를 유지하는 상속
다음의 구문을 살펴보도록 합시다.


public class Parent3 <A, B, C> { … }
public class Child3 <AChild, BChild, CChild> : Parent3 <AChild, BChild, CChild> { … }

인수의 이름이 달라지더라도 이상이 없음을 보여주는 예제였습니다.

4.4. Generic 클래스의 관계를 끊는 Concrete 클래스
다음의 구문을 살펴보도록 합시다.


public class Parent4 <A, B, C> { … }
public class Child4 : Parent4 <short, int, long> { … }

Child4는 클래스 자체에 대한 상속은 제공하고 있지만 Generic에 관한 특성을 완전히 소멸시킨 것입니다. Child4 클래스가 만약 봉인된 클래스라면 Generic에 관한 측면은 물론 클래스 자체에 대한 상속마저도 봉인한 고립된 클래스가 됩니다.

4.5. Concrete 클래스로부터 새로운 Generic 관계를 생성하기
다음의 구문을 살펴보도록 합시다.


public class Parent5 <A, B, C> : System.MarshalByRefObject { … }

System.MarshalByRefObject 클래스 자체는 봉인 클래스가 아니기 때문에 새로운 Generic 관계의 생성을 허용한 것이 되었습니다. 만약 System.MarshalByRefObject 클래스가 봉인 클래스였다면 위의 구문은 오류를 유발할 것입니다.

오늘 강좌는 Generic 클래스에 대한 것들을 살펴보았습니다. 다음회 강좌에서는 Generic 인터페이스에 대해서 살펴보도록 하겠습니다. 미리 말씀드리는 부분이지만 Generic 인터페이스의 상속도 섹션 4의 내용을 그대로 적용하므로 이 부분에 관한 충분한 실습을 통하여 감각을 익혀두시기를 권장합니다.

좋은 하루 되십시오. ^^

댓글 남기기