라이브러리
프로그래밍언어, 각종코드, 관련동향, 논문 등의 저장소
닷넷 프레임워크 2.0과 개발 언어의 변화

닷넷 프레임워크 2.0과 개발 언어의 변화

개발 환경의 새로운 변화, 비주얼 스튜디오 2005 분석

 

연재순서
1회.닷넷 개발의 새로운 변화, 비주얼 스튜디오 2005 맛보기
2회.닷넷 프레임워크 2.0과 개발 언어의 변화
3회. 웹 애플리케이션 개발의 진화, ASP.NET 2.0

 

2000년 봄 NGWS(Next Generation Windows Services)라는 이름으로 소개됐던 MS의 차세대 개발 환경이 오랜 베타 기간의 높은 관심을 뒤로 하고, 2002년 닷넷 프레임워크 1.0·비주얼 스튜디오 닷넷 2002와 함께 세상에 나타났다.

그 후 마이너 업그레이드인 닷넷 프레임워크 1.1과 비주얼 스튜디오 2003으로 안정화됐고, 이제는 2005년 출시를 위해 현재 닷넷 프레임워크 2.0과 비주얼 스튜디오 2005 베타1이 출시됐다. 베타 시절의 열기가 실제 출시 시점의 IT 경기 급랭으로 다소 부진하지 않느냐는 의견이 있었지만 기업 환경을 중심으로 2년간 성장세를 올리고 있다. 비록 북미 지역으로 제한되어 있지만 2003년말을 기준으로 닷넷 개발자가 자바 개발자를 넘어서고 있다(<그림 1>).

국내에서는 아직 닷넷의 점유율이 자바에 비해 낮지만 빠른 시간에 바뀔 것으로 예상되고 있다. 이제 본격적으로 MS의 개발 환경의 변화에 대해 알아보겠다.

사용자 삽입 이미지
<그림 1> 닷넷 플랫폼 채택률(2002.2/4분기~2003년 2/분기)


닷넷 프레임워크 2.0의 변화
최근 들어 많은 개발자들이 MS의 개발 환경은 닷넷 프레임워크와 비주얼 스튜디오 닷넷 개발 툴만으로는 2% 부족하다는 것을 이야기하고 있다. 이 2%에 어떤 것이 포함되어 있기 때문에 닷넷 프레임워크와 비주얼 스튜디오만으로는 힘들다는 것인지 한 번 살펴보겠다.

닷넷은 단순한 개발 프레임워크라기보다는 빈틈없는 컴퓨팅 환경을 구축하기 위한 웹 서비스 인프라 시스템이다. 윈도우와 엔터프라이즈 서버를 통해 운영 인프라 구조를 제공하고, 기존 웹 서비스인 ASMX와 WSE(Web Services Enhancements), 롱혼의 인디고(Indigo)를 위해 연결된 시스템 환경이 된다. 그리고 서비스, 아키텍처 가이던스(Patterns & Practices,
msdn.microsoft.com/architecture/patterns/default.aspx), 디바이스, 비즈니스 응용, 정보 근로자 도구와 인프라를 종합적으로 지원하고 있다.

이렇게 복잡한 환경을 지원하니 개발자들이 닷넷 개발 환경에 대해 혼란을 느끼게 되고 닷넷이 도대체 무엇인지 잘 모르겠다는 이야기를 많이 하게 된다.

혼란(?)을 줄이기 위해 우선 닷넷 프레임워크에 대해서만 살펴보자. 닷넷 프레임워크는 CLS(Common Language Specification)를 지원하는 언어(C#, VB.NET, J#, 매니지드 C++ 등)로 만들어진 IL(Intermediate Language)로 작성된 코드를 실행하는 환경이다.

닷넷 프레임워크 2.0은 크게 CLR 2.0, 윈도우 폼(Windows Forms) 2.0, ASP.NET 2.0, 닷넷 컴팩트 프레임워크 2.0, ADO.NET 2.0으로 구분할 수 있다. CLR 2.0을 제외한 다른 내용은 BCL에 포함되는 라이브러리 성격이 강하므로 여기서는 CLR 2.0을 중심으로 살펴보겠다. 닷넷 2.0의 큰 변화를 살펴보면 다음 세 가지 영역으로 구분할 수 있다.

◆ 플랫폼 확장 : SQL 서버와 통합, 64비트 프레임워크 지원

◆ 플랫폼 개선 : 성능 향상, RAD 디버깅, CLR 보안 강화

◆ 플랫폼 혁신 : 제너릭(Generics) 지원, BCL 강화


먼저 SQL 서버와의 통합으로 가질 수 있는 장점은 다음과 같다.

◆ 닷넷 프레임워크의 개발 모델을 데이터베이스 층에도 바로 적용할 수 있다.

◆ 비즈니스 로직을 개발 층으로 쉽게 마이그레이션할 수 있다.

◆ 데이터베이스 확장을 안전하고 쉽게 진행할 수 있다.


결론적으로 저장 프로시저와 트리거(Triggers), 데이터 타입의 정의를 관리되는 코드(managed code)로 할 수 있으므로 훨씬 개발 지향적인 데이터베이스 환경을 제공해준다.

사용자 삽입 이미지
<그림 2> SQL 내의 CLR 기능


64비트 프레임워크 지원으로 가질 수 있는 장점으로는 x86-64와 IA-64 아키텍처·관리되는 실행 환경·WoW64 지원을 들 수 있다. 현재 64비트 프레임워크 역시 베타1이 나와 있는 상태이고 비주얼 스튜디오 2005를 설치할 때 인텔과 AMD CPU를 선택해 설치할 수 있다. WoW64 지원으로 64비트 환경에서 32비트 환경 구동이 가능하며 개발, 배포, 디버깅 또한 64비트와 32비트를 선택할 수 있다.

성능 역시 크게 향상됐다. 기존 CLR 1.1에 비해 CLR 2.0은 애플리케이션 구동 시점의 IL 코드 병합과 구동에서 좋은 성능을 보여주며, 한 번 JIT(Just In Time) 컴파일된 코드를 디스크에 저장한 후 사용함으로써 성능이 향상됐다. 그리고 APPDomain Remoting에서 수십 배 이상, 그리고 델리게이트(delegate) 처리에서 두 배의 성능을 보여준다. UTF-8 인코딩은 여덟 배 정도의 차이를 보이고 있다.

또한 디버깅 모드로 실행시 코드 편집(클래스 내의 필드와 메쏘드)과 계속 기능을 통해 디버깅 환경이 더욱 개선됐으며, CLR 보안 역시 강화됐다. PKCS7과 XML 암호화 기능이 추가됐으며 X509 인증이 추가됐다. 그 외에도 권한 허가 계산기 기능이 ClickOnce와 통합되어 사용할 수 있다.

이와 같은 변화가 있지만 다음에 소개할 제너릭 기능이 가장 큰 변화이며 기능 또한 강력하다 할 수 있겠다. 제너릭 기능에 대해서는 좀 더 자세히 살펴보겠다.

제너릭
2002년에 열린 OOPSLA(Object-Oriented Programming, Systems, Language, and Applications)에서 C#의 아버지인 Anders Hejlsberg가 다음 버전의 C#에 추가될 새로운 특징들로 제너릭, 익명 메쏘드(anonymous method), 이터레이터(iterator), Partial Types에 대해 이야기했고 이러한 기능들이 C# 2.0(Generic C#)에서 구현됐다(닷넷 프레임워크 1.×에서도 제너릭은 이용할 수 있으며 이는 CLIX라는 이름으로 배포되고 있다). 잠시 C#의 디자인 목표에 대해 알아보자. C#의 대부분의 기능은 네 가지 디자인 목표를 기반으로 만들어졌다.

◆ 언어에서 값 및 참조 형식을 사용하는 방식을 간소화하며 통합된 형식의 시스템

◆ XML 주석, 특성, 속성, 이벤트 및 델리게이트와 같은 기능을 통해 구축된 구성 요소 기반의 디자인

◆ 안전한 포인터 조작, 오버플로 검사 등과 같은 C# 언어의 고유한 기능을 통해 구축

◆ 개발자의 생산성을 향상시키는 실용적인 언어 구문(예 : foreach, using 명령문) 사용

 

그럼 이제 이 네 가지 요소에 대해 제너릭을 중심으로 알아보겠다.

사용자 삽입 이미지
<그림 3> 2004년 샌 디에고(San Diego)에서 있었던 테크애드 2004(TechEd 2004)에서 C#의 아버지인 Anders Hejlsberg를 만나다.


재사용성 높인 제너릭
애플리케이션의 복잡도가 심해질수록 개발자에게는 기존 객체 기반 코드를 최대한 다시 사용해야 할 방법이 중요해진다. C# 2.0에서는 제너릭이라는 기능을 사용한다. C#에는 형식이 안전한 제너릭이 포함되어 있다. 이 제너릭은 C++의 템플릿과 유사하지만 구문이 약간 다르며 구현 방식도 크게 다르다.

C# 1.0에서의 클래스 작성
C# 1.0에서는 개발자가 기본 개체 형식의 인스턴스에 데이터를 저장해 미약한 기능의 제너릭 형식을 만들 수 있다. C#의 모든 개체는 기본 개체 형식에서 상속되고 통합된 닷넷 형식 시스템의 박싱(boxing : 자동 변환) 및 언박싱(unboxing) 기능이 있으므로 프로그래머는 참조 및 값 형식 모두를 개체 형식의 변수에 저장할 수 있다.

하지만 참조 형식, 값 형식 및 기본 개체 형식 사이를 변환하는 데 심각한 성능 저하가 발생하게 된다. 이러한 기능을 설명하기 위해 예를 하나 들어 보겠다. 다음 예는 마이크로소프트 연구소의 공식 Generics for C# 팀의 홈페이지인 Gyro(
research.microsoft.com/projects/clrgen)에 있는 Andrew Kennedy와 Don Syme이 작성한 「Design and Implementation of Generics for the .NET Common Language Runtime」(research.microsoft.com/projects/clrgen/generics.pdf)의 예를 사용했다(<리스트 1, 2>).

 <리스트 1> 객체 기반의 스택 예
사용자 삽입 이미지

class Stack {
   private object[] store;
   private int size;
   public Stack()
      store=new object[10]; size=0;
   }
   public void Push(object x) {
      if (size>=store.Size) {
         object[] tmp = new object[size*2];
         Array.Copy(store,tmp,size);
         store = tmp;
      }
      store[size++] = x;
   }
   public object Pop() {
      return store[--size];
   }
   public static void Main() {
      Stack x = new Stack();
      x.Push(17);
      Console.WriteLine((int) x.Pop() == 17);
   }
}
사용자 삽입 이미지


 <리스트 2> 제너릭 스택 예
사용자 삽입 이미지

class Stack<T> {
   private T[] store;
   private int size;
   public Stack()
      store=new T[10]; size=0;
   }
   public void Push(T x) {
      if (size>=store.Size) {
         T[] tmp = new T[size*2];
         Array.Copy(store,tmp,size);
         store = tmp;
      }
      store[size++] = x;
   }
   public T Pop() {
      return store[--size];
   }
   public static void Main() {
      Stack<int> x = new Stack<int>();
      x.Push(17);
      Console.WriteLine(x.Pop() == 17);
   }
}
사용자 삽입 이미지

앞의 코드에서는 Push 및 Pop의 두 가지 작업을 수행하는 간단한 스택 형식을 만든다. Stack 클래스는 개체 형식의 배열에 해당 데이터를 저장하고 Push 및 Pop 메쏘드는 기본 개체 형식을 사용하여 각각 데이터를 받고 반환하게 된다. 사용자 지정 형식을 스택에 푸시할 수 있다.

하지만 프로그램에서 데이터를 검색해야 하는 경우 기본 개체 형식인 Pop 메쏘드의 결과를 명시적으로 캐스팅해야 한다. 정수 등과 같은 값 형식이 Push 메쏘드에 전달되면 런타임은 이 형식을 참조 형식으로 자동 변환(boxing)한 다음 내부 데이터 구조에 저장한다.

정수 등과 같은 값 형식을 스택에서 검색하려면 Pop 메쏘드에서 얻은 객체 형식을 값 형식에 명시적으로 캐스팅(unboxing)해야 한다. 값 및 참조 형식 사이의 박싱 및 언박싱은 특히 부담되는 작업이 될 것이다.

C#의 제너릭은 내부 알고리즘이 동일하게 유지되고 내부 데이터 형식이 최종 사용자 설정에 따라 다를 수 있도록 매개 변수 있는 형식을 통해 구현한다. 만약 C++의 템플릿 기능을 사용해 본 독자라면 C++와 거의 동일한 방식으로 C#의 제너릭이 선언되므로 쉽게 이해할 수 있을 것이다. 일반적으로 기존 클래스 및 구조와 똑같이 만들 수 있으며 꺾쇠 괄호 표기법(< 및 >)을 사용하여 형식 매개 변수를 지정할 수 있다. 그렇다면 실제 위의 두 코드가 IL 코드로는 어떻게 다른지 한번 살펴보겠다(<리스트 3, 4>).

 <리스트 3> 객체 기반의 스택 IL 코드
사용자 삽입 이미지


.class Stack {
   .field private class System.Object[] store
   .field private int32 size
   .method public void .ctor() {
      ldarg.0
      call void System.Object::.ctor()
      ldarg.0
      ldc.i4 10
      newarr System.Object
      stfld class System.Object[] Stack::store
      ldarg.0
      ldc.i4 0
      stfld int32 Stack::size
      ret
   }
   .method public void Push(class System.Object x) {
      .maxstack 4
      .locals (class System.Object[], int32)
      .
      .
      .
      ldarg.0
      ldfld class System.Object[] Stack::store
      ldarg.0
      dup
      ldfld int32 Stack::size
      dup
      stloc.1
      ldc.i4 1
      add
      stfld int32 Stack::size
      ldloc.1
      ldarg.1
      stelem.ref
      ret
   }
   .method public class System.Object Pop() {
      .maxstack 4
      ldarg.0
      ldfld class System.Object[] Stack::store
      ldarg.0
      dup
      ldfld int32 Stack::size
      ldc.i4 1
      sub 

      dup
      stfld int32 Stack::size
      ldelem.ref
      ret
   }
   .method public static void Main() {
      .entrypoint
      .maxstack 3
      .locals (class Stack)
      newobj void Stack::.ctor()
      stloc.0
      ldloc.0
      ldc.i4 17
      box System.Int32
      call instance void Stack::Push(class System.Object)
      ldloc.0
      call instance class System.Object Stack::Pop()
      unbox System.Int32
      ldind.i4
      ldc.i4 17
      ceq
      call void System.Console::WriteLine(bool)
      ret
   }
}

사용자 삽입 이미지


 <리스트 4> 제너릭 스택 IL 코드
사용자 삽입 이미지

.class Stack<T> {
   .field private !0[] store
   .field private int32 size
   .method public void .ctor() {
      ldarg.0
      call void System.Object::.ctor()
      ldarg.0
      ldc.i4 10
      newarr !0
      stfld !0[] Stack<!0>::store
      ldarg.0
      ldc.i4 0
      stfld int32 Stack<!0>::size
      ret
   }
   .method public void Push(!0 x) {
      .maxstack 4
      .locals (!0[], int32)
      .
      .
      .
      ldarg.0
      ldfld !0[] Stack<!0>::store
      ldarg.0
      dup
      ldfld int32 Stack<!0>::size
      dup
      stloc.1
      ldc.i4 1
      add
      stfld int32 Stack<!0>::size
      ldloc.1
      ldarg.1
      stelem.any !0
      ret
   }
   .method public !0 Pop() {
      .maxstack 4
      ldarg.0
      ldfld !0[] Stack<!0>::store
      ldarg.0
      dup
      ldfld int32 Stack<!0>::size
      ldc.i4 1
      sub
      dup
      stfld int32 Stack<!0>::size
      ldelem.any !0
      ret
   }
   .method public static void Main() {
      .entrypoint
      .maxstack 3
      .locals (class Stack<int32>)
      newobj void Stack<int32>::.ctor()
      stloc.0
      ldloc.0
      ldc.i4 17
      call instance void Stack<int32>::Push(!0)
      ldloc.0
      call instance !0 Stack<int32>::Pop()
      ldc.i4 17
      ceq
      call void System.Console::WriteLine(bool)
      ret
   }
}
사용자 삽입 이미지

IL 코드 내에서 명시적으로 형식을 사용해 줌으로써 필요 없는 박싱과 언박싱을 줄여 주고 있는 것이 확연히 구분될 것이다. Generic 클래스가 컴파일되면 이 클래스는 일반 클래스와 실제로 차이가 없다. 결과는 메타 데이터 및 중간 언어(IL)일 뿐이다. 제너릭 형식의 IL은 제공된 형식 매개 변수가 값 형식 또는 참조 형식인지에 따라 다르다.

이외에도 제너릭 형식은 다양한 매개 변수를 지원하며 애플리케이션에서 형식 매개 변수의 멤버를 사용하여 프로그램의 제너릭 형식 내에서 명령문을 실행하도록 제약 조건을 주는 것도 가능하다. 그럼 C#의 제너릭과 다른 언어의 차이를 한번 알아보겠다.

C++ 템플릿은 C# 제너릭과는 표현 형식은 비슷하지만 실제는 현저히 다르다. C# 제너릭은 IL로 컴파일되므로 각각의 값 형식에 대해서는 런타임에, 참조 형식에 대해서는 한 번만 발생하게 된다. C++ 템플릿은 템플릿에 제공된 각 형식 매개 변수에 대한 특수화된 형식을 생성하는 코드 확장 매크로다. 따라서 C++ 컴파일러는 정수의 스택과 같은 템플릿을 발견하면 기본 형식으로서 정수를 내부적으로 포함하는 Stack 클래스로 템플릿 코드를 확장한다.

형식 매개 변수가 값 또는 참조 형식에 관계없이 코드 양을 감소시킬 수 있도록 링커를 디자인하지 않으면 C++ 컴파일러는 매번 특수화된 클래스를 만든다. 따라서 C# 제너릭에 비해 코드 양이 상당히 증가한다. 그리고 C++ 템플릿에서는 제약 조건을 정의할 수 없다. 그렇지만 C++ 템플릿은 그 자유로움이나 다양함에서 고급 C++ 개발자들의 총애를 받고 있다. C# 제너릭은 위에서 이야기한 C#의 디자인 목표를 충실히 이행하면서 C++의 템플릿보다 통합되고 실용적인 모습으로 구현됐다.

자바의 경우는 JDK 5.0에서 제너릭 기능을 추가하기로 했지만 성능 문제를 어떻게 극복할 것인지가 관건이 될 것 같다. 그리고 조금 늦은 감이 있지만 JDK 5.0에는 C#에서 볼 수 있는 박싱과 같은 기능이 추가로 들어가며, ADO.NET의 DataSet과 같은 비연결지향적인 DataSet 또한 추가한다고 한다.

닷넷 2.0에서는 C# 이외도 VB.NET과 매니지드 C++에서도 제너릭 기능을 지원하고 있다. 그 외의 제너릭의 장점으로는 다음과 같은 요소들을 들 수 있다.

◆ 코드 작성, 테스트 및 배포를 한 번에 수행할 수 있으므로 다양한 데이터 형식에 해당 코드를 재사용할 수 있다.

◆ 제너릭은 컴파일시 검사된다. 프로그램이 제공된 형식 매개 변수로 generic 클래스를 인스턴스화하는 경우 형식 매개 변수는 프로그램에서 클래스 정의에 지정한 형식으로만 사용할 수 있다.

◆ 제너릭 구현은 다른 형식의 구현과 비교해 보면 코드 양을 감소시킨다. 제너릭으로 형식화된 컬렉션을 만들면 각 클래스의 특정 버전을 만들 필요가 없으며 성능을 그대로 유지할 수 있다.

◆ 코드를 더 쉽게 읽을 수 있다.


이외에도 BCL(Base Class Library)에서의 Generic 컬렉션에 대해 알아보도록 하겠다. BCL에서는 다음과 같이 이미 구현된 클래스와 인터페이스를 제공하고 있다.

◆ System.Collections.Generic 클래스 : List<T>, Dictionary<K, V>, Stack<T>, Queue<T>

◆ System.Collections.Generic 인터페이스 : IList<T>, IDictionary<K, V>, ICollection<T>, IEnumerable<T>, IEnumerator<T>, IComparable<T>, IComparer<T>


이터레이터
이터레이터는 해당 요소에서 foreach 문이 반복되는 방식을 형식이 간단하게 선언할 수 있게 만들어 주는 것으로 이터레이터는 foreach 루프 구문의 논리적 구문으로 foreach 키워드 다음에 여는 괄호 및 닫는 괄호를 사용하여 함수와 유사하게 정의된다.

이터레이터는 프로그램 대신에 열거자 패턴을 구현하여 번거로운 작업을 처리한다. 클래스를 만들고 상태 시스템을 작성하는 것이 아니라 C# 컴파일러는 열거자 패턴을 사용하여 이터레이터에 작성한 코드를 적절한 클래스 및 코드로 변환한다. 이렇게 함으로써 이터레이터는 개발자의 생산성을 상당히 높여준다.

익명 메쏘드
익명 메쏘드는 유용한 언어 구문으로 프로그래머는 델리게이트에 캡슐화하고 나중에 수행할 수 있는 코드 블록을 만들 수 있다. 이 메쏘드는 lambda라는 함수형 언어의 개념을 기반으로 하며 LISP 및 파이썬과 개념적으로 비슷하다.

델리게이트는 메쏘드를 참조하는 객체로 C에서 이야기하는 함수 포인터다. 델리게이트가 실행될 때마다 이 델리게이트가 참조하는 메쏘드가 호출된다. 별도의 함수가 만들어지고 델리게이트에 의해 참조되고 델리게이트가 호출될 때마다 프로그램이 해당 함수를 호출한다. 함수 내에서 일련의 실행 단계가 수행된다.

익명 메쏘드를 추가하면 프로그램은 클래스에 대해 완전히 새로운 메쏘드를 만들 수 있으며 델리게이트에서 그 안에 포함된 실행 단계를 직접 참조할 수 있다. 익명 메쏘드는 실행 영역을 나타내는 중괄호 쌍이 있는 인스턴스화 문과 명령문 다음에 델리게이트를 인스턴스화하여 선언한다.

사용자 삽입 이미지
데이터 중심적 분산 프로그래밍을 위한 언어 Cω
사용자 삽입 이미지
 
사용자 삽입 이미지
사용자 삽입 이미지
Cω(코메가, COmega,
research.microsoft.com/Comega)는 실험실 언어이지만 현재 컴파일러를 구해 사용할 수 있다. Cω는 근래의 대표적 프로그래밍 분야인 데이터 중심적인 분산 프로그램을 위한 데이터 구조와 제어 구조를 제공하기 위해 설계됐으며, 동기적 혹은 비동기적 메쏘드를 지원하며, 언어에 데이터 지향적인 기능이 포함되어 있다. Cω가 C#이나 닷넷의 언어에 어떤 형태로 반영이 될지는 현재로서는 미지수이며, 앞으로 지켜봐야 할 것 같다.
사용자 삽입 이미지
사용자 삽입 이미지


익명 메쏘드는 클래스에서 선언한 변수뿐만 아니라 클래스가 상주하는 메쏘드에 선언된 지역 변수 또는 매개 변수를 참조할 수 있다. 또한 익명 메쏘드 문은 sender 및 e라는 매개 변수 두 개를 포함한다. 윈도우 폼의 Button 컨트롤 클래스의 Click 델리게이트의 정의를 찾아보면 델리게이트가 참조하는 모든 함수는 첫 번째는 형식 객체, 두 번째는 EventArgs 형식인 매개 변수 두 개를 포함해야 한다는 것을 알 수 있다.

익명 델리게이트가 발견되면 C# 컴파일러는 해당 실행 범위의 코드를 고유한 이름의 클래스 내에서 고유한 이름의 함수로 자동 변환한다. 코드 블록에 있는 델리게이트가 저장된 다음 참조(컴파일러가 생성한 개체 및 메쏘드)로 설정된다. 델리게이트가 호출되면 익명 메쏘드 블록은 컴파일러가 생성한 메쏘드를 통해 실행하게 된다.

Partial Types
Partial Types를 사용하면 대량의 소스 코드를 여러 개의 다른 소스 파일로 나눌 수 있다. 또한 시스템이 생성한 형식과 사용자가 작성한 형식을 분리할 수 있으므로 도구에서 생성한 코드를 간단하게 보충하거나 수정할 수 있다.

다음에 나타날 새로운 기술은 어떤 것일까

지금까지 간략하게나마 닷넷 2.0의 새로운 기능과 닷넷 2.0에서의 언어의 변화에 대해 살펴봤다. 언어나 개발 환경 또한 유기체와 같아서 지속적으로 변화하며 모방을 통해 새롭게 진화해 나간다는 것을 볼 수 있다. 결국 이러한 변화에 대응하지 못하게 되면 없어지지는 않지만 대중과 멀어지는 과거의 기술로 전락할 수밖에 없는 것이다. 지금 예측하기에는 무리겠지만 그럼 그 다음 변화는 어떻게 올 것인지 한 번 생각해 보는 것도 재미있을 것이다. @

* 이 기사는 ZDNet Korea의 제휴매체인마이크로소프트웨어에 게재된 내용입니다.

 

강성재 (한국MS)

2005/01/24

 

사용자 삽입 이미지

 

원문 :http://www.zdnet.co.kr/techupdate/lecture/dotnet/0,39024986,39133022,00.htm

  Comments,     Trackbacks