Welcome to MSDN Blogs Sign in | Join | Help

C# 3.0 의 새로운 기능들!!!

이미 모두들 알꺼 같지만, 이번 새로운 C# 3.0의 요는 Linq라는 거다. Linq에 대해 자세히 알고 싶음 search engine 가서 Linq라고 쓰면 아주 많은 정보를 찾을수 있을꺼다.

하여간, 이 Linq라는 놈을 지원 하기 위해서 (* 개인적으로 만약 database programming 이든 어떤 형태로든 data를 다루는 programming을 해 봤다면, 이 Linq라는 놈이 정말 정말 사람의 인생을 얼마나 편하게 해줄수 있는지 알수 있을꺼다 ), C# 3.0 에는 여러가지 새로운 기능이 들어 갔다.

그 기능들을 나열하자면

1. implicitly typed local variables

요놈이 무엇이냐면
var v = new List<int>(); 
처럼 쓰면 compiler가 알아서 compile time에 v의 타입을 정해서 지정해 주는거다. 일련의 다른 dynamic language처럼 run time에 타입이 결정 되는게 아니고, compile time에 지정 되는거기 때문에 한번 지정 되면 타입을 도중에 바꿀수 없다 물론 같은 이유로 var 로 선언 되는 변수는 항상 initialize가 선언과 동시에 되어야만한다. 그리고 또 local scope (method body)안에서만 사용 가능하다.

그럼 이게 Linq하고 왜 상관 있는가?
가령, 사용자가 table 안에 들어 있는 3개의 column중 2개의 column만 가지고 오고 싶다 할때
var v = from c in customers select new {c.Name, c.Ages};
라고 쓰면 컴파일러가 알아서 새로운 타입 (anonymous type)을 만들고 그 member로 Name과 Age를 선언한 다음에 이 새로운 type을 가지는 IEnumerate 타입을 만들고 이 type을 var에 주게 된다. 데이타 베이스 프로그램밍을 해 본 사람이면 이게 왜 필요한지 금방 알것이다.
hint는 select name, age from customers 다 ^^

2. Extension methods

두번째 새로 추가된 기능은 extension method다. 이게 뭐냐면 만약 내가
class A { } 과 같은 타입이 있고, 만약 내가 그 소스를 건드릴수 없다면, 혹은 특정 메소드를 한곳에 모아 두고 싶다면
static class ExtensionsForA {
   static void ExtensionMethodForA(this A, .... );
}
처럼, extension methods를 정의 한후,
A a;
a.ExtensionMethodForA(...);
처럼 extension method를 A 타입의 instance method 처럼 부를수 있게 하는거다.

그럼 이게 또 Linq랑 무슨 상관이 있는가?

Linq는 기본적으로 IEnumerable이나 IQueriable를 지원 하는 모든 타입에 대해 query를 지원한다.

이를 테면 List<Customer> customers = new List<Customer>(); 가 있다면
var v = customers.select(c=> new { c.Name, c.Age}); 라고 쓸수 있다는거다. 여기서 select 함수는 바로 Linq에서 지원하는 extension method다. 따라서 C# 3.0 사용자는 Linq extension method를 import 하는 순간, 사용자 collectoin type중 IEnumerable를 implement 하는 모든 타입에 대해 이 Where, Select, Join과 같은 모든 Linq 함수를 쓸수 있게 되는거다.

물론 이것도 compile time에 컴파일러가
Sequences.Select(customers, c=> new { c.Name, c.Age } );
와 같은 형태로 바꾸어 준다.

3. Lambda expression

이 lambda expressoin 이라는건 쉽게 말해 anonymous delegate을 쉽게 쓰는 방법이다. 실제 c => c+1; 은 delegate R Func<A,R>(A arg) { return arg + 1; }과 동일한 코드다. 그럼 이게 어떻게 작동하는가? C# 3.0에 새로운 기능 implicitly typed local variable 기억하는가? 이 implicitly typed 이란 쉽게 말해 type inference 기능이다. 다시 말해 c => c+1; 이라고 하면 컴파일러가 컴파일 타임에 c 의 타입을 알아내고 또한 c+1;의 타입을 알아낸다. 다시 말해 delegate R Func<A,R>(A arg}... 라고 한다면 컴파일러가 컴파일 타임에 generic type variable인 A와 R의 type을 알아내서 자동으로 지정해 주는거다.

그럼 이게 또 Linq랑 어떤 상관이 있는가? 만약 lambda expression이 없다고 해 보자. 그럼 
var v = customers.Select(delegate (int arg) { return arg+1;})이라고 쓰던지 아니면 
Funk<...> f = new Funk<..>( Method );
var v = customers.Select(f); 해야  할꺼다. 정말 간단한 이런 query문 조차 이렇게 복잡해 지는데 만약
var v = customers.Where(c => c.Age > 20).Select(c => new { c.Name, c.Gender }); 처럼 몇단계만 더 거친다고 생각해 보자... 물론 이것 말고도 다른 이유가 있는데, 일단 이걸로 넘어가자.

4. Object and Collection initializers

이건 쉽게 예상 가능하듯, object나 collection을 선언과 동시에 초기화 하는거다.
Customer c = new Customer() { Name="haha", Age=13, ... };
List<int> list = new List<int> {0,1,2,...};
물론 이것도 compile time에 compiler가 다음과 같이 해 주는거다
c.Name = "haha";
c.Age = 13; ...
혹은
list.Add(0);
list.Add(1), ...
물론 compiler가 어떻게 변환 해주는지 보면, 어떤 제약 사항이 있는지 알꺼다. Name, Age .. 등등은 반드시 accessible 해야 하는 member field나 property이어야 한다. 또한 Collection은 반드시 ICollection<T>을 implement 해야 사용할수 있다.

그럼 이게 Linq랑 또 무슨 상관이 있는가?

다음과 같은 코드가 있다고 하자
class Set { public string RightSide, public string LeftSide };

var v = relationSets.Select( c => new Set() { RightSide = c.Inch, LeftSide = c.Cm } );

위와 같이 named type을 사용하는 경우, 이 기능은 좀 더 간단하게 object를 초기화 하는 방법을 제공하게 된다. 만약 이 기능이 없다면 아마도

var v = relationSets.Select( c => { Set s = new Set(); s.RightSide = c.Inch; s.LeftSide = c.Cm; return s; } );

이렇게 써야 할꺼다.

5. Anonymous types

이것도 제목 자체가 나타내듯이
var v = new { Name = "haha", Age=19 };
처럼 이름이 없는 타입을 compile 타임에 만들수 있는 방법이다. 실제 사용자가 anonymous type을 만들게 되면 compiler가 compile 타임에 type inference를 이용하여

class _Anonymous { private string name, private ing ange; public string Name { ...}, public int Age {...}}

와 같은 타입을 만들어 준다.

그럼 이게 또 Linq와 무슨 상관이 있느냐?

알다시피 data를 다루는 프로그램밍을 하다 보면, 대부분의 operation이 projection을 하는거다 무슨 말이냐면 10개의 property (column)를 같는 object(table)에서 3개의 property(column)만 가져 오는 그런식 말이다.
ex) select name, age, gender from customers;

이 anonymous type은 C# 내에서 이것을 가능하게 해 준다. 만약 anonymous type이 없다면 사용자는 새로운 projection이 필요할때 마다 새로운 type을 만들어야만 할꺼다.

물론 이 anonymous type의 scope와 local에서만 사용 가능하다는 점 등등 여러가지 제약 사항이 있지만, 대부분의 경우 무척 간편하게 query를 가능하게 해 준다.
예로 ..
var v = from c in customers where c.Name = "haha" select new { c.Age, c.Gender};
얼마나 간편한가?

6. Implicitly typed arrays

이건 밑에 예처럼 array의 타입을 초기화 시키는 멤버로 부터 가져 오는거다
var a = new [] { 1, 2, 3, ... };
물론 역시 제약 사항은 있다, type inference를 사용하는거기 때문에 initializer안의 type은 모두 동일 해야 한다. 만약 그렇지 않으면 compile time error다. 물론 초기화 안의 내용이 implicitly convertible 하다면 괜찮다. 그때의 type은 가장 base 타입이 되게 된다.

그럼 이게 Linq랑 어떻게 상관 있는가?

이것 역시 다른 type inference의 예와 같이 Linq를 쓸때 projection을 쉽게 해 준다. 만약 이게 없다면 멤버에 array가 들어 갈때 마다 statement를 써야 할것 이다.

7. Query Expressions

드디어 Query Expression이다. 이건 완전히 100% Linq 때문에 생긴거다. 일반 사용자에게는 Query Expression이 Linq다. 위에 예제들을 보면 내가 가끔은

var v = customers.Select( c => c.Name ); 이라고 쓰고 가끔은
var v = from c in customers select c.Name; 이라고 쓴걸 볼수 있을 꺼다.

밑에 있는게 소위 말하는 query expressoin이다. Linq 자체는 사실 query expression이 아니다, Linq 자체는 위에 새로 들어간 모든 기능을 조합 하여 Lanquage 자체 내에서 data 에 대해 manipulation을 할수 있게 하는거다. 이 data는 XML에 저장되어 있든 database에 저장 되어 있든, List에 저장 되어 있든 사실상 상관이 없다. C# 언어 자체가 데이타라는 개념을 인식하고 그와 관련된 operation을 제공한다는게 Linq다.

하지만, 언어 자체가 data를 인식하고 backend와 상관 없이 동일한 조작 방법을 제공하더라도 만약 그게 너무 사용하기 어렵거나 이질적이라면 실제 사용자에게 돌아가는 혜택이 많을수가 없다. 그래서 들어간게 이 query expresison이다. 이 query expression은 sql과 무척 비슷한데 정확히 sql은 아니다. 이건 C# 자체에서 정의하는 데이타를 조작하는 keyword들이다.

이 query expression은 컴파일 타임에 Extension method들로 재 해석 되고 다시 이 extension method들은 위에서 설명한 static class method call로 재 해석 되어지게 된다.

사용자 입장에서는 뒷단에서 무슨 일이 일어나는지 알 필요 없이, 아주 간단하게 동일한 방법으로 데이타들을 조작할수 있게 되는거다.

예를 들면
var v = from c in customers orderby c.Country, c.Balance descending select new { c.Name, c.Country, c.Balance };

var v = from c in customers where c.city == "Londone" from o in c.Orders where o.OrderDate.Year == 2005 select new { c.Name, o.OrderID, o.Total };

물론 Query expression과 extension method를 사용하는것은 1:1 대응이기 때문에, 위에 query expression을 밑에와 같이 쓸수도 있다

customers.Where(c=>c.City == "London").SelectMany(c=> c.Orders.Where(o=> o.OrderDate.Year == 2005).Select(o=>new { c.Name, o.OrderID, o.Total} ));

밑에 extension method라 바뀐것과 비교하면 query expression이 얼마나 쉬운지 알수 있을꺼다. 물론 extension method 로 사용하는건 실제 데이타의 흐름을 느낄수 있다는 점에서 장점이 있긴 하다. extensio method로 된 query문을 보면 데이타가 어떤식으로 filtering 되어 왼쪽에서 오른쪽으로 이동하는지 느낄수 있을것이다.

8. Expression Trees

이제 다 왔다, 마지막 이다. Expression Tree가 무엇일까? compiler 과목을 들어본 사람은 알겠지만 이건 쉽게 말해서 AST이다. 일단 이게 왜 필요한지 부터 말을 하자면, 성능 때문이다.

위에 내가 쓴걸 쭉 읽어 보면서 만약 데이타 set을 하나의 query expression을 넘어 갈때 마다 처리 한다면 같은 data set을 도대체 얼마나 filtering 하는건가 생각 하는 사람이 있을꺼다.

예를 들어
var v = customers.where(c=>c.Name == "haha").where(c=>c.Age > 19).where(c=>Gender == Gender.Male );

이라고 한다면, customers 라는 데이타 set을 name이 haha인걸로 한번 filter 하고, 그 다음에 다시 age로 필터 하고 그 다음에 male로 filter한다면, 결국 같은 data set을 줄어들긴 하지만 3번에 걸쳐 iterate하게 된다.

만약 late execute이 불가능 하다면, 이 성능 저하는 Linq에게 치명적이게 된다, 그래서 만들어 진게 Expression Tree이다. 아까 위에서 말한 Lambda expression을 기억하는가? 위에서 내가 쉽게 설명 하기 위해 Lambda expression은 anonymous delegate의 간단한 표기 법이라고 했는데 사실 맞지 않다. lambda expression은 delegate에 assign되면 delegate가 되고 expreesion tree에 assign 되면 expression tree가 된다.

예들 들어
Func<int, int> f = x => x+1; 이라고 하면 여기서 lambda expression x => x+1;은 delegate이다. 하지만
Expression<Func<int, int>> e = x => x+1;이라고 하면 여기서 lambda expression은 expression tree라고 하는 데이타 스트럭쳐가 된다. 다시 말하면 실행 코드가 아닌 x=>x+1라는 구조를 가지는 AST가 되게 된다.

이게 중요한 이유는 위의 query 에서 한단계를 넘어 갈때 마다 실제 데이타를 manipulate 하는게 아니라, 어떻게 manipulate 하기를 원하는가에 대한 정보만 넘긴다는것이다.

나중에 실제 사용자가 데이타를 사용하는 시점에서 이 AST는 실제 backend data provider가 원하는 형태로 번역 되서 보내지게 된다.

DLinq를 예를 들면 (DLing = database, XLinq == XML)
var v = customers.where(c=>c.Name == "haha").where(c=>c.Age > 19).where(c=>Gender == Gender.Male );
의 경우 select * from customers where name="haha" 를 보내서 모든 데이타를 메모리로 들고 와서, 그 메모리 데이타를 다시 age와 gender에 따라 filter하는게 아니라

위의 statement 문에서는 사용자의 query를 Expression tree 형태로 받은 후에 DLinq에서 SQL statement에 맞게 바꾼후, 다시 그 SQL문을 optimize 한후, 그 sql statement를 저장하고 있다가, 사용자가 실제 v를 사용하는 순간 ex) foreach(var e in v) { e ...}, sql 서버에 query를 날려 데이타를 가져 오게 된다.

....

휴 길다.. 하여간 이상이 이번 새로운 C# 3.0 버젼에 들어가게 되는 새로운 기능과 소위 말하는 Linq에 대한 간략한 설명이었다.

좀 더 부연 설명 하자면, 이 C# 3.0의 새로운 기능은 모두 Compile time에 이루어 진다. 흠 정확히는 그렇지 않지만 expression tree를 delegate으로 runtime에 바꿀수 있으니까 ... - 하여간 그냥 compile time이라고 보면 된다. 그리고 이미 존재 하는 .Net Framework의 기능만을 사용하기 때문에, C# 2.0의 generic의 경우와는 달리 C# 3.0을 지원하기 위해 .NET Framework .NET Framework가 바뀌진 않는다.

그리고 위에 C# 3.0에 들어간 새로운 여러 기능을 꼭 Linq에서만 필요해서 들어간 것처럼 쓰긴 했지만, 아니다. 모든 새로운 기능들이 각자 사용자들에게 주는 혜택이 있다. 예를 들어 extension method의 경우, 꼭 Linq가 아니더라도 특정 interface에 적용 되는 모든 operation을 한곳에 모아 library 화 할수도 있고 visitor pattern의 경우 accept 함수를 한곳에 모아 둘수 있고, 등등 각각 나름의 혜택이 있다.

내가 여기 쓴건 정말 맛뵈기도 안되고, 실제 C# 3.0을 써보고 싶으신 분은 이번에 새로 release되는 CTP 를 구해 사용해 보기 바란다.

또 C# 3.0의 정확한 스팩을 알고 싶은 사람은 정식 C# 3.0 스펙을 다운 받아 보기 바란다.

하여간 그럼

수고!!!

 

Published Friday, April 28, 2006 11:39 PM by HeeJae Chang
Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Interesting Finds (oS edition) 2006/04/28

Monday, May 01, 2006 2:55 AM by Jason Haley

# Website Scripts &raquo; HeeJae&#8217;s Blog : C# 3.0 !!!

Monday, January 21, 2008 12:12 AM by Website Scripts » HeeJae’s Blog : C# 3.0 !!!

# re: C# 3.0 의 새로운 기능들!!!

Saturday, June 13, 2009 11:38 AM by 小向美奈子

話題の小向美奈子ストリップを隠し撮り!入念なボディチェックをすり抜けて超小型カメラで撮影した神動画がアップ中!期間限定配信の衝撃的映像を見逃すな

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker