'.NET/C#'에 해당되는 글 37건

  1. 2017.01.23 Language Server Protocol, OmniSharp-Roslyn 빌드 오류 해결
  2. 2009.02.16 [C# 4.0] Parallel Extension - [2] 병렬 처리 아키텍처
  3. 2009.02.12 [C# 4.0] Parallel Extension - [1] 병렬 처리
  4. 2008.08.21 C# 코드로 GAC 어셈블리 등록하기
  5. 2008.08.07 LINQ 퀴즈 문제 풀어봅시다
  6. 2008.07.24 .NET Framework 2.0 에서 LINQ TO Object 사용하기 (1)
  7. 2008.03.30 Custom LINQ Provider - [5]. LINQ To Naver Open API
  8. 2008.03.27 Custom LINQ Provider - [4]. Query(쿼리)를 이용한 원격 개체 탐색
  9. 2008.03.17 Custom LINQ Provider - [3]. Custom LINQ Provider 만들기 (IQueryProvider)
  10. 2008.03.13 Custom LINQ Provider - [2]. Custom LINQ Provider 만들기 (IQueryable)
  11. 2008.03.10 Custom LINQ Provider - [1]. 소개
  12. 2008.03.01 [C# 4.0] Parallel Extension - [3] TPL(Task Parallel Library)
  13. 2007.12.18 실전 event [6] - 취소 가능한 버튼 서버컨트롤 활용
  14. 2007.12.18 실전 event [5] - 취소 가능한 버튼 서버컨트롤 만들기
  15. 2007.12.17 실전 event [4] - 취소 가능한 이벤트 만들기
  16. 2007.12.17 실전 event [3] - 취소 가능한 이벤트란
  17. 2007.12.16 실전 event [2] - 유저컨트롤에서 페이지로 이벤트로 값 전달
  18. 2007.12.16 실전 event [1] - 이벤트 시작하기
  19. 2007.12.04 [C# 3.0] LINQ to Sql 의 쿼리를 로그로 남겨보자
  20. 2007.11.05 LINQ QUIZ
  21. 2007.09.17 LINQ To Sql 의 올바른 사용
  22. 2007.09.12 확장 메서드의 설계(Architect)
  23. 2007.09.09 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2] (1)
  24. 2007.09.08 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1] (1)
  25. 2007.09.04 LINQ 의 OUTER JOIN 작업
  26. 2007.09.04 LINQ to SQL Classes 와 LINQ의 JOIN 작업 (2)
  27. 2007.08.30 XDocument 클래스와 LINQ
  28. 2007.08.29 확장 메서드( Extension Method )
  29. 2007.08.12 SoundPlayer 클래스로 WAVE 파일 재생
  30. 2007.07.04 [제네릭 4편] 제약이 붙은 제네릭 클래스

마이크로소프트(Microsoft)는 VSCode 에서 다양한 개발 편의 기능을 제공하기 위한 Language Server Protocol 을 공개했다. 이 프로토콜의 C# 버전이 바로 OmniSharp-Roslyn이 되겠다.

그 외에 다양한 언어의 구현체가 등장했는데, 어떤 개발 언어가 구현 되었는지 아래의 링크에서 확인하기 바란다.

필자는 OmniSharp-Roslyn 을 git clone 하고 빌드하게 되면 다음과 같은 오류를 만났다.

개발환경

  • OS: MacOS Sierra
  • Version: 10.12.2

The type initializer for 'System.Net.Http.CurlHandler' threw an exception.

위의 이슈는 아래와 같이 보고가 되었다.

이 이슈는 다음과 같이 해결하면 된다.

brew update
brew install openssl
ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/
ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/


Posted by 땡초 POWERUMC
TAG c#, OmniSharo

댓글을 달아 주세요

지난 포스트에서 Parallel Extension 과 LINQ 를 이용한 PLINQ 에 대해서 살펴보았습니다. 지난번에 얘기했듯이 Manual Parallelism 는 Parallel Extension 의 성능을 절대 따라올 수 없다고 하였습니다. 왜냐하면, Parallel Extension 은 Manual Parallelism 의 병렬 처리 방식보다 더 복잡하고 정밀한 병렬 처리 알고리즘으로 처리하기 때문입니다.
 
Parallel Extension 이란?
 
Parallel Extension 은 전혀 새로운 것이 아닙니다. C# 3.0 의 LINQ 는 LINQ 쿼리식을 제공하기 위해 새로운 컴파일러(Compiler) 가 필요했습니다. 정확히 말하자면 C# 의 Language Service 의 버전이 업그레이드 되었고, LINQ 쿼리식을 편하게 쓸 수 있도록 Visual Studio 2008 을 사용해야 했습니다. 다시 말하자면, LINQ 쿼리식이 아닌 확장 메서드(Extension Methods) 만으로 쿼리가 가능했다는 것이 이것을 증명해 줍니다. 확장 메서드(Extension Methods) 는 결국 IL 레벨에서는 정적(Static) 인 인스턴스(Instance) 에 불과하니까요.
 
Parallel Extension 은 새로운 컴파일러(Compiler) 가 필요하지 않습니다. .NET 의 기본적인 코어(Core) 인 mscorelib.dll, System.dll, System.Core.dll 만을 사용하여 구현이 되었습니다. 그리고, 기존의 ThreadPool 을 개선하였고, LINQ 와 통합하여 선언적으로 Parallel Extension 을 사용할 수도 있게 되었죠.
 
Task Parallel Library 를 통해 데이터 처리와 어떤 작업(Task)에 대해서도 병렬 처리도 가능해 졌습니다. 이제는 데이터의 병렬 처리뿐만 아니라, 작업(Task) 단위로서도 Task Parallel Library 로 병렬 처리가 가능합니다.
 
이제는 병렬 처리가 된다는 것이 중요한 게 아니라, 병렬 처리의 내부적인 예외 핸들링이나 Visual Studio 에서 디버깅(Debugging) 이 가능합니다. 이러한 새로운 매커니즘으로 내부적으로는 새로운 예외 핸들링 모델(Exception Handling Model)이 필요했습니다.
 
또한 .NET Framework 4.0 의 Parallel Extension 은 다양한 언어를 지원합니다. C#, VB.NET, C++, F# 그리고 .NET 컴파일러(Compiler) 로 컴파일(Compile) 되는 언어라면 상관없습니다. RoR/PHP 라도 .NET 컴파일러(Compiler)에 의해 컴파일(Compile) 된다면 Parallel Extension 을 사용하는데 전혀 문제가 없습니다.
 
 
Parallel Extension 아키텍처
 
 
[그림1] Parallel Extension 아키텍처 (클릭하면 확대)
 
Parallel Extension 의 병렬 처리는 .NET 컴파일러(Compiler) 로 컴파일(Compile) 되는 어떤 언어든 차별을 두지 않고, 병렬 처리 기능을 사용할 수 있습니다. PLINQ 로 작성된 쿼리(Query)는 별도의 PLINQ Provider 의 엔진(Engine) 에서 쿼리를 분석하는 과정을 거치게 됩니다. 쿼리 분석(Query Analysis) 에 의해 선언된 LINQ 쿼리식을 분석하여 최적의 알고리즘으로 각각의 작업을 배치하게 됩니다.
 

[그림2] Parallel Extension 작업 분할
 
각각 분배되는 작업은 쓰레드 큐(Thread Queue) 에 쌓이고, 이 큐에 쌓이는 작업(Task) 는 실제 작업자 쓰레드(Worker Thread) 에 할당이 됩니다. 하지만, Parallel Extension 은 단지 쓰레드에 할당하는 것으로 작업이 마치기를 기다리지 않습니다. 만약, 작업을 분배하는 것은 Manual Parallel 과 크게 다르지 않기 때문이죠.
 

[그림3] Parallel Extension 작업 분할
 
Parallel Extension Library 는 병렬 처리의 작업을 지속적으로 최적의 상태를 감시합니다. 예를 들어, A 의 작업이 Task 1, Task 2, Task 3 인데, B 의 작업은 모두 마친 상태라고 할 때, Parallel Extension Library 는 A 의 작업을 놀고 있는 B 에게 또 다시 분배합니다. 이러한 반복적으로 병렬 처리의 작업이 최적화 될 수 있도록 하여, 병렬 처리의 성능을 극한으로 끌어올립니다.
 
LINQ 만 알면 난 병렬 처리 개발자
 
Parallel Extension 은 LINQ 와 통합하기 위한 프로바이더(Provider) 를 제공합니다. 아직 LINQ 잘 모르신다구요? 30분만 투자하시면 LINQ 를 사용하는데 큰 지장이 없습니다. 그리고 그만큼 쉽습니다. LINQ 를 이해하기 위해 제네릭(Generic), 확장 메서드(Extension Methods), 익명 타입(Anonymous Methods) 도 함께 공부하시면 좋습니다.
 
예전에 이런 광고도 있었죠.
“비트 박스를 잘하려면?” “북치기, 박치기만 잘하면 됩니다”
 
“그럼 PLINQ 개발자가 되기 위해서는?” “AsParallel() 만 잘하면 됩니다.”
 
맞습니다. Parallel Extension Library 의 AsParallel() 확장 메서드(Extension Methods) 만 알면 당신도 이제 병렬 처리 개발자입니다. 이전 포스트의 PLINQ 예제에서 처럼 AsParallel() 만 붙이면 그것을 PLINQ 라고 부릅니다^^ (병렬 처리를 위한 확장 메서드와 옵션은 더 많이 존재합니다 )
 
아래는 AsParallel() 의 예 입니다.
private static void ParallelSort(List<Person> people)
{
       var resultPerson = from person in people.AsParallel()
                                    where person.Age >= 50
                                    orderby person.Age ascending
                                    select person;
 
       foreach (var item in resultPeople) { }
}
 
하지만, 무조건적인 병렬 처리는 오히려 성능을 저하시킬 수 있습니다. 특히 PLINQ 를 사용하는 병렬 처리는 .NET Framework 내부적으로 쿼리 분석(Query Analysis) 과정을 거치게 됩니다. 각각 프로세서(Processor) 에 분배된 데이터는 또 분배되고, 최적화가 가능할 때까지 계속적으로 분배됩니다. 마치 세포 분할이 일어나는 것처럼 말이죠.
 
최소한 병렬 처리를 위해서 데이터에 대한 이해와 추측이 가능해야 합니다.
 
1.     추측 가능한 데이터의 양
2.     추측 가능한 데이터의 내용
3.     추측 가능한 데이터 처리 시간
 
이러한 최소한의 예측 작업을 하지 않는다면, 오히려 PLINQ 를 이용할 때 성능은 저하될 수 있습니다. 예를 들어, 평균 데이터의 양이 2개라고 가정한다면, PLINQ 의 쿼리 분석(Query Analysis) 작업은 오히려 성능 저하의 요인이 됩니다. PLINQ 쿼리 분석(Query Analysis) 에 의해 두 번째 프로세서(Processor) 의 사용량이 많다고 판단된다면, 병렬 작업은 의미가 없어지고 오히려 성능을 저하시킬 테니까요. ‘쿼리 분석(Query Analysis) 작업이 눈 깜빡 거리는 시간(1/40(0.025)초) 이라고 가정한다면, 만 건의 쿼리 분석(Query Analysis) 작업 시간은 250초’가 될 테니까요.

Posted by 땡초 POWERUMC

댓글을 달아 주세요

최근 대부분의 사용자들의 컴퓨터의 사양이 코어2 로 업그레이드 되고 있습니다. CPU 제품에 따라 코어에 대한 아키텍처가 다르지만, 기본적으로 이들 제품은 하나의 컴퓨터에 CPU 가 두 개인 제품들입니다. 인간과 비교하자면 뇌가 두 개인 사람인데 그다지 상상해서 떠올리고 싶지 않네요^^.

컴퓨터는 CPU 두 개를 보다 효율적으로 이용하기 위해 바로 Parallelism Processing(병렬 처리)를 하게 됩니다. 하나의 CPU 의 성능을 향상시키는 방법이 아닌, 두 개의 CPU 에게 작업을 할당함으로써 데이터의 처리 성능을 극대화 시키게 됩니다. 우리에게 익숙한 운영체제인 윈도우(Windows) 의 멀티 쓰레딩(Multi Threading) 을 생각하면 병렬 처리(Parallelism Processing) 는 그렇게 어려운 개념은 아닙니다.
 
[그림1] 어쨌든 뇌가 두 개 (여기에서 참조)
 
원래 오픈 소스 프로젝트로 Parallel Extension 프로젝트를 CodePlex 에서 본 기억이 있는데, 지금은 링크의 주소를 찾을 수 가 없네요. 구글을 통해 “Parallel Extension” 을 검색하시면, .NET 에서의 Parallel Programming 의 흔적을 찾아볼 수 있습니다.
 
우선 아래의 Person 클래스를 작성하여 테스트에 사용할 것입니다.
 
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
 
 
General~
 
코어(Core) 하나로 작업할 경우, 개발자는 아무것도 염려 하지 않아도 됩니다. 그 동안 우리가 배웠던 대로 코드를 작성하기만 하면 됩니다. 병렬 처리에 대한 고민을 하지 않고 개발한 코드라면 모두 이 범주에 속하겠네요. 이러한 방법은 가장 보편적으로 작성할 수 있습니다.
 
private static void GeneralSort(List<Person> people)
{
       List<Person> resultPeople = new List<Person>();
       foreach (Person person in people)
       {
             if (person.Age >= 50)
                    resultPeople.Add(person);
       }
 
       resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
 
       foreach (var item in resultPeople) { }
}
 
List<Person> 개체를 파라메터로 넘겨주고, Person 중에 Age 가 50이 넘는 개체를 정렬하는 코드입니다.
바로 이 코드를 병렬 처리를 하고자 합니다. 이 코드를 병렬 처리를 하고자 한다면 코드의 양은 훨씬 늘어나고, 복잡한 처리를 해야 합니다.
 
 
Manual Parallelism
 
일반적으로 데이터의 처리를 병렬 처리로 전환하기 위해서는 쓰레드(Thread) 를 사용합니다. 쓰레드(Thread) 가 생성이 되면 커널 또는 물리적인 프로세서에 의해 의해 유휴 상태 또는 처리가 가능한 코어(Core) 로 작업이 할당되어 다중 작업(Multi Process) 을 가능하게 됩니다.
 
이러한 방법의 병렬 처리는 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 비동기 작업을 합니다.
 
private static void ThreadSort(List<Person> people)
{
       var resultPeople = new List<Person>();
       int partitionsCount = Environment.ProcessorCount;
       int remainingCount = partitionsCount;
       var enumerator = (IEnumerator<Person>)people.GetEnumerator();
       try
       {
             using (var done = new ManualResetEvent(false))
             {
                    for (int i = 0; i < partitionsCount; i++)
                    {
                           ThreadPool.QueueUserWorkItem(delegate
                           {
                                 var partialResults = new List<Person>();
                                 while (true)
                                 {
                                        Person baby;
                                        lock (enumerator)
                                        {
                                              if (!enumerator.MoveNext()) break;
                                              baby = enumerator.Current;
                                        }
                                        if (baby.Age >= 50)
                                        {
                                              partialResults.Add(baby);
                                        }
                                 }
                                 lock (resultPeople) resultPeople.AddRange(partialResults);
                                 if (Interlocked.Decrement(ref remainingCount) == 0) done.Set();
                           });
                    }
                    done.WaitOne();
                    resultPeople.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
             }
       }
       finally
       {
             if (enumerator is IDisposable) ((IDisposable)enumerator).Dispose();
       }
 
       foreach (var item in resultPeople) { }
}
 
중요한 부분은 추출된 데이터의 정렬(Sort) 작업입니다. 이 작업을 하기 위해서는 모든 쓰레드(Thread) 의 작업이 끝나야 합니다. 만약 모든 쓰레드(Thread) 가 종료되지 않은 시점에서 정렬 작업을 하게 되면, 과연 정렬된 데이터를 신뢰할 수 있을까요?? ( 왜 그런지는 여러분의 상상에 맡기도록 합니다. )
 
정렬 작업을 하기 전 ManualResetEvent 의 WaitOne() 메서드를 호출하여 모든 쓰레드(Thread) 의 WaitHandle 이 작업이 신호를 받을 때까지(동기화 작업) 기다려야 합니다. 예를 들어, 두 개의 쓰레드(Thread) 가 생성 되고 첫 번째 쓰레드는 이미 작업을 종료하였지만, 두 번째 쓰레드는 아직 작업이 완료되지 않았다면, 작업을 마친 모든 쓰레드(Thread) 는 가장 늦게 처리가 완료되는 쓰레드를 기다려야 정렬 작업을 진행할 수 있습니다.
 
마지막으로, 위의 코드의 병렬 처리 작업은 성능에 한계가 있습니다. 프로세서(Processor) 개수만큼 쓰레드(Thread) 를 생성하여 작업을 분배하는 방식이기 때문에, 병렬 처리 작업의 성능은 곧 프로세서(Processor) 개수가 될테니까요!
 
 
Parallel Extension
 
C# 4.0 은 병렬 처리를 하기 위해 코드의 양을 획기적으로 줄일 수 있습니다.
 
private static void ParallelSort(List<Person> people)
{
       var resultPerson = from person in people.AsParallel()
                                    where person.Age >= 50
                                    orderby person.Age ascending
                                    select person;
 
       foreach (var item in resultPeople) { }
}
 
LINQ 식을 사용하여 데이터 처리와 정렬 작업을 간단하게 할 수 있습니다. 감격이네요^^ 바로, .NET Framework 4.0 의 Parallel Extension 을 사용하여 LINQ 처럼 사용하는 것을 PLINQ 라고 합니다.
 
Q : foreach (var item in resultPeople) { } 코드를넣었나요?
 
A: 동일한 테스트를 하기 위함입니다. LINQ 식은 내부 구조의 특성상 “쿼리식”에 불과합니다.
보다 자세한 내용은 필자의 블로그를 참고하세요.
 
Parallel Extension 은 Manual Parallelism 보다 더 복잡하고 좋은 성능을 낼 수 있는 알고리즘으로 구현이 되어 있습니다. 그렇기 때문에 아무리 많은 코어를 가진 컴퓨터에서 동일한 테스트를 한다고 하여도 결코 Manual Parallelism 은 Parallel Extension 의 병렬 처리 성능을 기대할 수 없습니다.
 
이제 살며시 그 내부 구조도 궁금해 집니다. (다음에 계속…)
Posted by 땡초 POWERUMC

댓글을 달아 주세요

C# 코드로 GAC 어셈블리 등록하기
 
예전에 MSDN Forum 에 자료를 찾던 중에 “C# 코드를 이용하여 GAC 에 어셈블리를 등록할 수 없나요?” 라는 질문을 본적이 있었습니다.
아마, 그때 답변은 엉뚱한 답변들이었죠. 물론, 저도 오늘의 이 코드를 보기 전까지 말이죠^^;
 
희미하게 기억이 날듯 말듯 합니다만, 그때 답변중의 내용이 RegisterAssembly 였던 것 같기도 합니다. 이 메서드는 COM Interop 에 등록하는 메서드인데 말이죠.. ㅎㅎ
 
그래서 혼자 생각했었습니다.
배포 시에 GAC 에 어셈블리를 등록할 경우가 생긴다면,,
1.      GacUtil 을 리소스에 포함한다.
2.      Deploy Project 에서 리소스를 파일로 복원하여, Command Prompt(Process.Start) 를 통해 GACUTIL 로 등록하고,
3.      배포를 종료한다
 
이런 시나리오를 머리 속으로 그렸던 적이 있었습니다.
하지만 오늘 단 한 줄이 이 코드를 보고, ‘정말 어리석었구나’ 느꼈답니다.
 
뭐 긴말 없이, 코드를 보시죠.
 
new System.EnterpriseServices.Internal.Publish()
.GacInstall(@”...Path...\ClassLibrary1.dll");
 
이렇게 한 줄로, GAC 에 어셈블리를 등록할 수 있었답니다. ( 두 줄이라고 우기지 마세요 -_-; )
 
아마 저처럼 테스트 해 보실 분도 계실 거라고 생각합니다. 10의 2~3명은 “어!! 안되는데요!!” 라고 하실겁니다.
Strong Key 를 주시고, 다시 해보세요^.^
( 훔… Strong Key 로 서명이 안되어 있어도, 오류는 없더군요 )
 
머.. 뒷북이라면 당신은 대략 “지못미!!”
 
Reference
 

Posted by 땡초 POWERUMC
TAG c#, GAC

댓글을 달아 주세요

Mitsuru 블로그에 재미있는 LINQ 문제가 올라와 있네요..
 
문제는
 
var values1 = newstring[] { "1", "2", "3" };
var values2 = newstring[] { "A", "B" };
 
var q = ?
 
foreach (var v in q)
    Console.WriteLine(v);

 
 
뭐.. 내가 볼땐 조인하라는 말 같은데,, 저는 나름대로 아래와 같이 풀어 보았습니다.

답은 여러가지 나올 수 있겠죠? 같이 푸실 분은 아래를 보지 마시고, 원문 먼저 보시고 맞춰보세요 ^^

 
원문
 














 
var values1 = new string[] { "1", "2", "3" };
var values2 = new string[] { "A", "B" };
 
var q = from a in values1
               from b in values2
               orderby a+b ascending
               select a+b;
 
foreach (var v in q)
        Console.WriteLine(v);
 
실제 쿼리문만 보시면 될 것 같네요. 허벌나게 간단하죠?
 
엇. 쓰고보니, 두 번째 댓글에 비스므리 하게 답을 누가 달아놓았네요 +_+;
그래도 여기까지 쓴게 아까워서…
 
텨텨 =3=3=3
Posted by 땡초 POWERUMC
TAG LINQ, Quiz

댓글을 달아 주세요



이전에 LINQ 에 대한 자료를 찾던 중 .NET Framework 2.0 으로 작성되어진 LINQ To Object 라이브러리를 찾아놓은 적이 있습니다. 최근에 다른곳에 신경을 쓰다보니, 이제서야 이 자료를 공유하고자 합니다.
 
.NET Framework 2.0 으로 작성된 코드는
 
List<string> source = new List<string>();
source.Add("Rhapsody Of Fire");
source.Add("TYR");
source.Add("Echo of Dalriada");
source.Add("Finntroll");
source.Add("Finntroll3");
source.Add("Finntroll2");
source.Add("Finntroll1");
source.Add("Finntroll4");
           
//run a simple query
var result = from item in source
             let x = item + "hej"
             orderby x
             select x;

하지만, 순수히 .NET Framework 2.0 에서 C# 2.0 컴파일러가 사용되기는 문제가 있겠죠? VS 2005 IDE 의 컴파일러가 C# 3.0 에서 추가된 Keyword 를 인식하지 않기 때문에, 좀 더 정확하게 표현하자면 VS 2008 이 사용하는 C# 3.0 컴파일러로 컴파일이 가능합니다. 어차피 var 키워드나 LINQ 에서 사용되는 from, select 와 같은 Keyword 는 런타임이 아닌 컴파일 단계에서 IL 코드로 변환되어질테니까요.
 
때문에, 위의 라이브러리는 VS 2008 의 Multi Targetting 의 .NET Framework 2.0 버전으로 컴파일이 가능합니다.
 
개발환경이 Visual Studio 2008 에서 .NET Framework 2.0 으로 작업될 때, 위의 라이브러리를 이용하여 LINQ TO Object 를 사용할 수 있겠네요. 만드신 분 굿입니다^^
 
소스코드 다운로드 및 원문
 
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 지송 2011.01.21 08:40 Address Modify/Delete Reply

    잘보고 갑니다 ^^ 오늘도 즐겁게 보내세요.




 
소스 코드 다운로드시 코드의 Naver Open API Key 를 변경하여 사용하시기 바랍니다.

드디어 5회차까지 왔네요. 이전까지는 Custom LINQ Provider 를 만들기 위해 몸풀기 과정이었다면, 이번 시간에는 이것을 응용한 LINQ To Naver Open API 를 만들어 볼 것입니다. 아직 Custom LINQ Provider 에 대한 품새가 잘 그려지지 않는다면, 이번 예제를 통해 왜 Custom LINQ Provider 가 유용한 지에 대해 잘 알 수 있을 거라고 생각합니다.
 




 
LINQ To Naver Open API

우선 이 LINQ To Naver Open API 를 만들면서 몇 가지 고민을 하게 되었습니다.
 
1.      Naver Open API 는 조건 검색을 지원하지 않는다.
2.      query 파라메터를 통해 where 절의 조건을 넣게 되면, 수많은 쿼리 parsing 이 필요하다. 그렇기 때문에 구현하기 위해 좀 더 많은 시간이 필요하다.
3.     query 를 검색 한 후에, 검색 결과에 대해 where 조건을 만족시킨다. 현재 이 방법으로 구현되었으며, 기본적인 Lambda 식을 이용하기 때문에 구현하기 쉽다
 
.현재 3번의 방법으로 구현이 되었습니다. 그럼 어떤 모습으로 구현되었는지 한번 볼까요?
 
 
어떤가요?? 굉장히 편해하지 않습니까? 다음과 같이 블로그 검색도 지원합니다.
 
 
아래는 비디오 검색을 사용하고, 그 결과를 보여줍니다.
 
 
예제로 보여드리는 이 LINQ To Naver Open API 는 다음과 같은 검색을 지원합니다.
 
1.      NaverOpenAPIModel.Blog – 블로그 검색
2.      NaverOpenAPIModel.Kin – 지식in 검색
3.      NaverOpenAPIModel.WebKR – 웹 문서 검색
4.      NaverOpenAPIModel.Cafe – 카페 검색
5.      NaverOpenAPIModel.Shop – 쇼핑 검색
6.      NaverOpenAPIModel.Encyc – 백과사전 검색
7.      NaverOpenAPIModel.Krdic – 국어사전 검색
8.      NaverOpenAPIModel.Jpdic – 일어사전 검색
9.      NaverOpenAPIModel.Endic – 영어사전 검색
10.   NaverOpenAPIModel.News – 뉴스 검색
11.   NaverOpenAPIModel.Local – 지역검색
12.   NaverOpenAPIModel.Video – 동영상 검색
13.   NaverOpenAPIModel.Image – 이미지 검색
 
이중 책 검색과 실시간 검색어는 Request XML 스키마가 조금 달라서, 귀찮아서 빼버렸습니다 ㅡ,.ㅡ;
 
Context 클래스를 제네릭 클래스로 변경하면서 쉽게 LINQ 쿼리를 만들 수 있습니다.
 
 
쓸만한 LINQ To Naver Open API 가 되지 않았나 생각합니다.
이번 포스팅은 코드가 한 줄도 없는 포스팅이 되었네요. 코드는 첨부파일을 참고하시면 될 것 같고, 부수적인 내용은 1,2,3,4 회차 포스팅을 참고 하시면, 특별한 설명이 없어도 될 것 같습니다.
 
 
연재를 마치며…
 
처음에 외국 문서를 그냥 번역 또는 각색해서 올릴까 라고 생각했었지만, 제대로 된 Custom LINQ Provider 를 직접 손대기에는 난이도가 높았습니다. 외국 문서에 비해 비록 디테일이 떨어지지만, LINQ 의 내부를 훓어 보기엔 충분하다고 생각이 듭니다.
 
첨부된 LINQ To Naver Open API 소스코드를 참고하시면, 실제 웹 서비스에서 적용해도 될 만큼 다양한 LINQ Provider 가 나올 수 있길 기대해 봅니다. ( 다양한 테스트는 하지 못해서, 버그가 있을 수도 있습니다 )
 
그 동안 읽어주신 분들께 감사드립니다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
이번에는 쿼리를 이용하여 원격 개체 탐색을 하는 방법에 대해서 알아보겠습니다. 이 파트는 마치 LINQ To SQL 과 비슷하긴 하지만, 원격 개체라는 것의 대상을 SQL 서버에만 두는 것이 아니라는 것을 명심하셔야 합니다. 이번 예제는 SQL 서버를 이용하여 쿼리를 탐색하는 것이지만, 이 다음 파트인 LINQ To Naver Open API 를 보시면, 다양한 원격 개체에 접근 할 수 있다는 것을 알 수 있을 것입니다.

 
 
SampleContext 에 쿼리 Log 프로퍼티 추가
                                                  
SampleContext 의 소스는 2회차의 소스와 똑같습니다. 다만, 원격 탐색을 하기 위해 질의식을 어떻게 만들었는지 알 수 있도록 Log 프로퍼티를 추가합니다. 원격 서버에 원하는 데이터를 가져올 수 있도록, 질의를 해야 하는데, 그것이 SQL 서버면, SQL 쿼리식이 될 것이고, 또는 WMI 통한다라고 하면, WMI 쿼리식이 될 것입니다.
 
public class SampleContext : IQueryable<Person>
{
    // 생략
 
       public string Log
       {
             get { return this.provider.sbLog.ToString(); }
       }
}
 
 
SampleProvider 의 프로퍼티 추가와 Visitor 클래스 만들기
 
아래의 StringBuilder 는 쿼리식을 만들기 위한 객체입니다.
 
public class SampleProvider : IQueryProvider
{
    // 생략
 
       public StringBuilder sbLog = new StringBuilder();
}
 
그리고 IProvidor 의 Execute<T> 메서드의 내용을 변경하고자 합니다. 우선 테스트용으로 이렇게 작성하였고, 실제 원격 개체에 연결하기 위해 이후에 다시 이 메서드의 코드는 변경할 예정입니다.
 
public TResult Execute<TResult>(Expression expression)
       {
       var exp             = expression as MethodCallExpression;
       var func     = (exp.Arguments[1] as UnaryExpression).Operand as Expression<Func<Person, bool>>;
       var lambda   = Expression.Lambda<Func<Person, bool>>(func.Body, func.Parameters[0]);
 
       var r = context.DataSource.Where(lambda.Compile());
 
       TranslateExpression trans = new TranslateExpression();
       sbLog.Append( trans.Translate(expression) );
 
       return (TResult)r.GetEnumerator();
}
 
이전 소스와 비교해 보았을 때, 약간 틀린 점이 있습니다.
 
TranslateExpression trans = new TranslateExpression();
sbLog.Append( trans.Translate(expression) );
 
바로 이 부분인데, TranslateExpression 클래스는 C# 3.0 의 쿼리식을 실제 원격 서버에서 질의 할 수 있는 쿼리식으로 변경하는 클래스입니다. Translate() 메서드를 통해 LINQ 식을 텍스트로 변환하는 것입니다.
 
Visitor 패턴이란?
패턴을 구분할 때 Visitor 패턴은 행위 패턴에 속합니다. 간접적으로 클래스에 다형적인 기능을 추가합니다. 즉, 새로운 클래스를 많이 추가하기를 원치 안을 경우 선택하면 좋은 대안이 될 수 있습니다.
 
 
TranslateExpression 클래스
 
소스코드를 전체로 보면 좋겠지만, LINQ 의 내부를 살펴보는 기초적인 포스팅이니, 메서드별로 구분하여 설명 드리고자 합니다.
 
TranslateExpression 클래스의 맴버와 생성자는 다음과 같습니다. StringBuilder 의 sb 맴버는 하나하나의 식을 분석하여 쿼리를 만들 것입니다. 그리고 생성자의 expression 은 LINQ 의 표현식이겠죠?
 
생성자
 
public class TranslateExpression
{
       private StringBuilder sb   = new StringBuilder();
 
       public string Translate(Expression expression)
       {
             this.Visit(expression);
 
             return sb.ToString();
       }
}
 
Visit 메서드
 
Visit 메서드는 이 클래스에서 상당히 중요한 부분입니다. 위에 말씀드린 Visitor 패턴을 구현하고 있지요. Visitor 패턴은 다른 패턴과 유사한 점이 많기 때문에, 이것은 Interpreter 패턴처럼 보일 수도 있고, 확장한다면 Composite 패턴과도 같아 보일 수 있습니다. 하지만 패턴은 코드의 내용 보다는 관점을 어떻게 보느냐가 더 중요합니다.
 
Expression 클래스는 ExpressionType 의 열거형 맴버를 가지고 있습니다. 현재의 Expression 이 어떤 표현식을 가지고 있는지 명확히 나타내고 있습니다. 적당히 쿼리가 만들어 질 수 있는 정도만을 구현하였기 때문에 JOIN 이나 GROUPING 기능은 수행할 수 없답니다.
 
protected Expression Visit(Expression expression)
{
       switch (expression.NodeType)
       {
             case ExpressionType.Call:
                    return this.VisitCall((MethodCallExpression)expression);
 
             case ExpressionType.Constant:
                    return this.VisitConstant((ConstantExpression)expression);
 
             case ExpressionType.Lambda:
                    return this.VisitLambda((LambdaExpression)expression);
 
             case ExpressionType.MemberAccess:
                    return this.VisitMember((MemberExpression)expression);
 
             default:
                    throw new Exception( string.Format("{0} 지원하지않습니다", expression.NodeType));
       }
}
 
VisitCall 메서드
 
이 메서드는 MethodCallExpression 표현식을 분석합니다. LINQ 식의 모든 C# 메서드는 이것의 대상이 되는 것입니다.
 
MethodCallExpression 의 메서드는 하나 이상의 상수나 변수를 포함하거나, 리턴 타입이 있어야 합니다. 즉, void 형의 C# 메서드는 LINQ 식에 포함이 될 수 없습니다.
 
 
protected virtual Expression VisitCall(MethodCallExpression mce)
{
       switch (mce.Method.Name)
       {
             case "Where":
                    sb.Append("SELECT * FROM").Append( Environment.NewLine );
                    this.Visit(mce.Arguments[0]);
                    sb.Append(" AS T WHERE ");
 
                    UnaryExpression ue         = mce.Arguments[1] as UnaryExpression;
                    LambdaExpression le        = ue.Operand as LambdaExpression;
 
 
                    BinaryExpression be        = le.Body as BinaryExpression;
            if (be != null)
                this.VisitBinary(be);
                           break;
 
             case "StartsWith":
                    this.Visit(mce.Object);
                    sb.AppendFormat(" LIKE '{0}%'", mce.Arguments[0].ToString().Replace("\"",""));
                    break;
       }
 
       return mce;
}
 
mce.Arguments[1]
 
를 디버깅 해보면,
 
{r => (((r.Age >= 20) && (r.Age <= 30)) && r.Name.StartsWith("엄"))}
 
위와 같이 마치 람다식과 같이 생겼습니다. 하지만, 이것의 NodeType 은 Lambda 가 아닌 Quote 입니다. Quote 는 상수값이 포함된 표현식입니다. 이것의 피연산자를 람다표현으로 바꾸는 구문이
 
UnaryExpression ue         = mce.Arguments[1] as UnaryExpression;
LambdaExpression le        = ue.Operand as LambdaExpression;
 
이렇게 되고, BinaryExpression 으로 다시 표현이 가능할 경우, BinaryVisit 을 수행하게 됩니다.
 
그리고,
 
case "StartsWith":
       this.Visit(mce.Object);
       sb.AppendFormat(" LIKE '{0}%'", mce.Arguments[0].ToString().Replace("\"",""));
       break;
 
위 코드는 StartsWith 의 메서드를 SQL 쿼리식과 같이 LIKE ‘xxx%’ 처럼 바꾸는 역할을 하게 됩니다.
 
VisitLambda 메서드
 
VisitLambda 메서드는 LambdaExpression 을 분석합니다. LambdaExpression 은 Body 속성이 있으며, 이 Body 는 여러가지의 NodeType 이 올 수 있습니다. 현재 소스에서는 특별한 기능을 하지 않습니다.
 
protected virtual Expression VisitLambda(LambdaExpression le)
{
       return le;
}
 
 
VisitConstant 메서드
 
이 메서드는 ConstantExpression 을 분석합니다. 신기하게도 잘 살펴보면 이 ConstantExpression.Value 는 SampleContext 를 참조하고 있습니다. 이놈은 테이블을 참조 하고 있지만, Constant 로 가장하고 있습니다. 이 ConstantExpression 의 ElementType 을 가져와서 매핑되는 테이블로 변환해 줍니다.
 
protected virtual Expression VisitConstant(ConstantExpression ce)
{
       IQueryable q = ce.Value as IQueryable;
       if (q is IQueryable)
       {
             sb.AppendFormat(" ( SELECT * FROM {0} ) ", q.ElementType.Name)
                    .Append( Environment.NewLine );
       }
       else
       {
             sb.AppendFormat(" {0} ", ce.Value);
       }
 
       return ce;
}
 
그래서 ConstantExpression 은 IQueryable 로 가장하고 있지 않을 경우, 일반적인 상수값으로 취급할 수 있습니다.
 
 
VisitMember 메서드
 
이 메서드는 MemberExpression 의 표현을 분석합니다. LINQ 쿼리식의 맴버는 모두 여기에 해당됩니다.
 
protected virtual Expression VisitMember(MemberExpression me)
{
       sb.Append(me.Member.Name);
 
       return me;
}
 
예를 들어,
 
var result = from r in context
                     where r.Age >= 20 && r.Age <= 30 && r.Name.StartsWith("")
                     select r;
 
와 같은 식의 MethodCallExpression 은 Where 절이 될 것이고, 이곳의 람다 표현식으로 각각의 연산식을 분석해 보면,
 
각각의 BinaryExpression 은
 
r.Age >= 20
r.Age <= 30
r.Name.StartsWith("")
 
이 됩니다. 이 BinaryExpression.Left 는 r.Age 로 표현이 되지만, 우리가 변환할 쿼리식에서 “r.Age” 의 “r.” 은 필요가 없습니다. 때문에, MemberExpression 의 Member.Name 을 통해 “Age” 와 같이 오직 맴버 이름만을 표현하도록 하고 있습니다.
 
VisitBinary 메서드 ( Update 2008/03/27 - 오타 수정 )
 
이 메서드는 BinaryExpression 의 표현을 분석합니다. BinaryExpression 의 Left/Right 를 구현하고 있으며, 이 두 피연산자를 쪼개어내어 결합하는 기능을 하고 있습니다.
 
중요한 것은 BinaryExpressoin 의 Left/Right 는 그 안에 또 다른 BinaryExpression 을 포함할 수 있습니다.
 
var result = from r in context
                     where r.Age >= 20 && r.Age <= 30 && r.Name.StartsWith("")
                     select r;
 
와 같은 식의 BinaryExpression 은
 
be.Left = {((r.Age >= 20) && (r.Age <= 30))}
be.Right = {r.Name.StartsWith("엄")}
 
가 될 수 있습니다. 때문에, 위의 각각의 Left/Right 대한 VisitBinary 를 수행해야 합니다. 즉,재귀호출과도 같죠.
 
protected virtual Expression VisitBinary(BinaryExpression be)
{
       if (be.Left is BinaryExpression)
             this.VisitBinary((BinaryExpression)be.Left);
       else
       {
             this.Visit(be.Left);
       }
 
    switch (be.NodeType)
    {
        case ExpressionType.GreaterThan:
            sb.Append(" > ");
                    break;
 
        case ExpressionType.GreaterThanOrEqual:
                    sb.Append(" >= ");
                    break;
 
             case ExpressionType.LessThan :
                    sb.Append(" < ");
                    break;
 
             case ExpressionType.LessThanOrEqual:
                    sb.Append(" <= ");
                    break;
 
        case ExpressionType.Equal:
            sb.Append(" = ");
                    break;
 
             case ExpressionType.And:
             case ExpressionType.AndAlso:
                    sb.Append(" AND " );
                    break;
 
             case ExpressionType.Or:
                    sb.Append(" OR ");
                    break;
 
             default:
                    throw new Exception( string.Format("{0} 형식은지원하지않습니다", be.NodeType) );
    }
 
       if (be.Right is BinaryExpression)
             this.VisitBinary((BinaryExpression)be.Right);
       else
       {
             this.Visit(be.Right);
       }
 
    return be;
}
 
BinaryExpression.NodeType 은 기본적인 덧셈(+),뺄셈(-) 외에도 &&, ||, 또는 비트연상 등의 연산도 포함될 수 있습니다. 특히, &&, || 와 같은 조건식을 AND,OR 로 변환해 주어야 할 필요가 있습니다.
 
 
한번 프로그램을 실행해 볼까요?
 
다음과 같은 코드입니다.
 
class Program
{
       static void Main(string[] args)
       {
             SampleContext context = new SampleContext() ;
             context.DataSource = new List<Person> {
                    new Person { Name="엄준일", Age=29},
                    new Person { Name="엄호희(내동생)", Age=26},
                    new Person { Name="엄혜진(울누나)", Age=31},
                    new Person { Name="멍멍이", Age=6},
                    new Person { Name="발발이", Age=5}
             };
 
 
             var result = from r in context
                      where r.Age >= 20 && r.Age <= 30 && r.Name.StartsWith("")
                      select r;
 
             result.ToList().ForEach( o=>Console.WriteLine(o.Name ));
 
             Console.WriteLine("----------------------------");
             Console.WriteLine( context.Log );
       }
 
결과는 다음과 같습니다.
 
엄준일
엄호희(내동생)
----------------------------
SELECT * FROM
 ( SELECT * FROM Person ) AS T
 WHERE Age >= 20 AND Age <= 30 AND Name LIKE '엄%'
계속하려면 아무 키나 누르십시오 . . .
 
어떻습니까? 제법 쓸만한 QueryProvider 가 되었지요?
 
 
원격 서버에 연결
 
우리는 원격 서버를 MS-SQL 서버로 실습을 할 것입니다. 실습을 위해 테이블을 만들고, 테스트 데이터를 만들도록 하겠습니다.
 
Person 클래스와 같은 스키마와 실습 데이터를 넣었습니다.
 
CREATE TABLE Person
(
[Name] VARCHAR(50) NOT NULL,
[Age] INT NOT NULL
)
 
INSERT INTO Person(Name,Age) VALUES ('엄준일',29)
INSERT INTO Person(Name,Age) VALUES ('엄호희(내동생)',26)
INSERT INTO Person(Name,Age) VALUES ('엄혜진(울누나)',31)
INSERT INTO Person(Name,Age) VALUES ('멍멍이',6)
INSERT INTO Person(Name,Age) VALUES ('발발이',5)
 
그리고 SampleContext 의 GetEnumerator<Person> 메서드를 다음과 같이 수정하였습니다.
 
public IEnumerator<Person> GetEnumerator()
{
       provider.Execute<IEnumerator<Person>>(this.expression);
 
       SqlConnection cn           = new SqlConnection("Server=xxxx.kr;DataBase=xxxx;UID=xxxx;PWD=xxxx");
       SqlCommand cm              = new SqlCommand( Log, cn);
       cm.CommandType                   = System.Data.CommandType.Text;
 
       cn.Open();
       Console.WriteLine("DataBase Connection!");
 
       SqlDataReader reader       = cm.ExecuteReader();
                   
       List<Person> list          = new List<Person>();
       while (reader.Read())
       {
             list.Add( new Person {
                    Name   = reader["Name"].ToString(),
                    Age    = Convert.ToInt32(reader["Age"])
             });
       }
 
       reader.Close();
       cn.Close();
 
       return list.GetEnumerator();
}
 
위와 같이 실제 데이터베이스를 연결하여 쿼리를 수행하도록 하였습니다.
 
결과는 다음과 같습니다.
 
DataBase Connection!
엄준일
엄호희(내동생)
----------------------------
SELECT * FROM ( SELECT * FROM Person ) AS T WHERE Age >= 20 AND Age <= 30 A
ND Name LIKE '%'
계속하려면아무키나누르십시오 . . .
 
이 과정에서 변경된 소스는 특별히 자세한 설명이 필요 없을 것 같아서 첨부된 소스코드를 참고 하시기 바랍니다.
 

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
지난 시간에 이어, 이번 시간에는 실제 Provider 를 구현해 보도록 하겠습니다. 지난 시간에 언급하였듯이, 실제 쿼리식을 해석하고, 동작이 가능한 형태로 바꾸는 작업을 아래의 Provider 에서 할 수 있습니다.
 
IQueryProvider
 
실제로 외부 서버나 서비스 등에 필요한 쿼리를 만들 수 있는 인터페이스입니다. 이 인터페이스는 4개의 메서드를 지원합니다. 중복된(제너릭/비제너릭) 메서드를 제외하면 2개의 메서드만 제대로 구현하시면 됩니다.
 
우선 소스부터 나갑니다.
 
public class SampleProvider : IQueryProvider
{
       private SampleContext context;
 
       public SampleProvider(SampleContext context)
       {
             this.context = context;
       }
 
       #region IQueryProvider Members
 
       public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
       {
             return (IQueryable<TElement>)context;
       }
 
       public IQueryable CreateQuery(Expression expression)
       {
             throw new NotImplementedException();
       }
 
       public TResult Execute<TResult>(Expression expression)
       {
             var exp             = expression as MethodCallExpression;
             var func     = (exp.Arguments[1] as UnaryExpression).Operand as Expression<Func<Person, bool>>;
             var lambda   = Expression.Lambda<Func<Person, bool>>(func.Body, func.Parameters[0]);
 
             var r = context.DataSource.Where(lambda.Compile());
 
             return (TResult)r.GetEnumerator();
       }
 
       public object Execute(Expression expression)
       {
             throw new NotImplementedException();
       }
 
       #endregion
}
 
소스에는 특별한 주석은 없으며, 차근차근 서술식으로 의문을 풀어드리도록 하겠습니다.
 
 
Constructor
이전에 봤던 SampleContext 객체에서 Provider 프로퍼티를 보셨을 겁니다. 프로퍼티는 다음과 같이 정의되어 있습니다.
 
public IQueryProvider Provider
{
        get
        {
               if( provider == null )
                       provider = new SampleProvider(this);
 
               return provider;
        }
}
 
SampleProvider 에서 SampleContext 에 정의된 DataSource 나, 다음 회차에서 볼 원격 탐색을 하거나, 로그를 남길 수 있도록 하기 위해 SampleContext 를 참조해야 합니다.
 
CreateQuery<T> Method
이 메서드는 Expression, 즉 표현식의 인자를 받는 메서드입니다. IQueryable<TElement> 를 리턴하며, TElement 의 타입은 Person 이 되겠습니다.
 
context.expression = expression;
return (IQueryable<TElement>)context;
 
와 같이 매우 보잘 것 없는 내용으로 구현되었습니다. 하지만, 원격 개체에 연결하기 위한 쿼리를 만들기 위해서 이곳에서 실제 SQL 쿼리의 “SELECT” 구문과 같은 쿼리를 만드시면 됩니다.
 
Execute<T> Method
쿼리식을 이곳에서 해석해서 실행합니다. SampleContext 객체에서 GetEnumerator 메서드는
 
public IEnumerator<Person> GetEnumerator()
{
        return provider.Execute<IEnumerator<Person>>(this.expression);
}
 
보시는 것과 같이, SampleProvider 의 Execute 메서드를 호출합니다. 리턴 타입이 IEnumerator<Person> 인 것을 미루어 보아, 쿼리 호출 후 반환되는 결과값을 리턴하는 것이라는 것을 짐작할 수 있습니다.
 
여기에서도 어김없이 몇 가지 재미있는 표현식(Expression) 이 나옵니다.
 
먼저, MethodCallExpression 을 보겠습니다. 이 Expression 은 쿼리식에서 호출한 메서드를 가져오는 녀석입니다. 아래의 샘플을 보겠습니다.
 
Expression<Func<string,string,bool>> func = (s1,s2) => ( s1.Substring(0,2) == s2 );
BinaryExpression be        = func.Body as BinaryExpression;
MethodCallExpression me    = be.Left as MethodCallExpression;
Console.WriteLine(me.Method.Name);

이항 연산식의s1.Substring(0,2) == s2” 좌측부분을 가져와서 이것을 MethodCallExpression 으로 변환하여 실행 메서드 내용을 가져옵니다. 감이 잡히시나요?
 
답은
 
Substring
 
됩니다.
 
이런 방법으로 실제 메서드를 원격 서버의 쿼리식에 맞도록 변환 할 있습니다.
 
위의 func 아래와 같이 바꾸어
 
Expression<Func<string,string,bool>> func = (s1,s2) => ( s1.ToUpper() == s2 );
 
결과는 “ToUpper” 출력 되지만, MSSQL 쿼리로 “UPPER” 변환 하는 것과 같이 쿼리식을 자유자제로 변형 할 있습니다.
 
그럼, 다음으로 나오는 UnaryExpression 보겠습니다.표현식은 단항 연산자가 있는 식을 나타냅니다. bool 연산식에서 부정 NOT 등과 같이 하나의 피연산자를 사용하는 bool 연산자입니다.
 
들어가기 전, 문제를 하나내겠습니다. UnaryExpression 단항 연산자를 나타내는 표현식 클래스입니다. 그럼, 이항 연산자를 나타내는 클래스는??
 
 
그렇습니다. 이항 연산을 나타내는 클래스는 BinaryExpression 입니다. 후후,,, 기억하고계시는군요.
 
다음은 UmcBlog Article 테이블입니다.
 
r[
그림2] UmcBlog Article 테이블
 
UmcBlogDataContext db      = new UmcBlogDataContext();
var query    = from article in db.Articles
              where article.Title.Contains("Umc")
              select article;
 
MethodCallExpression me    = query.Expression as MethodCallExpression;
UnaryExpression ue         = me.Arguments[1] as UnaryExpression;
Console.WriteLine(me.Method.Name);
Console.WriteLine(ue.Operand);
 
이것의 결과는 아래와 같습니다.
 
Where
article >= article.Title.Contains(“Umc”)
 
위의 UnaryExpression 결과가 이렇게 나오는지 이해가 가지 않습니다. 분명 단항 연산자는 피연산자가 하나일 경우를 일컷습니다. UnaryExpression.Operand 속성은 단항 연산의 피연산자를 가져온다고 했는데, 위의 결과는 연산자가 LambdaExpression 말하는 같군요. 피연산자를 LambdaExpression 대리자인 “article” 일컷는 것이라 생각되지만 확실히 감이 서질 않습니다. 누가 아시는 분 답변 좀 부탁합니다,.;
 
LambdaExpression
이것은 우리가 흔히 사용하는 바로 람다식표현하는 Expression 입니다. 여러가지 Expression 조합해서 약간의(?) 동적으로 람다식을 만들어 수도 있습니다. (준비된 표현식을 이용하여…)
 
Expression<Func<string,string,string>> func   = ( s1, s2 ) => ( s1 + s2 );
var le       = Expression.Lambda(func.Body, func.Parameters.ToArray());
 
var result1 = func.Compile();
var result2 = le.Compile();
 
Console.WriteLine( result1("Umc","Blog" ) );
Console.WriteLine( result2.DynamicInvoke("Umc","Blog") );
 
func 표현식을 명시적으로 컴파일되며, le 표현식을 이용해 람다식을 만든것입니다.
 
위에 보이는 Compile() 메서드를이용하여 IL 코드로변환하게됩니다.
 
IL (Intermediate Language)코드란?
.NET 에서 MSIL 이라고도 부릅니다. 컴파일러에 의해 실행이 가능하도록 중간 언어로 컴파일 것을 말합니다. 비로소, IL 코드는 .NET Framework CLR(Common Language Runtime) JIT(Just-In-Time) 컴파일러에 의해 Native 코드로 컴파일이 됩니다.
 
Compile() 메서드는 아래와 같이 생겼답니다.
 
internal Delegate Compile(LambdaExpression lambda)
{
    this.lambdas.Clear();
    this.globals.Clear();
    int num2 = this.GenerateLambda(lambda);
    ExpressionCompiler.LambdaInfo info2 = this.lambdas[num2];
    ExpressionCompiler.ExecutionScope scope2 =
        new ExpressionCompiler.ExecutionScope(
                      null,
                      lambda,
                      this.lambdas.ToArray(),
                      this.globals.ToArray(),
                      info2.HasLiftedParameters);
    return info2.Method.CreateDelegate(lambda.Type, scope2)
}
private int GenerateLambda(LambdaExpression lambda)
{
    this.scope = new ExpressionCompiler.CompileScope(this.scope, lambda);
    DynamicMethod method2 = new DynamicMethod(“lambda_” + ExpressionCompiler.iMethod++,
                                              lambda.Body.Type,
                                              this.GetParameterTypes(lambda),
                                              typeof(ExpressionCompiler.ExecutionScope),
                                              true);
    ILGenerator generator2 = method2.GetILGenerator();
    this.GenerateInitLiftedParameters(generator2);
    this.Generate(generator2, lambda.Body, ExpressionCompiler.StackType.Value);
    generator2.Emit(OpCodes.Ret);
    int num2 = this.lambdas.Count;
    this.lambdas.Add(new ExpressionCompiler.LambdaInfo(lambda,
                                                       method2,
                                                       this.scope.HasLiftedParameters));
    this.scope = this.scope.Parent;
    return num2;
}
 
소스 코드가 중요한게 아니라, 바로 Compile() 메서드가 실행 코드로 변환해 준다는 것 입니다. 그래서, LambdaExpression 예제 코드의 실행 결과는
 
UmcBlog
UmcBlog
 
동일한 결과가 나타나게 됩니다. 때문에, SampleProvidier Execute<T> 메서드 내의
 
context.DataSource.Where(lambda.Compile());
 
코드는 어떠한 람다식이라도 실행 가능한 형태로 변환해주게 되며, DynamicInvoke 호출하여 동적으로 컴파일된 람다식을 실행하게됩니다.
 
 
SampleContext Provider 이용한 쿼리 만들기
 
여기까지해서 SampleContext SampleProvider 클래스에 대한 설명은 다 한같습니다. 그럼, Custom LINQ Provider 이용하여 LINQ 식을만들어보면
 
class Program
{
       static void Main(string[] args)
       {
             SampleContext context = new SampleContext() ;
             context.DataSource = new List<Person> {
                 new Person { Name="엄준일", Age=29},
                 new Person { Name="내동생", Age=26},
                    new Person { Name="울누나", Age=31},
                    new Person { Name="멍멍이", Age=6},
                    new Person { Name="발발이", Age=5}
             };
            
 
             var result = from r in context
                                  where r.Age > 20
                                  select r;
 
             result.ToList().ForEach( o=>Console.WriteLine(o.Name ));
       }
}
 
내용은 간단합니다. SampleContext 객체를 만들어, DataSource 임의의 데이터를 넣고, LINQ 쿼리식의 결과를 출력하는 예제입니다.
 
실제로 소스를 실행해 보면,
 
엄준일
내동생
울누나
 
출력이 되고, LINQ 쿼리식에 브레이크 포인터를 걸어 디버깅해 보면, 순차적으로 SampleContext SampleProvider 타면서 LINQ 쿼리식이 해석 되는 것을 있습니다.
 
[
그림3] 브레이크 포인터를 통해 LINQ 식의 Custom LINQ Provider 내부를 디버깅하는 화면
 
다음 시간에는, Custom LINQ Provider 통해 원격 개체를 탐색하는 방법에 대해 살펴 보도록하고, 이만 마치겠습니다. 이번 한주도 즐겁게 시작하시기 바랍니다^^//

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 

이전 시간에 Custom LINQ Provider 에 대해서 살며시 알아보았습니다. 이것을 다시 한번 요약하자면, .NET Framework 이 제공하는 공급자가 아닌, 개발자에 의해 통합 쿼리식을 이용하여 전혀 다른 데이터를 질의 하기 위해 사용합니다.
 
 
Entity Class 만들기
 
우리는 이 단원에서 간단한 Entity Class 를 이용할 것입니다. List<> 클래스에 간단한 데이터를 담을 수 있도록 선언된 클래스입니다.
 
public class Person
{
       public string Name { get; set; }
       public int Age      { get; set; }
}
 
 
Context 객체 만들기
 
우선 여기서 IQueryable<> 인터페이스를 살펴보겠습니다. MSDN 이 말하길 이 인터페이스는 “데이터 형식이 알려진 특정 데이터 소스에 대한 쿼리를 실행하는 기능을 제공합니다.” 라고 합니다. 물론, 제네릭 형식의 인터페이스므로 “데이터 형식이 알려진…” 이라고 하였으나, IQueryable 과 같은 비 제네릭 인터페이스는 제네릭이 아닌 IEnumerable 을 구현해야 합니다. 마찬가지로 IQueryable<> 인터페이스는 IEnumerable<> 을 구현해야 합니다.
 
이 Context 객체는 IQueryable<> 인터페이스를 구현함으로써, 쿼리식을 이용할 DataSource 에 대해 독자적인 Provider 로 식을 연산할 수 있습니다.
 
public class SampleContext : IQueryable<Person>
{
       internal Expression expression;
       private SampleProvider provider;
       public List<Person> DataSource { get; set; }
      
       #region IEnumerable<Person> Members
 
       public IEnumerator<Person> GetEnumerator()
       {
             return provider.Execute<IEnumerator<Person>>(this.expression);
       }
 
       #endregion
 
       #region IEnumerable Members
 
       System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
       {
             return (this as IEnumerable<Person>).GetEnumerator();
       }
 
       #endregion
 
       #region IQueryable Members
 
       public Type ElementType
       {
             get { return typeof(Person); }
       }                         
 
       public Expression Expression
       {
             get { return Expression.Constant(this); }
       }
 
       public IQueryProvider Provider
       {
             get
             {
                    if( provider == null )
                           provider = new SampleProvider(this);
 
                    return provider;
             }
       }
 
       #endregion
 
}
 
아까 잠시 설명드린 IQueryable<> 인터페이스가 제공하는 프로퍼티와 메서드는 모두 중요한 역할을 하고 있습니다.
 
Provider Property
우리는 DataSource 라는 속성에 특정 데이터를 넣게 되고, 이 데이터의 쿼리식을 연산할 Provider 의 객체를 리턴해야 합니다. 하나의 인스턴스에 하나의 객체만을 생성하기 위해서 null 체크를 하고, SampleContext 를 매개변수로 생성된 Provider 를 리턴합니다. 아직 구현되지 않았고, 잠시 후에 살펴보겠습니다.
 
Expression Property
Expression 속성은 Expression.Constant 라는 메서드를 사용합니다. 쿼리식을 표현하기 위해 Lambda Expression 과 같은 표현을 사용하게 되며, 이 Expression 은 다양하게 구문을 해석하기 위한 클래스를 제공해 줍니다.
 
기본적인 Expression 은 표현식을 처리할 수 있습니다.
 
Expression<Func<string,string,string>> ff = (s1,s2) => (s1 + s2);
Console.WriteLine( ff.Body );
 
위의결과는표현식의 Body(몸통) 부분을가져오는예입니다.
 
(s1 + s2)
 
신기하지 않나요?? ^^
 
가령, BinaryExpression 은 이항 연산과 관련된 식을 표현합니다. ( xxxExpression 은 Expression 을 상속 받습니다 ) +, *, EQ, AND 등이 여기에 속합니다.
 
Expression<Func<int,int,int>> f = (v1,v2) => v1 + v2;
BinaryExpression be = (BinaryExpression)f.Body;
Console.WriteLine( "{0} {1} {2}", be.Left, be.Right, be.NodeType );
 
위의결과는
 
v1 v2 Add
 
의 결과가 나옵니다.
 
그럼, ConstantExpression 의 예를 보도록 하죠. 이 Expression 은 상수 값이 있는 표현식을 처리할 수 있습니다.
 
Expression<Func<int, bool>> f = (v1) => ( v1 == 5 );
BinaryExpression be = (BinaryExpression)f.Body;
ConstantExpression ce = (ConstantExpression)be.Right;
Console.WriteLine( ce.Value );
 
결과는
 
5
 
가 됩니다.
 
그럼 문제를 하나 내 보도록 하겠습니다. 이후에 이 예제에서 나오게 될 LINQ 식입니다. 이 LINQ 식에서 ConstantExpression 은 어디에 해당할까요?
 
var result = from r in p
              where r.Age > 20
              select r;
 
설마 잘 모르시겠다고요?? ConstantExpression 은 상수의 값이 있는 식을 가져온다고 하였습니다. 저 쿼리식 중에 상수의 값이 어디 있는지 찾아보시면 답은 나올 겁니다.
 
ElementType Property
이 속성은 반복되는 식의 요소, 즉, 위에서 우리가 만들 Person 클래스는 List<Person> 객체에 담을 것이기 때문에, Person 클래스의 타입을 리턴하시면 됩니다.
 
GetEnumerator Methods
이 메서드도 굉장히 중요한 부분입니다. 잠시 예를 들어 보죠. 보통 DataBase 에서 SELECT 문을 이용하여 데이터를 조회할 때, 또는 프로시져 내의 SELECT 구문을 만나면 당장 이런저런 처리를 하여 결과를 뱉어냅니다.
 
그럼 여기서 또 하나의 문제를 내보겠습니다. 다음의 서로 다른 환경의 쿼리식을 10000번 반복했을 때, 수행시간은 누가 더 빠를까요?
1. 실제 Database 쿼리를 10000 번 수행
2. LINQ To SQL 쿼리식을 10000 번 수행
SELECT *
FROM p r
WHERE r.Age > 20
var result = from r in p
          where r.Age > 20
            select r;
 
언듯 생각하기에는, 둘의 결과는 비슷하리라 생각이 들기도 합니다. 2번은 Database Connection 이 맺어질 것이고, 더 느리면 느렸지, 빠르진 않다고 생각할 수 도 있겠죠. 좀 애매하네요 ^^;
 
하지만, 잠시 언급한 “비슷하다, 2번이 더 느릴것이다”라는 예상은 완전히 빗나갑니다.
 
1번의 경우 무지하게 오래 걸립니다. 왜냐하면, 질의에 대해 바로 바로 결과를 반환합니다.
2번의 경우 질의 결과를 반환하지 않습니다. 그렇기 때문에 굉장히 빠르게 10000번의 쿼리를 실행합니다.
 
 
LINQ 의 쿼리식은 다릅니다. 왜 2번이 답이 되는지 봅시다.
 
var result = from r in p
              where r.Age > 20
              select r;
 
의 쿼리식이 있을 경우, 예상과 다르게 런타임이 저 구문을 지날 때 result 는 어떠한 데이터의 질의 결과를 가지고 있지 않습니다. 즉, 질의에 대한 결과값은 전혀 없는, 그냥, SampleContext 객체일 뿐입니다. 런타임이 위의 쿼리식을 만나게 되면, Provider 에게 표현식을 위임하게 되고, 식을 해석만 합니다. 이것이 Database 에 연결된다고 할지라도, Database 에 요청할 쿼리식(SQL) 만을 만들 뿐입니다.
 
바로, 이 GetEnumerator 메서드가 호출되는 시점에, 데이터를 가공하여 결과를 반환하게 됩니다. 마찬가지로, Database 에 연결되는 작업이라면 이 시점에서 실제로 Database Connection 이 열리게 되고, 쿼리하여 결과를 반환할 수 있게 되는 것입니다.
 
 
다음 회차에서 IQueryProvider 인터페이스를 구현해 보겠습니다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
 
LINQ 의 출연
 
정말 .NET 이라는 세상이 많이 좋아진 것 같습니다. 강력한 Visual Studio 2008 이란 개발 툴에 차세대 프레임웍인 .NET Framework 3.5 로 아직까지 그것의 모두를 채 맛보지 못했습니다. C# 3.0 의 여운이 가시기도 전에 Silverlight 2.0 Beta 의 출시로 다시 한번 RIA 업계를 강타하였고, 올해는 너무나도 할게 많은 한 해가 될 것 같습니다.
 
C# 3.0 의 특징은
l Lambda Expression ( 람다식 )
l Object Initialize ( 객체 초기화 )
l Extension Methods ( 확장 메서드 )
l Anonymous Type ( 익명 타입 )
l 등등…
 
바로, C# 3.0 의 LINQ 는 이러한 람다식이라든지, 확장 메서드 등을 이용한 종합 예술이라고 생각합니다. 때문에, 위의 C# 3.0 의 특징을 이해하지 못한다면, LINQ 를 효율적으로 사용하기에 한계가 있을 것입니다. 때문에, 위의 C# 3.0 의 새로운 기능에 대해서 아직 잘 모르시는 분은 의 C# 3.0 을 참고하십시오.

 
 
LINQ Provider
 
Provider 란 사전적 의미로 “공급자” 정도로 해석할 수 있습니다. LINQ 는 Language Integrated Query 로써, 언어 통합 쿼리를 뜻합니다. 그럼, LINQ Provider 는 쿼리식과 같은 언어적 구문을 해석할 수 있는 공급자라고 생각하시면 됩니다.
 
.NET 이 제공하는 LINQ Provider 는 다음과 같습니다.
l LINQ To Objects
l LINQ To Entities
l LINQ To SQL – MS SQL 만 지원함
l LINQ To XML
 
바로 LINQ(Language Integrated Query ) 는 이렇게 다양한 Provider 들을 서로 다른 쿼리식이 아닌, 통합된 쿼리식으로 C# 3.0 에서 사용할 수 있답니다.
특히, LINQ To SQL 은 Visual Studio 에 함께 통합되어, 이전에 Typed DataSet 과 같이 Drag&Drop 을 지원하며, 자동으로 Entity 클래스를 생성하고, 이것에 대한 Context 객체를 생성해 줌으로써 Typed DataSet 과 Entity 객체의 장점을 모두 갖추며, 더욱 강력한 언어적 기능을 제공해 줍니다.
 
하지만, 이것은 단지 시작에 불과합니다. 기본적으로 제공하는 LINQ Provider 는 몇 개 되지 않지만, 개발자가 직접 LINQ Provider 를 만듦으로써 모든 대상을 통합된 쿼리식을 사용할 수 있습니다. 이토록 경이로울 수가… +_+;
 
 
Custom LINQ Provider
 
이미 해외의 많은 개발자들이 Custom LINQ Provider 를 만들어 내놓았습니다.
대표적인 사례는… ( 님 로그 참조 )
 
 
정말 대단합니다. LINQ To SQL 에 이은 LINQ To Oracle 과 LINQ To WMI, LDAP, Active Directory 등등… 대부분이 Open Source 이기 때문에, 그냥 가져다 쓰기만 하면 될 정도입니다. 이런걸 보고 있자면, 정말 우리(?)보다 훨씬 앞서가고 있는 개발자들이 태반이네요 ㅋ.ㅋ;;
이미 .NET Framework 3.5 Beta 버젼부터 나온 소스들도 있기 때문에, 소스중 현재 정식 .NET Framework 3.5 와 Namespace 체계가 다른것도 있습니다. 조금만 수정해 주시면, 위의 소스들을 컴파일 하시는데 어렵지 않으실 겁니다.
 
하지만, 저도 뭐하나 해봐야 할 것 같긴한데… 다음시간에 Custom LINQ Provider 를 살짝 만들어 보겠습니다. 기대는 하지 말아 주세요~ ^^;
Posted by 땡초 POWERUMC

댓글을 달아 주세요

Task Parallel Library
 
Parallel Extension 은 PLINQ 와 더불어 확장 가능한 Task Parallel Library 를 제공합니다. Task Parallel Library 는 PLINQ 를 이용하지 않고 개별적이고 수동적인 병렬 처리 작업을 위해 사용할 수 있습니다.
 
Task Parallel Library 는 크게 세 가지 방법으로 병렬 처리를 위한 Library 를 제공합니다.
 
Loops
 
[그림1] Parallel.For 를 이용한 병렬 처리
 
[그림2] Parallel.Foreach 를 이용한 병렬 처리
 
Task Parallel Extension 으로 병렬 처리를 쉽게 처리할 수 있으며, 병렬 처리로 인자값을 넘기거나 하는 작업을 쉽게 할 수 있습니다.
 
Statements
 
 
[그림3] Parallel.Invoke 를 이용한 병렬 처리
 
 
Task
 
특히 Parallel Extension Library 에서 Task 는 수동적으로 병렬 처리를 하기 위해 다양한 기능을 지원합니다. 정교하게 스레드(Thread) 를 처리했던 것에 비하면 심플하고도 직관적으로 병렬 작업을 처리할 수 있습니다.
 
Task 는 보다 정교하게 병렬 처리 작업을 할 수 있습니다.
l 대기
l 취소
l 연장
l 상하(부모/자식) 간의 관계
l 디버그 지원
 
아래는 ThreadPool.QueueUserWorkItem 처럼 바로 작업을 시작하도록 합니다.
 
Task.StartNew(…);
 
아래는 Task 에 대해 대기 및 취소 작업을 진행하도록 합니다.
 
Task t1 = Task.StartNew(…);
t1.Wait();
t1.Cancel();
Task t2 = t1.ContinueWith(…);
 
아래는 작업에 대해 지속적인 병렬 처리를 가능하도록 합니다.
 
var p = Task.StartNew(() => {
    var c = Task.StartNew(…);
}
 
아래는 특정 작업의 결과를 받아 올 수 있습니다.
 
var p =
 Future.StartNew(() => C());
int result = p.Value;
 
 
 
Coordination Data Structures
 
병렬 처리 작업은 PLINQ 와 TPL(Task Parallel Library) 를 지원하기 위해 기존의 데이터 컬렉션 등이 등장하였습니다. 내부적으로 동기화를 지원하지 않았던 문제들을 지원하게 되었고, 특히 오늘날 멀티 코어(Multi Core) 프로세스를 위해 많은 동기적인 문제를 고민해야 했습니다. .NET Framework 4.0 은 이러한 공통적인 문제들을 해결하기 할 수 있습니다.
 
l Thread-safe collections
       ConcurrentStack<T>
       ConcurrentQueue<T>
       ConcurrentDictionary<TKey,TValue>
      
l Work exchange
       BlockingCollection<T>
       IProducerConsumerCollection<T>
l Phased Operation
       CountdownEvent
       Barrier
l Locks
       ManualResetEventSlim
       SemaphoreSlim
       SpinLock
       SpinWait
l Initialization
       LazyInit<T>
       WriteOnce<T>

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
 
ButtonEx 컨트롤의 활용
5회차에서 빡시게 ButtonEx 서버 컨트롤을 만들어 보았습니다. 취소 가능한 이벤트를 통해 만든 서버 컨트롤을 활용할 수 있는 샘플을 보도록 하겠습니다.
 
여기서 만들어 볼 샘플은 유효성체크(Validate Check) 입니다. TextBox 컨트롤에 입력된 문자가 숫자인지 판별하는 간략한 샘플이지만, 어떻게 Before/After 이벤트를 분리하여 구현하는지 잘 보여주는 샘플이라고 생각합니다.

[그림1] 웹 폼 구성
 
아래의 소스는 int.Parse 메서드를 통해 숫자가 아닐 경우 Cancel = true 를 통해 After 이벤트를 취소하는 예제입니다.
namespace WebApplication4
{
        public partial class _Default : System.Web.UI.Page
        {
 
               protected void Page_Load(object sender, EventArgs e)
               {
               }
 
               protected void ButtonEx1_BeforeClick(object
sender,Sample.ButtonEx.BeforeClickEventArgs e)
               {
                       try
                       {
                              int.Parse( TextBox1.Text ); // 입력한값이숫자가아니라면 Exception 발생하겠죠?
                              Response.Write( TextBox1.Text + " 입력하였습니다<br/>");
                       }
                       catch
                       {
                              Response.Write("숫자만입력하세요");
                              e.Cancel = true;
                       }
               }
 
               protected void ButtonEx1_AfterClick(object sender,
Sample.ButtonEx.AfterClickEventArgs e)
               {
                       Response.Write("입력한값은숫자가맞습니다");
               }
        }
}
 
[그림2] 실행 결과
 
어떤가요? .NET Framework 은 이벤트 프로그래밍이라는 말이 과언이 아니라고 생각합니다. 이벤트는 .NET 프로그래밍에 있어서 굉장히 유용한 것 같네요. 두서 없이 아티클을 적어 보긴 했지만, 이미 필요한 분에게 많은 도움이 되었을 거라 생각합니다. 그럼 안녕^^//
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요




 
 
실전 취소 가능한 버튼 컨트롤 만들기
 
Umc.Core.EventHandlerDictionary 클래스
이 클래스는 Umc.Core 프로젝트에 포함된 클래스 입니다. 이 클래스의 이름에서 알 수 있듯이 이벤트를 사전(Dictionary)로 관리하도록 하기 위한 클래스 입니다.
 
namespace Umc.Core
{
        ///<summary>
        /// Umc.Core<br/>
        /// Delegate 담는 EventHandlerDictionary 컬렉션
        ///</summary>
        public class EventHandlerDictionary : IDisposable
        {
               ///<summary>
               /// Umc.Core<br/>
               /// Event 담는컬렉션
               ///</summary>
               private Dictionary<object,Delegate> eventDictionary = new Dictionary<object,Delegate>();
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary EventHandler 추가한다.
               ///</summary>
               ///<param name="key">키값</param>
               ///<param name="value">Delegate</param>
               public void AddHandler(object key, Delegate value)
               {
                       if ( eventDictionary.ContainsKey(key) )
                       {
                              eventDictionary[key] = Delegate.Combine(eventDictionary[key], value);
                       }
                       else
                       {
                              eventDictionary[key] = value;
                       }
               }
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary EventHandler 제거한다.
               ///</summary>
               ///<param name="key">키값</param>
               ///<param name="value">Delegate</param>
               public void RemoveHandler(object key, Delegate value)
               {
                       if ( eventDictionary.ContainsKey(key) )
                       {
                              eventDictionary[key] = Delegate.Remove(eventDictionary[key], value);
                       }
               }
 
               public bool Contains(object key)
               {
                       return this.eventDictionary.ContainsKey(key);
               }
 
               ///<summary>
               /// Umc.Core<br/>
               /// EventHandlerDictionary 인덱서
               ///</summary>
               ///<param name="key"></param>
               ///<returns></returns>
               public Delegate this[object key]
               {
                       get { return eventDictionary[key]; }
                       set { eventDictionary[key] = value; }
               }
 
               #region IDisposable 멤버
               ///<summary>
               /// Umc.Core<br/>
                /// EventHandlerDictionary 자원을해제한다.
               ///</summary>
               public void Dispose()
               {
                       if( eventDictionary != null )
                              eventDictionary.Clear();
 
                       eventDictionary = null;
               }
               #endregion
        }
}
 
클래스 내부에는 Dictionary 제네릭 클래스의 오브젝트를 생성하여, AddHandler(), RemoveHandler() 를 통해 이벤트를 사전에 추가/삭제 하는 로직이 들어있습니다.
 
 
ButtonEx 네임스페이스
Click 이벤트 전/후에 발생할 이벤트의 인자를 전달한 EventArgs 클래스 입니다. 이미 지난 회차를 보신 분이라면 그렇게 생소하지 않을 것입니다.
///<summary>
/// Before 이벤트가발생할전달될이벤트인자클래스입니다.
///</summary>
public class BeforeClickEventArgs : CancelEventArgs
{
        public string Reason { get; set; }
}
 
///<summary>
/// After 이벤트가발생할전달될이벤트인자클래스입니다.
///</summary>
public class AfterClickEventArgs : EventArgs { }
 
또한, 직관적인 코딩을 위해 Before/After 이벤트를 위한 델리게이트를 선언하였습니다. 특별한 변경된 EventArgs 와 같은 인자가 없다면 기존 EventHandler 를 활용해도 되지만, 언제든 추후에 확장될 가능성은 충분하다고 생각합니다. 그렇기 때문에 델리게이트를 선언해 주었답니다.
///<summary>
/// BeforeClick 이벤트를캡슐화하는델리게이트입니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
public delegate void BeforeClickEventHandler(object sender, BeforeClickEventArgs e);
///<summary>
/// AfterClick 이벤트를캡슐화하는델리게이트입니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
public delegate void AfterClickEventHandler(object sender, AfterClickEventArgs e);
 
여기서 이벤트는 사전(Dictionary) 을 통해 이벤트를 관리하게 됩니다. Umc.Core 프로젝트에 사용되는 EventHandlerDictionary 클래스의 일부를 직접 예제 클래스에 포함한 것입니다. 이 EventHandlerDictionary 에 접근하기 위한 Events 프로퍼티를 선언하였습니다.
private EventHandlerDictionary events;
///<summary>
/// EventHandlerDictionary 통해이벤트를관리합니다.
///</summary>
protected EventHandlerDictionary Events
{
        get
        {
               if( events == null )
                       events = new EventHandlerDictionary();
 
               return events;
        }
}
 
여기에서 선언된 이벤트는 EventHandlerDictionary 클래스에서 관리하게 될 것이기 때문에, event 선언에 대해 커스트마이징(?)이 필요합니다. 즉, 이벤트의 추가/삭제에 대한 액션을 변경할 필요가 있습니다.
private object EVENT_BEFORE_CLICK     = new object(); // BeforeClick 이벤트오브젝트
private object EVENT_AFTER_CLICK      = new object(); // AfterClick 이벤트오브젝트
 
///<summary>
/// BeforeClick 이벤트입니다. Click 이벤트가발생하기전에발생합니다.
///</summary>
[Description("Click 이벤트가발생하기전에발생합니다")]
public event BeforeClickEventHandler BeforeClick
{
        add
        {
               this.Events.AddHandler(EVENT_BEFORE_CLICK, value);
        }
        remove
        {
               this.Events.RemoveHandler(EVENT_BEFORE_CLICK, value);
        }
}
 
///<summary>
/// AfterClick 이벤트입니다. Click 이벤트가발생한발생합니다.
///</summary>
[Description("Click 이벤트가발생후에발생합니다")]
public event AfterClickEventHandler AfterClick
{
        add
        {
               this.Events.AddHandler(EVENT_AFTER_CLICK, value);
        }
        remove
        {
               this.Events.RemoveHandler(EVENT_AFTER_CLICK, value);
        }
}
 
여기는 각각의 Before/After 이벤트를 발생하는 메서드와 CancelEventArgs 의 Cancel 프로퍼티를 검사하는 메서드가 존재합니다. OnEventsFire() 메서드를 눈여겨 보시면 될 것 같습니다.
///<summary>
/// BeforeClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
protected virtual void OnBeforeClick(object sender, BeforeClickEventArgs e)
{
        if( !this.Events.Contains( EVENT_BEFORE_CLICK ) ) return;
 
        BeforeClickEventHandler handler       =
this.Events[ EVENT_BEFORE_CLICK ] as BeforeClickEventHandler;
        if( handler != null )
               handler(sender, e);
}
 
///<summary>
/// AfterClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
protected virtual void OnAfterClick(object sender, AfterClickEventArgs e)
{
        if( !this.Events.Contains(EVENT_AFTER_CLICK) ) return;
 
        AfterClickEventHandler handler =
this.Events[ EVENT_AFTER_CLICK ] as AfterClickEventHandler;
        if( handler != null )
               handler(sender, e);
}
 
///<summary>
/// Click 이벤트가발생할경우 BeforeClick/AfterClick 이벤트를발생합니다.
///</summary>
///<param name="sender"></param>
protected virtual void OnEventsFire( object sender )
{
        BeforeClickEventArgs beforeArgs       = new BeforeClickEventArgs();
        OnBeforeClick( sender, beforeArgs );
 
        if( beforeArgs.Cancel ) return;
 
        AfterClickEventArgs afterArgs = new AfterClickEventArgs();
        OnAfterClick( sender, afterArgs );
}
 
[Obsolete("ButtonEx 컨트롤에서는 Click 이벤트를사용하지않도록합니다. 대신 AfterClick 이벤트를사용하세요")]
protected override void OnInit(EventArgs e)
{
        base.OnInit(e);
        this.Click += new EventHandler(ServerControl1_Click);
}
 
private void ServerControl1_Click(object sender, EventArgs e)
{
        OnEventsFire(sender);
}
 
이제 간략한 소스 코드 설명은 끝났네요. 힘들게 만든 ButtonEx 서버 컨트롤을 어떻게 활용하면 될지 다음 회차를 참고하세요.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요




 
 
CancelEventArgs 클래스
CancelEventArgs 는 .NET Framework 이 제공하는 클래스 입니다. 이 클래스는 특정한 기능의 구현이 된 것이 아니라, 몇 가지 프로퍼티가 제공이 되는 것 뿐입니다.
 
CancelEventArgs 클래스의 메타 데이터를 보면 두 개의 생성자와 Cancel 프로퍼티를 제공하는 것을 알 수 있습니다.
 
[그림1] CancelEventArgs 클래스 메타데이타
 
 
예제
그럼 한번 취소 가능한 이벤트를 작성해 보겠습니다.
 
우선 이벤트가 사용할 대리자를 선언해 보았습니다. AfterEventHandler 의 경우 특별히 지정하지 않아도 되지만, 직관적으로 코딩이 나름대로 편하기 때문에 선언해 보았답니다.
public delegate CancelEventHandler BeforeEventHandler(object sender, CancelEventArgs e);
public delegate EventHandler AfterEventHandler(object sender, EventHandler e);
 
다음은 Sample 클래스의 모든 소스를 한번에 보도록 하겠습니다.
 
public class Sample
{
        private object EVENT_BEFORE_EVENT     = null; // Before 이벤트객체
        private object EVENT_AFTER_EVENT      = null; // After 이벤트객체
 
        // 이벤트의수가많아경우, 대부분의이벤트가사용되지않을것이라고예상될경우유용한방법이다.
        // 다음회차에자세히알아보자.
        public event CancelEventHandler BeforeEvent
        {
               add
               {
                       EVENT_BEFORE_EVENT     =
(CancelEventHandler)EVENT_BEFORE_EVENT + value;
               }
               remove
               {
                       EVENT_BEFORE_EVENT     =
(CancelEventHandler)EVENT_BEFORE_EVENT - value;
               }
        }
 
        public event EventHandler AfterEvent
        {
               add
               {
                       EVENT_AFTER_EVENT      = (EventHandler)EVENT_AFTER_EVENT + value;
               }
               remove
               {
                       EVENT_AFTER_EVENT      = (EventHandler)EVENT_AFTER_EVENT - value;
               }
        }
 
        // Before 이벤트를발생한다. private 메서드에주의
        private void OnBefore(object sender, CancelEventArgs e)
        {
               CancelEventHandler handler = (CancelEventHandler)EVENT_BEFORE_EVENT;
 
               if( handler != null )
                       handler(sender, e);
        }
 
        // After 이벤트를발생한다. private 메서드에주의
        private void OnAfter(object sender, EventArgs e)
        {
               EventHandler handler          = (EventHandler)EVENT_AFTER_EVENT;
 
               if( handler != null )
                       handler( sender, e);
        }
 
        // Before, After 이벤트를발생하여 Before 이벤트의 Cancel 여부를판단한다.
        public void OnFire(object sender)
        {
               CancelEventArgs args          = new CancelEventArgs();
               this.OnBefore(sender, args);
 
               if( args.Cancel ) return;
 
               this.OnAfter(sender, EventArgs.Empty);
        }
}
 
여기서 event 를 선언하는 코드가 약간 생소하네요.
 
public event CancelEventHandler BeforeEvent { add; remove; }
 
이 구문은 대부분의 이벤트가 사용하지 않을 것으로 예상될 경우 이벤트의 추가/삭제를 커스트마이징 할 수 있기 때문에 굉장히 효율적인 방법입니다. 여기에서 이벤트는
 
EVENT_BEFORE_EVENT
 
오브젝트(object) 를 통해 추가/삭제 되는 것을 알 수 있습니다. 즉, EVENT_BEFORE_EVENT 오브젝트는 이벤트의 키 값이라고 보셔도 무방할 것 같네요.
 
OnBefore(), OnAfter() 와 같이 실제로 이벤트를 발생하는 메서드는 이미 1,2 회차에서 자주 보셨을 거라 생각합니다. 실제로 가장 중요한 구문은 OnFire() 메서드 입니다. OnFire() 메서드는 참조 타입(클래스)인 CancelEventArgs 클래스의 인스턴스를 생성하여 OnBefore() 메서드에게 넘겨주고 있습니다. Before 이벤트의 실제 구현부가 실행되고 CancelEventArgs 의 Cancel 프로퍼티가 True 가 될 경우 조건문에 의해 더 이상 After 이벤트가 실행되지 않도록 return 이 된답니다.
 
소스 코드에 주석을 잘 보시면서 차근차근 생각하시면 이해가 되실 겁니다.
 
Default.aspx.cs
[그림2] default.aspx 실행결과
 
만약, Before 이벤트 중 이벤트의 진행을 취소하고 싶다면 Cancel 프로퍼티를 True 설정하는 것만으로 이벤트의 진행의 취소가 가능합니다.
 
[그림3] Cancel 프로퍼티를 True 로 설정한 실행결과

Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요


 
 
1,2 회 이벤트에 대해 잘 알아 보셨는지요. 이번 3회차 에서는 1,2회차에 비해 난이도가 월등히 높아지게 됩니다. 반드시 이벤트를 정복하고자 한다면 1,2 회차를 직접 코드로 작성해 보며 이벤트에 대한 감각을 익히세요.
 
취소 가능한 이벤트
아마도 윈폼을 조금이라도 해 보신 분이라면, FormClosing/FormClosed 이벤트를 보신적이 있을 것입니다. 이 이벤트 중 FormClosing 은 폼이 닫히기 전에 발생하는 이벤트로 FormClosed 이벤트가 발생하는 것을 방지할 수 도 있답니다. 바로 이것이 취소 가능한 이벤트 입니다.(제가 붙혀서 부르는 것임 +_+)
 
아래의 간략한 윈폼 소스를 보면 알 수 있을 것입니다.
 
[그림1] FormClosing/FormClosed 예제
 
FormClosing 과 FormClosed 는 폼이 닫힐 때 차례로 발생하는 것을 알 수 있습니다.
 
[그림2] FormClosing/FormClosed 이벤트
 
하지만, 만약 CancelEventArgs 에서 제공하는 Cancel 프로퍼티를 True 로 설정할 경우 FormClosing 이후 FormClosed 이벤트를 실행되지 않습니다.
 
[그림3] CancelEventArgs 의 Cancel 프로퍼티를 True 로 설정한 경우
 
위와 같이 Cancel 프로퍼티를 True 로 설정한 경우 더 이상 FormClosed 이벤트는 발생하지 않게 되며, 폼 또한 종료하게 되지 않습니다.
 
[그림4] FormClosed 가 발생하지 않는 예제
 
이렇게 특정 이벤트가 발생하기 위해 Before/After(전후) 의 처리가 필요한 이벤트의 경우 After 이벤트를 발생하지 않도록 함으로써 Before 이벤트의 활용도가 많아진다.
 
이러한 Before 이벤트는 여러 가지 용도로 사용할 수 있습니다.
l 하나의 이벤트 구현에 들어갈 내용을 Before/After 단계로 구분하여 작성할 수 있습니다.
l Before 이벤트에서 유효성 검사를 실시하여, 이벤트의 진행/취소 유무를 결정할 수 있습니다.
 
대략 위 두 가지 정도가 가장 많이 활용될 수 있을 것 같습니다.
 
실제로 여러 상용 컨트롤에서는 위와 같은 Before/After 와 같은 굉장히 방대한 이벤트를 제공해 줍니다. 하나의 이벤트로 처리될 것이 두 개의 Before/After 이벤트로 제어할 수 있게 되면 보다 직관적이고 구현을 분리하여 작성하는데 도움이 됩니다.
 
다음 회차에 이러한 취소 가능한 이벤트를 만들어 보도록 하겠습니다.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요



 
 
우리는 전편에서 이벤트의 간략한 소개와 예제를 통해 이벤트의 사용 방법을 익혀보았습니다. 이번편 부터는 이벤트를 활용할 수 있는 예제들을 중심으로 소개하려고 합니다. 아마 그동안 이벤트가 뭉게구름처럼 정확하게 머리 속에 그려지지 않았다면, 이번 예제들을 통해 확실히 이벤트의 개념에 대해 알 수 있을 것 같습니다.
 
 
유저컨트롤에서 페이지로 값 전달
1편에서 선언부와 구현부의 분리로 점차적으로 프로그램을 보다 융통성 있게 만들 수 있다고 하였습니다.  페이지로 값을 이벤트를 통해 전달하는 방법을 살펴보겠습니다.
 
우선 우리가 사용할 이벤트 인자를 넘길 ReceiveEventArgs 클래스를 만들 것입니다. 이 ReceiveEventArgs 는 이벤트가 발생할 때 이벤트가 전달하는 Argument(인자)를 전달받을 수 있기 위함입니다.
 
받을 인자는 간단한 string 값으로 하겠습니다.
 
ReceiveEventArgs.cs
public class ReceiveEventArgs : EventArgs
{
        public string Item { get; set; }
}
 
유저컨트롤을 만들기 전에 UserControlBase 를 만들 것입니다. 이 UserControlBase 에는 기본적인 델리게이트와 이벤트를 선언하고 이벤트를 발생시키는 메서드가 포함이 됩니다.
 
UserControlBase.cs


public delegate void ReceiveEventHandler(object sender, ReceiveEventArgs e);
 
public class UserControlBase : System.Web.UI.UserControl
{
        public event ReceiveEventHandler ReceiveEvent;
 
        protected void OnRecevieEvent(object sender, ReceiveEventArgs e)
        {
               if( ReceiveEvent != null )
                       ReceiveEvent( sender, e);
        }
}
 
복잡하게 생각할 것 없이, 1편에서 보던 간략한 예제를 클래스 별로 분리하였다고 보시면 됩니다.
 
그럼 이제 유저컨트롤을 만들어 보겠습니다. 유저컨트롤에는 RadioButtonList 를 두고 아이템을 선택 후 전달 버튼을 누르면 이벤트를 발생하는 부분이 포함될 것입니다.
 
WebUserControl1.ascx.cs
public partial class WebUserControl1 : UserControlBase
{
        protected void Page_Load(object sender, EventArgs e) { }
 
        protected void Button1_Click(object sender, EventArgs e)
        {
               OnRecevieEvent(sender,
new ReceiveEventArgs{ Item=RadioButtonList1.SelectedItem.Text });
        }
}
 
보시는 것과 같이 버튼을 클릭하였을 때, OnReceiveEvent 를 발생하는 메서드를 호출하여 RadioButtonList 에서 선택된 아이템의 텍스트를 인자 값으로 전달합니다.
 
이제 default.aspx.cs 를 만들어 보겠습니다. 여기의 코드도 무척 심플하네요.
 
Default.aspx.cs
public partial class _Default : System.Web.UI.Page
{
        protected void Page_Load(object sender, EventArgs e)
        {
               this.ucWebUserControl1.ReceiveEvent +=
new ReceiveEventHandler(ucWebUserControl1_ReceiveEvent);
        }
 
        void ucWebUserControl1_ReceiveEvent(object sender, ReceiveEventArgs e)
        {
               string msg     = string.Format("선택된아이템은 {0} 입니다",e.Item);
               lbl.Text       = msg;
        }
}
 
 
결과 화면을 보겠습니다.
[그림2] 실행화면
 
글 만으로 잘 이해가 안되시면 첨부 파일을 다운로드 하여 한번 살펴보시면 좋을 것 같습니다.
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요



실전 event 목차
 
 
이벤트란 무엇인가?
이벤트는 간단히 말하자면 특정 사건이 발생했음을 알리는데 사용됩니다. Page_Load 되었을때 Page_Load 이벤트가 발생할 것이고, 버튼을 클릭했을 경우 _Click 이벤트가 발생할 것입니다. 예를 들어, 내 여자친구의 생일날이 오면 생일 이벤트가 발생할 것이고, 12월 25일 크리스마스가 오면 크리스마스 이벤트가 발생할 것입니다.
 
점차적으로 프로그램의 복잡성이 증가함에 따라 이벤트를 굉장히 유용하게 사용될 수 있습니다. 대리자를 통해 메서드의 형식을 캡슐화할 수 있기 때문에, 선언부와 구현부를 따로 분리할 수 있기 때문입니다. 단지, 우리는 이벤트의 발생을 알리기만 하면 될 뿐이니까요.
 
 
이벤트 만들기
이벤트는 대리자(Delegate) 와 떨어질 수 없는 관계입니다. 바로 이벤트는 이 대리자를 통해 메서드의 구현을 실행시키게 되는 것이기 때문입니다. 하지만 이곳에서는 대리자에 대한 설명은 하지 않을 것입니다^^;
 
이벤트는 선언을 하는 것만으로 하나의 이벤트가 완성됩니다.
 
event EventHandler Event;
 
굉장히 간단하지 않습니까? 이벤트의 선언은 위와 같이
 
event 키워드 + 대리자 + 이벤트 이름
 
이렇게 3가지만 알고 있으면 이벤트가 완성됩니다.
 
참고 : EventHandler 대리자는 다음과 같이 정의 되었습니다.
public delegate void EventHandler(object sender, EventArgs e);
 
 
실전 기초 예제
그럼 어디서나 볼 수 있는 간단한 예제를 한번 만들어 보겠습니다.
 
우선 폼에 버튼을 하나 올려 놓았습니다.

[그림1] 웹폼에 버튼 컨트롤을 올림.
 
아래 소스는 Delegate 와 event 를 선언하고 이벤트를 발생하는 모든 과정을 구현한 것입니다. 별거 아니죠??
 
public partial class _Default : System.Web.UI.Page
{
        delegate void MyEventHandler();
        event MyEventHandler MyEvent;
 
        void OnMyEvent()
        {
               if( MyEvent != null )
                       MyEvent();
        }
 
        protected void Page_Load(object sender, EventArgs e)
        {
               this.MyEvent += new MyEventHandler(_Default_MyEvent);
        }
 
        protected void Button1_Click(object sender, EventArgs e)
        {
               OnMyEvent();
        }
 
        void _Default_MyEvent()
        {
               Response.Write("MyEvent 발생하였습니다");
        }
}
 
실행결과는 더욱 더 별거 아닙니다.
 
[그림2] 실행결과
 
만약 위 소스가 이해가 안되신다면, 절대로 다음 회차를 보지 마시고, 다음의 사이트를 통해 대리자와 이벤트에 대한 기초 문법을 더 익히시기 바랍니다.
 
소설 같은 자바
http://www.jabook.org/
 
훈스 닷넷
http://www.hoons.kr/Lectureview.aspx?key=Lecture&LECCATE_IDX=7&ref=1&lecture_idx=208
http://www.hoons.kr/Lectureview.aspx?key=Lecture&LECCATE_IDX=7&ref=1&lecture_idx=209
Posted by 땡초 POWERUMC
TAG c#, Event

댓글을 달아 주세요

이미 C# 3.0 에서 LINQ to Sql 란 말은 자주 들어 보았을 것이다. 많은 세미나 또는 블로그 포스트에서 LINQ to Sql 의 쿼리가 MSSQL 프로필러를 통해 실행되는 모습을 익히 보았을 것이다.
하지만 LINQ to Sql 을 이용하여 쿼리가 되는 것을 기록할 필요가 있다. 쿼리가 수행되는 시간, 쿼리 되는 빈도나 부하 등을 유지보수 하기 위해서 반드시 이 쿼리들이 로그에 기록되어야 한다.
 
그럼 오늘은 이 LINQ to Sql 이 어떻게 로그를 남기고 어떻게 로그에 기록하면 될지 알아보자.
 
먼저 콘솔 프로젝트를 만들어보자.
우선 LINQ to Sql 항목을 추가하고, [그림1] 과 같이 서버탐색기를 통해서 데이터베이스의 테이블을 끌어놓자. 여기서 사용하는 테이블은 UmcBlog 실제 웹서버 테이블을 끌어놓아 보았다 +_+
 
[
그림1] 서버탐색기를 통해 테이블을 추가함.
 
콘솔 프로젝트의 소스는 아래와 같이 무척 간단하다.
 
[
그림2] 콘솔 프로젝트 소스
 
그리고 데이터베이스의 DataContext 객체를 생성하면, 위와 같이 Log 프로퍼티가 제공될 것이다. Log 프로퍼티는 Stream 을 받을 수 있는 프로퍼티이다. Console.Out 을 통해 Console Output Stream 을 넣어보았다.
 
[
그림3] 콘솔 프로젝트 실행 결과
 
Console Output Stream 을 통해 LINQ to Sql 쿼리는 SQL Server 의 프로필러로 보내지는 쿼리를 콘솔에서도 확인할 수 있다.
 
그렇다면 이 Stream 을 텍스트 파일로 Log 를 남기는 것은 생각보다 간단하다.
아래는 웹 프로젝트로 만든 소스이다.
 
[
그림4] 웹 프로젝트 소스
 
[
그림5] 웹 프로젝트 실행 결과
 
[
그림6] Log.txt 에 LOG 가 남겨진다.
 
 
서버가 실행되는 위치에 StreamWriter 객체를 이용해 “Log.txt” 라는 텍스트 파일로 남길 수가 있다.
 
writer.Close()
 
라는 맨 마지막 줄의 빨간 밑줄이 보일것이다. 바로, Stream 을 반드시 닫아 주어야 한다. 그렇지 않으면 로그 파일을 열려있는 채로 프로세서가 잡아놓고 있을 테니 말이다.
UmcBlogDataContext 와 리플랙터로 DataContext 를 확인한 결과 Log 프로퍼티로 제공되는 Stream 은 닫혀지지 않기 때문에 반드시 Stream 을 닫아 주어야 한다.
 
그렇다면 개발자는 매번 이 Stream 을 생성하고 닫아주는 코드를 작성해야 하는 것일까? 그렇지 않다.
이 로그를 자동화 하는 방법이 있다.
 
[
그림7] UmcBlogDataContext 의 Base Class 지정하는 속성창
 
위와 같이 친절하게도 LINQ to Sql 디자이너는 생성된 DataContext 개체의 Base Class 를 지정할 수 있도록 되어 있다. 그럼 우리는 Base DataContext 가 될 수 있는 클래스를 이곳에서 상속받아 로그의 기록을 자동화 하도록 만들면 된다. 

음.. 다음에 기회가 된다면 DataContext 를 상속받아 로깅하는 간단한 예제를 만들어 보도록 하자. 텨텨텨 =3=3=3

Posted by 땡초 POWERUMC

댓글을 달아 주세요

LINQ QUIZ

.NET/C# 2007.11.05 00:44 |
LINQ QUIZ
 
간단한 퀴즈를 풀면서 LINQ 에 대한 지식을 테스트 합니다.
모든 샘플은 다음과 같은 네임스페이스가 선언되어 있습니다.
 
using System;
using System.Linq;
using System.Data.Linq;
using System.Xml.Linq;
using System.Collections;
 
그리고 다음과 같은 배열이 선언되어 있습니다.
 
string[] colors = { "green", "brown", "blue", "red" };
 
 
 
자!! 그럼 이제 도전해 보십시오.
 
 
Q1. 다음 표현의 결과는 무엇입니까?
 
colors.Max (c => c.Length)
 
(A) 5
(B) green
(C) brown
(D) Compile-time error
(E) Exception thrown
 
 
Q2. 다음 표현의 결과는 무엇입니까?
 
colors.OrderBy (c => c.Length).Single()
 
(A) 3
(B) red
(C) Compile-time error
(D) Exception thrown
 
 
Q3. 주어진 쿼리식을 보고 답하세요.:
 
var query =
 from c in colors
 where c.Length > 3
 orderby c.Length
 select c;
 
query 변수의 type 은 무엇입니까?
 
(A) int
(B) string
(C) IEnumerable<int>
(D) IEnumerable<string>
(E) IQueryable<int>
(F) IQueryable<string>
 
 
Q4. 다음의 출력 결과는 무엇입니까?
 
var query =
 from c in colors
 where c.Length == colors.Max (c => c.Length)
 select c;
 
foreach (var element in query)
Console.WriteLine (element);
 
colors 배열의 값은 { "green", "brown", "blue", "red" } 입니다.
 
(A) green followed by brown
(B) 5 followed by 5
(C) Compile-time error
(D) Exception is thrown
 
 
Q5. 위의 예제에서 Subquery 는 최대 몇번 실행합니까?
 
(A) 한번
(B) 두번
(C) 세번
(D) 네번
 
 
Q6. 다음 코드의 출력 결과는 무엇입니까?
 
var list = new List<string> (colors);
IEnumerable<string> query = list.Where (c => c.Length == 3);
list.Remove ("red");
 
Console.WriteLine (query.Count());
 
(A) 0
(B) 1
(C) 2
(D) Exception thrown
 
 
Q7. 다음 코드의 출력 결과는 무엇입니까?
 
string[ ] colors = { "green", "brown", "blue", "red" };
 
var query = colors.Where (c => c.Contains ("e"));
query     = query.Where (c => c.Contains ("n"));
 
Console.WriteLine (query.Count());
 
 
(A) 1
(B) 2
(C) 3
(D) 4
 
 
Q8. 다음 코드의 출력 결과는 무엇입니까?
 
string s = "e";
var query = colors.Where (c => c.Contains (s));
 
s         = "n"
query     = query.Where (c => c.Contains (s));
 
Console.WriteLine (query.Count());
 
(A) 1
(B) 2
(C) 3
(D) 4
 
 
Q9. 다음의 쿼리에서, 컴파일러는 쿼리 문법을 어떻게 해석합니까?
 
from c in colors
let middle = c.Substring (1, c.Length - 2)
where middle.Contains ("e")
select middle;
 
(A)  Enumerable.Let을 호출하는 것으로 번역이 된다.
(B)  where, select 구문에서 middle은 c.Substring (1, c.Length - 1)으로 확장된다.
(C) 임시로 익명 타입으로 해석한다.
 
 
Q10. 컴파일러가 여러 생성자를 포함하여 쿼리를 번역하려면…?
 
(A) Multiple Selects
(B) SelectMany
(C) Join
 
 
Q11. 다음 보기 중, LINQ 에서 같이 사용할 수 있는 JOIN 절을 고르시오(복수선택 가능)
 
I.    Inner joins
II.   Left outer joins
III. Full outer joins
IV. Non-equi inner joins
V.   Non-equi outer joins
 
 
Q12. LINQ 의 JOIN 절은 어떤 SQL 구문의 JOIN 을 사용할 수 있습니까?(복수선택가능)
 
I.    Inner joins
II.   Left outer joins
III. Full outer joins
IV. Non-equi inner joins
V.   Non-equi outer joins
 
 
Q13. LINQ to SQL 쿼리는 자신의 로컬 메서드를 호출할 수 있습니까?
 
A. Where 절에서만
B. In the final projection only
C. 쿼리의 어떤곳에서도 가능하다
D. 불가능하다
 
 
Q14. In LINQ to SQL, to request that an entity’s association properties be populated along with the entity (in a single round trip), you would:
 
(A) set DelayLoaded to false in the Association attribute
(B) set EagerLoad to true in the Association attribute
(C) use a DataLoadOptions object, and call AssociateWith
(D) use a DataLoadOptions object, and call LoadWith
 
 
Q15. 다음 코드의 출력 결과는 무엇입니까?
 
var city = new XElement ("city", "Seattle");
 
var customer1 = new XElement ("customer", city);
var customer2 = new XElement ("customer", city);
 
city.SetValue ("London");
Console.WriteLine (customer2.Element ("city").Value);
 
(A) Seattle
(B) London
(C) An exception is thrown
 
 
Q16. 다음 코드에서 element 이름 안의 네임스페이스는 무엇입나까?
 
XNamespace ns = "http://albahari.com/linqquiz";
 
var x =
 new XElement (ns + "customer",
    new XElement ("name", "Bloggs")
);
 
(A) "" (empty string)
(B) "http://albahari.com/linqquiz"
(C) "name"
 
 
Q17. XElement 또는 XDocument 클래스를 사용하여 파일로 XML 을 저장할 때, XML 선언문은 출력파일에 포함이 된다.
 
(A) 항상 포함된다.
(B) 전혀 포함되지 않는다.
(C) XDeclaration 객체를 사용할 경우에만
 
 
 
모든 문제를 푸셨나요? 의외로 긴가민가 한 부분이 많지요? ㅋ
그럼 채점에 들어가겠습니다.
해설까지 번역하려고 하였으나, 짧은 문장력으로 너무 힘드네요~
아래의 주소에 자세한 설명이 나와있으니 한번씩 둘러보시기 바랍니다^^
( Q14 번 번역 가능하신 분 구합니다 ㅡㅡㅋ )
 
 
 
답안
 
Q1. A
Q2. D
Q3. D
Q4. C
Q5. D
Q6. A
Q7. A
Q8. B
Q9. C
Q10. B
Q11. I and IV
      II and V
Q12. I
      II
Q13. B
Q14. D
Q15. A
Q16. A
Q17. A

Posted by 땡초 POWERUMC
TAG LINQ

댓글을 달아 주세요

LINQ TO Sql 은 데이터베이스를 엑세스 하고 쿼리하는데 데이터베이스와 어플케이션간에 많은 장벽을 없애버렸다. 바로 어플케이션에서 쉽게 SQL 쿼리를 사용할 수 있기 때문이다.
하지만, 이와 같이 장벽이 사라진 만큼 데이터베이스와 어플케이션의 더욱 더 깊은 이해가 요구 되어야 더욱 더 섬세하고 좋은 성능을 낼 수가 있을 것이다.
 
 
일반적인 데이터엑세스 과정
 
 
대략 위와 같은 논리적인(또는 물리적인) 3 Layer 형태가 갖추어 질 것이다.(위 그림 정말 못그렸다.;;)
그렇다면 LINQ TO Sql 의 사용은 Data Access Layer 가 가장 적절할 것 같다.
LINQ TO Sql 클래스를 만들어, 비쥬얼하게 멋진 다이어그램과 사용할 프로시져를 끌어다 놓는 것 만으로 SqlParameter 를 만드는 귀찮은 작업은 생략될 수 있을 것이다.
 
그렇다면 왜 Data Access 에서만 LINQ TO Sql 을 사용해야 할까
 
이유는 간단하다. 바로 디스어셈블러 때문이다.
데이터베이스의 ConnectionString 은 자동으로 app.config 에 기어 들어간다고 쳐도, 데이터베이스의 테이블 스키마와 관계 등이 그대로 노출 될 수 있다.

다음은 LINQ TO Sql 이 만들어 주는 Entity 클래스의 일부이다.
 
 
아래는 LINQ 쿼리가 어떻게 디스어셈블리 되는지 보여준다.
 
var joinList = from c in db.Comments
              group c by c.ArticleNo into g_c
              join a in db.Articles on g_c.Key equals a.ArticleNo
              orderby g_c.Key ascending
              select new
              {
                        g_c.Key,
                        a.Title,
                        CommentCount = g_c.Count()
              };
var joinList = db.Comments.GroupBy<Comment, int>(Expression.Lambda<Func<Comment, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(Comment), "c"), (MethodInfo) methodof(Comment.get_ArticleNo)), new ParameterExpression[] { CS$0$0000 })).Join(db.Articles, Expression.Lambda<Func<IGrouping<int, Comment>, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(IGrouping<int, Comment>), "g_c"), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), new ParameterExpression[] { CS$0$0000 }), Expression.Lambda<Func<Article, int>>(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(Article), "a"), (MethodInfo) methodof(Article.get_ArticleNo)), new ParameterExpression[] { CS$0$0000 }), Expression.Lambda(Expression.New((ConstructorInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>..ctor, <>f__AnonymousType0<IGrouping<int, Comment>, Article>), new Expression[] { CS$0$0000 = Expression.Parameter(typeof(IGrouping<int, Comment>), "g_c"), CS$0$0002 = Expression.Parameter(typeof(Article), "a") }, new MethodInfo[] { (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_a, <>f__AnonymousType0<IGrouping<int, Comment>, Article>) }), new ParameterExpression[] { CS$0$0000, CS$0$0002 })).OrderBy(Expression.Lambda(Expression.Property(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>), "<>h__TransparentIdentifier0"), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), new ParameterExpression[] { CS$0$0000 })).Select(Expression.Lambda(Expression.New((ConstructorInfo) methodof(<>f__AnonymousType1<int, string, int>..ctor, <>f__AnonymousType1<int, string, int>), new Expression[] { Expression.Property(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>), "<>h__TransparentIdentifier0"), (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(IGrouping<int, Comment>.get_Key, IGrouping<int, Comment>)), Expression.Property(Expression.Property(CS$0$0000, (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_a, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)), (MethodInfo) methodof(Article.get_Title)), Expression.Call(null, (MethodInfo) methodof(Enumerable.Count), new Expression[] { Expression.Property(CS$0$0000, (MethodInfo) methodof(<>f__AnonymousType0<IGrouping<int, Comment>, Article>.get_g_c, <>f__AnonymousType0<IGrouping<int, Comment>, Article>)) }) }, new MethodInfo[] { (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_Key, <>f__AnonymousType1<int, string, int>), (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_Title, <>f__AnonymousType1<int, string, int>), (MethodInfo) methodof(<>f__AnonymousType1<int, string, int>.get_CommentCount, <>f__AnonymousType1<int, string, int>) }), new ParameterExpression[] { CS$0$0000 }));
 
일일이 탭도 맞추고 정렬한 후에야 비로소 볼만 하겠지만, 조막만한 쿼리가 어쨌든 알아보긴 힘들지만 역어셈블리가 되었다.
그렇다면 충분히 어셈블리의 IL 코드를 조작하여, 원하는 데이터를 리턴시키기엔 충분할 것이다. (쉽지 않겠지만^^;)
 
위의 무지막지한 디스어셈브리 코드를 보면, LINQ TO Sql 의 성능마저 궁금하게 만든다.
하지만, 성능 테스트는 다음으로 슬쩍 미루도록 해야겠다. (음하하;;)
왜냐하면 난 소중하니까 ^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요

우리는 C# 3.0 에 확장 메서드(Extension Methods) 가 언어적으로 지원된다는 말은 수없이도 들어보았다. 확장 메서드는 기존 C# 2.0 에 비해 굉장히 파격적이다. 추후 확장 프로퍼티와 확장 이벤트 등도 지원된다고 하니, 가히 언어적으로 파격적이다.
 
 
확장 메서드의 문제점
 
확장 메서드는 굉장히 파격적이다. 내가 C# 2.0 을 하면서 감히 이런 언어적 지원이 가능하리라곤 상상도 못했으니 그 아이디어 적이 면만으로도 충분히 놀랄만 하다.
확장 메서드는 원본 타입의 Type 에 따라 지원된다.
하지만, 자주 사용되는 string, int, bool 등과 같은 타입에 확장 메서드를 추가하게 되고, 프레임웍이 커짐에 따라 이런 확장 메서드의 양도 무한대(?)로 커질 가능성이 충분히 있다.
 
확장 메서드는 기본적으로 자신이 속한 namespace 에 한해 Intelisense 에 표시된다. 즉, 다른 namespace 의 확장 메서드를 사용하고 싶다면 using 문을 추가하면 된다.
바로, 여기에서 문제점이 도출된다.
기존 Utility 성격의 메서드나 해당 namespace 에 존재하는 많은 메서드들이 확장 메서드로 마이그레이션이 가능하다는 것이다.
C# 에서는 수많은 namespace 가 존재하고, 수많은 클래스가 존재한다. 만약, using 문을 추가해 다른 namespace 의 확장메서드를 사용하려 했으나, 중복되는 경우도 생길것이며, 구현하려는 기능이 일반 static 메서드였는지, 확장 메서드였는지 조차 기억하기엔 너무나도 번거로운 작업이 될 것이다.
 
일반적인 확정 메서드가 구현되는 상황을 보도록 하자.
 
class Program
    {
        static void Main(string[] args)
        {
                     string encrypt = "AAA".Encrypt();
 
                     Console.WriteLine(encrypt);
        }
    }
 
static class MyExtender
{
           public static string Encrypt(this string arg)
           {
                     return arg + "는 암호화 됨.(MyExtender)";
           }
}
 
Encrypt 메서드는 특정 문자열을 암호화 하는 확장 메서드이다. (암호화 코드가 있다는 가정임)
 
즉, 해당 클래스가 속해있는 namespace 에 한해 확장 메서드를 구현하고, 만약 다른 namespace 의 확장 메서드가 필요하게 되면 using 네임스페이스; 구문을 추가하여 다른 namespace 영역의 확장 메서드를 끌어다 쓰면 되는 것이다.
 
만약, using 문이 추가되면 될수록 우리가 기억 해야할 확장 메서드의 양은 얼마만큼 많아 지게 될지 알 수 없다.
 
수많은 고민을 통해 “모든 확장 메서드는 하나의 namespace 안에서 관리하자” 라는 생각을 해보았다. 즉, 확장 메서드가 필요하면 using 구문 하나만으로 모든 확장 메서드를 호출할 수 있도록 하는 구성이다.
하지만, 여기에 결정적인 단점이 있다. 이 부분은 뒷부분에 다시 설명 하도록 하겠다.
 
 
 
확장 메서드(Extension Methods) 설계
 
 
위 그림과 같이 확장 메서드를 지원하는 별도의 폴더와 namespace 를 만들었다.
이 namespace 는 솔루션 또는 프레임웍이 사용하는 모든 확장 메서드를 한군데에 모아 놓기 위한 namespace 이다.
 
즉,
 
using Umc.Core.Extender;
 
구문 하나만으로 모든 확장 메서드를 활용할 수 있다.
 
그럼 SecurityExtender.cs 파일과 TextExtender.cs 파일을 연속으로 보도록 하자.
본 소스는 예제용으로 제공되는 소스이니 크게 비중을 두지 말고 보도록 하자.
 
namespace Umc.Core.Extender
{
           public static partial class SecurityExtender
           {
                     public static Security.Security Security(this string arg)
                     {
                                return new Umc.Core.Security.Security(arg);
                     }
           }
}
 
namespace Umc.Core.Extender
{
           public static partial class TextExtender
           {
                     public static Umc.Core.Text.Text Text(this string arg)
                     {
                                return new Umc.Core.Text.Text(arg);
                     }
           }
}
 
위 두 클래스는 동일한 namespace 안에 존재하게 되며, 특히 partial 클래스로 선언되어 언제 어디서든 동일한 namespace 와 class 명을 갖게 된다면 확장 및 기능 추가가 가능하도록 했다.
 
아직 리턴 타입의 클래스 구현을 확인하진 못했지만, 어느정도 객체지향 프로그램을 해 본 사람이라면 일찌감치 감을 잡았을 것이라고 생각한다.
 
위 두 클래스가 지원하는 확장 메서드의 원본 타입은 string 이 되며, 해당 확장 메서드의 리턴 타입은 또 다른 인스턴트형의 클래스이다.
 
위처럼 구현한 이유는 확장 메서드를 쩜(.)을 찍고 구현할 수 있도록 소위 Depth(깊이)를 준 것이다. 여기에서 말하는 소위 Depth 라고 함은 다음의 그림을 보면 쉽게 이해가 갈 것이다.
 

 
위와 같이 해당 확장 메서드에서 쩜(.) 을 누르는 순간 새로운 메서드들이 Intelisense 에 나타나게 된다.
 
 
개발자 입장에서는 이렇게 직감적으로 코딩을 가능하게 하는 것도 “사용자 경험(UX)” 라고 말하고 싶다.
 
이렇게 설계가 가능하다면, 다음과 같이 최소한의 확장 메서드로 다양한 기능을 추가할 수 있다는 것이다.
 
그럼, Security 와 Text 클래스가 어떻게 구현되었는지 연속적으로 보도록 하자.
 
namespace Umc.Core.Security
{
           public class Security
           {
                     // SourceString 이 private 이므로 Object Initializer 를 사용할 수 없다.
                     public Security(string str)
                     {
                                this.SourceString = str;
                     }
 
                     // 암호화할 문자열
                     private string SourceString { get; set; }
 
                     public string Encrypt()
                     {
                                // 암호화 알고리즘이라고 가정.
                                return SourceString + "는 암호화 됨.";
                     }
 
                     public string Decrypt()
                     {
                                // 복호화 알고리즘이라고 가정.
                                return SourceString + "는 복호화 됨.";
                     }
           }
}
 
namespace Umc.Core.Text
{
           public class Text
           {
                     public Text(string str)
                     {
                                this.SourceString = str;
                     }
 
                     private string SourceString { get; set; }
 
                     public string Left(int len)
                     {
                                return SourceString.Substring(0, len);
                     }
 
                     public string Right(int len)
                     {
                                return SourceString.Substring(SourceString.Length - len , len);
                     }
 
                     // 기존 확장메서드 방식으로 인스턴스를 리턴하여 호출할 방법이 없음.
                     public static string Test()
                     {
                                return string.Empty;
                     }
           }
}
 
 
 
특히나 interface 를 통한 확장 메서드의 구현은 그 진가를 십분 발휘하게 될 것임이 분명하다.
그렇지만, 이러한 편리한 설계임에도 불구하고 단점이 존재하게 된다.
 
 
확장 메서드 설계의 문제점
 
바로 static 메서드로 구현된 메서드는 호출할 뾰족한 방법이 없다.
위의 확장 메서드의 설계 에서 보듯이, 구현된 클래스는 반드시 인스턴스를 생성하여 사용할 수 있도록 구성되었다.
 
아래와 같이
 
 
처럼 static 으로 구현된 메서드는 인스턴스를 생성하여도 호출할 방법이 없다.
우리가 설계한 확장 메서드는 생성자에 인자값을 전달하는 형식이지만, 더군다나 static 메서드는 생성자에 인자값을 전달한다고 하더라도, 생성자가 전달한 인자값을 활용할 방법이 없다는 것이다. 아마도 이 부분은 확장 프로퍼티라는 개념이 C# 3.0 정식 버젼에서 지원하느냐, 어떻게 지원하느냐가 관건이 될 것 같다.
 
 
마치며
 
C# 3.0 의 Lambda, LINQ, Extension Methods 는 새로 등장하는 시스템의 설계에 엄청난 파장을 불러 일으킬만한 이슈이다. 아직 완벽하게 완성된 C# 3.0 은 아니지만, 분명한 것은 수많은 Language(언어) 들의 표본이 C# 3.0 이 될 것이 분명하다.(이렇게 말하니 무슨 홍보대사 같네 ㅋ)
정식 C# 3.0 의 모습을 기대하면서 이만 ^^//
Posted by 땡초 POWERUMC

댓글을 달아 주세요

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]
 
 
 
지난 아티클에서 우리는 List<> 컬렉션에 특정 요소를 탐색하기 위해 delegate 와, 익명 메서드, 람다식을 이용한 방법을 알아보았다.
 
List<> 컬렉션의 Find와 FindAll 메서드는 List<> 클래스가 제공하는 메서드지만, 이와 비슷한 확장 메서드를 손수 구현해 보도록 할 것이다.
 
 
이번에도 다음의 List<> 컬렉션의 데이터를 가지고 예제를 만들어 볼 것이다.
 
List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);
 
지난 강좌에서 보듯 다음과 같이 List<> 클래스의 Find 메서드를 이용하여 특정 요소를 탐색한다.
 
int result = arr.Find(o => o == 2);
Console.WriteLine(result);
 
 
 
List<Int>.Find 메서드를 확장 메서드로 구현
 
확장 메서드를 구현하기 위해 MyExtension 이라는 클래스를 만들고 UmcFind 라는 메서드를 만들자.
 
static class MyExtension
{
           public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
           {
                     List<int> ret = new List<int>();
                     foreach (var v in arr)
                     {
                                if (p(v))
                                          return v;
                     }
 
                     return 0;
           }
}
 
위 확장 메서드는 다음과 같이 호출이 가능하다.
 
int result = arr.UmcFind(o => o == 2);
Console.WriteLine(result);
 
 
Public static int UmcFind(this IEnumerable<int> arr, Predicate<int> p)
 
의 선언을 보자.
 
인자값에 오는 this 는 확장메서드를 지원할 원본 객체의 타입이다. 즉, 우리는 List<int> 에 대한 객체에만 확장 메서드를 지원하게 된다.
 
Predicate<Int> p
 
는 .NET Framework 에서 선언된 bool 을 리턴하는 델리게이트이다.
 
즉, o => o == 2 람다식의 “ o == 2 “ 의 구현부는 Predicate 델리게이트에게 위임된 것이다.
때문에,
 
if( p( v ) )
 
와 같이 Predicate 의 리턴이 bool 이므로 람다식의 조건을 True/False 로 리턴 받을 수 있다.
 
 
 
List<Int>.FindAll 메서드를 확장 메서드로 구현
 
아래와 같이 사용되는 List<int>.FindAll 메서드를 확장 메서드로 구현해 보자.
 
List<int> result = arr.FindAll(o => o >= 2 && o <= 3);
result.ForEach(o => Console.WriteLine(o));
 
 
이번에는 .NET Framework 가 제공하는 델리게이트를 사용하지 않고, 직접 델리게이트를 선언해 볼 것이다.
 
delegate TResult FindAllHandler<T, TResult>(T t);
 
FindAll 확장 메서드를 구현하기 위해 제네릭 델리게이트를 선언해 보았다.
첫번째 T 는 받을 인자 타입이고, 두번째 TResult 는 리턴 타입이 된다.
 
그럼 UmcFindAll 이라는 확장 메서드를 만들어 보자.
 
static class MyExtension
{
           public static List<int> UmcFindAll(
            this IEnumerable<int> arr, FindAllHandler<int,bool> p)
           {
                     List<int> result = new List<int>();
 
                     foreach (var v in arr)
                     {
                                if( p(v) )
                                          result.Add( v );
                     }
                     return result;
           }
}
 
위의 확장 메서드는 다음 처럼 사용이 가능하다.
 
List<int> list = arr.UmcFindAll(o => o >= 2 && o <= 3);
list.ForEach(o => Console.WriteLine(o));
 
 
 
보시는 바와 같이 List<> 클래스가 제공하는 FindAll 메서드와 동일한 기능을 하는 확장 메서드를 만들어보았다.
 
UmcFindAll 확장 메서드는 제네릭 델리게이트를 선언하여 예제를 만들어 보았지만, UmcFind 확장메서드를 유심히 본 독자라면 보는데 큰 어려움이 없을 것 같다.
 
 
이것으로. 람다식의 간략한 이용 방법과 람다식을 사용하는 확장 메서드에 대해서 알아 보았다.
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 상준 2011.03.24 14:17 Address Modify/Delete Reply

    글 너무 너무 잘봤습니다.
    하지만 이해하기 너무 힘드네요...
    실례지만 해당 소스코드도 있었으면..
    더좋은 글이 되지 안을까 생각합니다..

[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [1]
[.NET/C# 3.0] - 람다식(Lambda Expressions) 을 이용한 확장 메서드(Extension Methods) 만들기 [2]



C# 3.0 에서 LINQ 를 위해 많은 언어적 개념이 도입되었다.
확장 메서드(Extension Methods)와 Lambda Expression 등이 바로 그것이다.
그중 Lambda Expression(이하 람다식) 은 Ruby 에서 먼저 나왔다고 하지만, C# 3.0 에서의 람다식은 LINQ 와 확장 메서드와 굉장히 사슬처럼 엮여 있는 듯한 모습이다.
 
람다식은 대리자(Delegate)와 제네릭 메서드의 복잡한 식을 간결하게 줄여줄 수 있다.
 
=>
 
연산자를 이용하여, 익명 메서드(Anonymous Method) 의 여러줄의 코드를 단 한줄의 코드로 줄여줄 수 가 있다.
 
좀더 자세한 설명히 필요하다면 다음을 참고 하도록 하자.
http://msdn.microsoft.com/msdnmag/issues/07/09/BasicInstincts/Default.aspx?loc=ko
 
 
우리가 사용할 예제는 다음의 List<int> 컬렉션을 사용할 것이다.
 

List<int> arr = new List<int>();
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);

 
 
List<> 컬렉션은 Find 라는 메서드를 제공한다. ( 확장 메서드가 아님 )
이 메서드는 컬렉션의 특정 요소를 찾기 위해 사용되어 지는데, C# 이 제공하는 Predicate<> 델리게이트를 통해 True/False 를 반환하는 델리게이트이다.
 
그럼 List<> 컬렉션의 특정 요소를 찾기 위해 다음과 같이 사용된다.
 

static void Main(string[] args)
{
           List<int> arr = new List<int>();
           arr.Add(1);
           arr.Add(2);
           arr.Add(3);
           arr.Add(4);
 
           Predicate<int> p = new Predicate<int>(Compare);
           int result = arr.Find(p);
           Console.WriteLine(result);
}
static bool Compare(int i)
{
           return i == 2;
}

 
 
Predicate<int> p = new Predicate<int>(Compare);
 
Predicate<> 대리자는 Compare 메서드에게 일거리를 할당한 것을 볼 수 있다.
 
그럼, 가상 메서드를 이용하여 코드양을 줄여 보도록 하자.
 

int result = arr.Find(new Predicate<int>(
                                delegate(int i)
                                {
                                          return i == 2;
                                }
                     ));
Console.WriteLine(result);

 
 
위와 같이 당연히 결과는 “2” 가 나타났다.
델리게이트를 통해 i==2 라는 요소를 찾기위해 int 인자와 bool 을 리턴하는 메서드를 만들어야 하지만, 위의 delegate 키워드의 구문은 가상 메서드를 만들게 됨으로써 코드양이 많이 줄어 들게 되었다.
 
하지만, 람다식을 이용하여 더 줄여보도록 하자.
 

Console.WriteLine(arr.Find(o => o == 2));

 
 
위 7개 라인을 단 한줄로 해결하였다.

 
사실 이 구문이 선듯 이해가 가지 않는 필자는 무진장 머리를 굴려보았다.
잘 이해를 하기 위해 람다식을 마치 메서드가 있는 구문이라고 머리속으로 상상해보기 바란다.
 
 
 
=> 연산자를 기준으로 왼쪽은 넘길 인자값이 되는 것이고, 오른쪽은 내용이 되는 것이다.
 
그래도 잘 이해가 안된다면 마치 메서드가 존재한다는 상상 훈련을 해보면 그리 오래 걸리지 않고 쉽게 이해가 될 것 같다.
다음 2편에서는 제네릭 대리자를 통해 람다식이 가능한 확장 메서드를 만들어 보도록 하자
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 지송 2010.06.17 12:57 Address Modify/Delete Reply

    람다식 찾다왔는데 역시 이해가 쉽게 써놓으셨네요.

    좋은 하루 되세요 !

LINQ 의 OUTER JOIN 작업

.NET/C# 2007.09.04 00:24 |
LINQ 를 이용한 OUTER JOIN 을 해보자.
이번 강좌를 지난 강좌에 이어 LINQ to SQL Classes 항목을 추가 하여야 한다.
이부분에 대해서는 다음의 URL 을 참고하기 바란다.
 
[.NET/C# 3.0] - LINQ to SQL Classes 와 LINQ의 JOIN 작업
http://umc.pe.kr/article/2007/09/02/LINQ-to-SQL-Classes-AND-LINQ-JOIN.aspx
 
 
우선 우리가 원하는 SQL 쿼리식을 보자
 
SELECT A.ArticleNo, C.Content, C.InsertDate
FROM Article A
LEFT OUTER JOIN Comment C ON C.ArticleNo = A.ArticleNo
ORDER BY C.InsertDate DESC
 
아주 간단한 OUTER JOIN 의 예이다.
이 쿼리의 결과는 다음과 같다.
 
 
위 그림은 보시다시피 결과 데이터의 중하단 쯤에 나온 결과이다.
쿼리를 보듯 NULL 값이 주루룩 있다. 당연히 INNER JOIN 되지 않은 데이터는 NULL 값이 표시될 것이다.
 
 
그럼 LINQ 의 확장 메서드를 통한 OUTER JOIN 을 해보자.
 
var joinList = db.Articles.GroupJoin(
             db.Comments,
             _article => _article.ArticleNo,
             _comment => _comment.ArticleNo,
             (_article, c) => c.DefaultIfEmpty().Select(
                    _comment => new
                    {
                           ArticleNo = _article == null ? -1 : _article.ArticleNo,
                           CommentContent = _comment.Content ?? string.Empty,
                           InsertDate = _comment.InsertDate.ToString() ?? string.Empty
                    }
             )
             ).SelectMany(obj => obj)
             .OrderByDescending(o => o.InsertDate);
 
 
위 식을 INNER JOIN 식과 비교해 보았을 때, 좀 더 부가적인 작업이 필요하다.
 
c.DefaultIfEmpty()
 
을 통해 JOIN 가능한 키가 없을 경우에 따로 처리를 해주어야 한다.
 
또한, 키가 없을 경우의 데이터 또한 nullable 의 ?? operator 를 통해 null 데이터에 대한 경우에 대한 작업을 해야한다.
 
SelectMany(obj => obj)
 
확장 메서드를 통해 반복이 가능한 순환자(열거자) 에 대한 키(Key)값을 모두 반환한다.
 
아무리 람다식을 이용해 코딩양을 줄였다고 하더라고, 사실 벅찬 감이 무척 많다..
두개의 테이블이지만, 3-4 개의 테이블이라고 생각하면, 아마도 SQL 쿼리를 이용하는 것이 나을 것이다.
 
 
그렇지만 다음의 LINQ 식을 이용한 쿼리를 보도록 하자.
 
var joinList = from a in db.Articles
             join c in db.Comments on a.ArticleNo equals c.ArticleNo into _c
             from c in _c.DefaultIfEmpty()
             orderby c.InsertDate descending
             select new
             {
                       ArticleNo = a.ArticleNo,
                       CommentContent = c.Content,
                       InsertDate = c.InsertDate.ToString()
             };
 
개체를 이용한 쿼리식에 비해 무척이나 SQL 쿼리와 가까워 졌다.
 
join c in db.Comments on a.ArticleNo equals c.ArticleNo into _c
from c in _c.DefaultIfEmpty()
 
를 보면, into 키워드를 통해 join 데이터를 _c 컬렉션에 담고
다시 _c.DefaultIfEmpty() 를 통해 null 데이터를 처리하고 있다.
 
into 키워드는 LINQ 의 출연과 함께, 쿼리식 뿐만 아니라 통계 쿼리에도 자주 등장하게 될 키워드이다.
 
그럼, 다음 아티클을 스스로 기대하며 이만^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요

LINQ 의 JOIN
 
기존 C# 2.0 의 Typed DataSet 의 개념과 LINQ 가 결합하여 LINQ to SQL Classes 라는
이름으로 굉장히 강력한 기능을 제공한다.
 
LINQ, 람다식, 익명 형식에 대한 내용은 다음의 URL 을 참고 하세요.
 
2009-06-20 아래의 경로로 접속할 수 없습니다.
LINQ의 발전과 C# 설계에 미치는 영향

 
LINQ to SQL Classes 항목 만들기
 


[새 항목 만들기] 에서 LINQ to SQL Classes 를 만든다.
그럼 .dbml 확장자를 가진 몇가지 파일과 디자이너 화면이 나타나게 된다.
 
여기서 사용하게 될 데이터베이스는 UmcBlog 소스와 함께 공개된 데이터베이스를 사용할 것이다.
 
 
Article 테이블은 블로그의 아티클을 저장할 테이블이다.
Comment 테이블은 아티클에 대한 댓글을 저장하는 테이블이다.


 

디자이너는 두 테이블간 관계가 맺어져 있다면, 이 두 테이블 간의 RULE 과 Key 등이
자동으로 매핑되는 것을 볼 수 있다.
 
 
 
DataBase 쿼리와 LINQ 를 이용한 JOIN 작업
 
SELECT A.ArticleNo, C.Content, C.InsertDate
FROM Article A
INNER JOIN Comment C ON A.ArticleNo=C.ArticleNo
Article, Comment 테이블의 JOIN 쿼리
 
간단하게 JOIN 하는 쿼리를 작성해 보았다.
결과는 다음과 같을 것이다.
 
 
Comment 테이블의 댓글의 ArticleNo 를 Article 테이블과 조인하여 나온 결과이다.



그렇다면 LINQ 를 이용한 JOIN 작업을 보자.

위에서 LINQ to SQL Classes 를 다음과 같이 객체를 생성한다.

 
UmcBlogDataContext db = new UmcBlogDataContext();
 
 
// INNER JOIN
var joinList = db.Articles.Join(db.Comments,
                           _article => _article.ArticleNo,
                           _comment => _comment.ArticleNo,
                           (a, c) => new
                           {
                               ArticleNo             = a.ArticleNo,
                               CommentContent = c.Content,
                               InsertDate            = c.InsertDate
                           }
                    ).OrderByDescending( o => o.InsertDate );
 
SQL 쿼리와 비교해 볼 때 다소 복잡해 보이는 감이 없지 않다.
C# 2.0 의 익명 메서드를 이용와 비교해볼 때 위와 같이 람다식 을 이용하여 그나마 짧게
작성한 코드이다.

 
물론 위와 같이 불편하게 쿼리를 작성하지 않아도 된다.
LINQ 의 쿼리식을 이용하여 마치 SQL 쿼리를 작성하듯 쿼리를 완성할 수 있다.

 
LINQ 식을 이용한 JOIN
 
var joinList = from a in db.Articles
                      from c in db.Comments
                      where a.ArticleNo == c.ArticleNo
      orderby c.InsertDate descending
                      select new {
                              ArticleNo = a.ArticleNo,
                              CommentContent = c.Content,
                              InsertDate            = c.InsertDate
                       };
 
위의 예제를 보면 from 절이 두개가 오는 것을 알 수 있다.
마치 PL/SQL 의 from Article A, Comment C 와 같이 사용하듯 하다.
 
물론 ANSI SQL 과 같은 방법으로 JOIN 이 가능하다.
그럼 아래의 구문을 보자.
 
// INNER JOIN 3
var joinList = from a in db.Articles
                       join c in db.Comments on a.ArticleNo equals c.ArticleNo
                      orderby c.InsertDate descending
                      select new {
                              ArticleNo            = a.ArticleNo,
                              CommentContent       = c.Content,
                              InsertDate           = c.InsertDate
                       };
 
위와 같이 join 문과 함께 on 뒤에 두개의 테이블을 조인을 하게 된다.
On 과 함께 ==(Equals) 을 사용할 수 없기 때문에 “equals” 을 통해 두 테이블을 조인하게 된다.
 
 
위의 예제를 보듯, 객체를 이용한 쿼리와 SQL 쿼리식과 같은 쿼리가 모두 가능하다는 것을
알 수 있다.
하지만, outer join 은 기존 방법과는 약간 다른 방법으로 조인을 하게된다.
다음시간에 LINQ 의 outer join 을 알아보도록 하자.
Posted by 땡초 POWERUMC

댓글을 달아 주세요

  1. 엔돌슨 2012.09.04 17:23 신고 Address Modify/Delete Reply

    궁금한것이 있습니다.
    join c in db.Comments on a.ArticleNo equals c.ArticleNo
    이렇게 조인되면 키가 1개만 매치되는 데 2건이상이면 어떻게 해야하나요?

    조인을 할때 조인되는 키가 여러개의 경우 어떻게 처리해야 하나요?

    • 땡초 POWERUMC 2012.09.06 00:40 신고 Address Modify/Delete

      JOIN 구문을 이렇게 사용하셔도 됩니다.
      http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/a8bf3932-611f-44d2-b4d8-6cac83b30406/

      또는 where 절을 이용하셔도 됩니다.
      from n1 in new[] { "Tom", "Dick", "Harry" }
      from n2 in new[] { "A", "B", "Tom", "Dick" }
      where n1 == n2 && n1.Length >= 3
      select n2

XDocument 클래스
 
Framework 3.0 에 들어서면서 새로이 등장한 클래스이다.
그 이전 Framework 1.1, 2.0 에서 XML 작성을 해보았다면, 그 노가다와 다름없는 코딩을
해보았을 것이다.
 
XmlDocument 또는 XmlTextWriter, XmlTextReader 클래스 사용하여 편리하게 XML 작업을 했다.
XmlDocument 는 DOM 과 같은 형태로 작업이 가능하며, 빠른 탐색이 장점이다.
XmlTextWriter 와 XmlTextReader 는 Inline 작업이 가능하며, 간결한 사용법이 장점이었다.
 
기존 XmlTextWriter 로 XML 만드는 일반적인으로 작업하는 예
 
하지만, 오늘 XDocument 클래스를 알게된다면, 위 두 클래스를 쳐다도 안보게 될 상황이 생길 것 같다^^
 
 
XDocument 시작하기
 
기존 DOM 과 같은 형태로 객체를 생성하여 XML 을 만드는 방법이다.
 
// XElement 의 객체를 생성하는 방법
XDocument xdoc = new XDocument();
xdoc.Add(new XComment("XML TEST"));
XElement xele1 = new XElement("ROOT");
xele1.Add(new XElement("Child1", "1"));
xele1.Add(new XElement("Child2", "2"));
xdoc.Add(xele1);
Console.WriteLine(xdoc.ToString());
[코드1] 객체를 생성하여 XML 만들기
 
 
위와 같은 방법은 Element 에 가공이 필요할 때(attribute 추가) 사용하면 될 것 같다.
만약, 그러한 작업이 필요 없다면 더욱 간결하게 코딩 할 수 있다.
 
// params 로 선언된 XDocument 에 연속적으로 XElement 를 코딩.
xdoc = new XDocument(
        new XComment("XML TEST"),
        new XElement("ROOT",
               new XElement("Child1", "1"),
               new XElement("Child2", "2")));
        // 위의 결과와 동일
Console.WriteLine(xdoc.ToString());
[코드2] XDocument 의 params 인자로 연속적인 작업
 
XDocument 의 두번째 생성자는 params 키워드로 object 로 선언된 불특정 다수의 인자값을
받을 수 있다.
 
 
그렇기 때문에 위의 [코드2] 와 같은 연속적인 인자를 받는 코딩이 가능하다.
 
 
 
LINQ 를 이용한 Element 추출
 
위의 샘플과 같은 XML 데이터가 있을 경우, 이것을 추출하기 위해 탐색작업을 해야한다.
Framework 3.0 의 LINQ 는 간결하다 못해, 강력한 기능의 데이터 탐색 작업을 할 수 있다.
 
var result = from r1 in xdoc.Element("ROOT").Elements()
                where r1.Name == "Child1"
                select r1;
                      
foreach (var r in result)
{
        Console.WriteLine(r.Name);
}
[코드3] LINQ 를 이용한 XML 탐색 작업
 
 
XML Element Name 이 Child1 인 것을 탐색하는 구문이다.
기존 SQL 쿼리와 다른 것은 select 가 맨 마지막에 위치한다는 것이다.
 
데이터베이스의 SQL 구문과 C# 의 객체지향을 어느정도 이해한다면
그리 어렵지 않게 LINQ 쿼리를 만들어 낼 수 있을 것이다.
Posted by 땡초 POWERUMC
TAG c#, C# 3.0, XML

댓글을 달아 주세요


확장 메서드(Extension Method)
 
이름만 들어보아도 뭔가 심오한 분위기가 물씬 풍긴다.
쉽게 말하자만, 기존의 클래스를 확장한다고 해야할까?
만약, 클래스의 메서드를 확장하기 위해선 상속을 하고, 상속한 클래스에서 메서드를 선언하여 확장하는 방식이었다.
하지만 Framework 3.0 에서는 더 이상 메서드의 확장을 위해 클래스의 상속이 필요가 없다.
 
 
시작하기
 
심플한 샘플부터 보도록 하자.
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleTest1
{
        class Program
        {
               static void Main(string[] args)
               {
                       string url = "http://umc.pe.kr";
 
                       Console.WriteLine(url.GetUrl());
               }
        }
 
        public static class MyExtension
        {
               public static string GetUrl(this string url)
               {
                       return string.Format("<a href='{0}'>{0}</a>", url);
               }
        }
}
[코드1] 확장 메서드 사용예

 
 
 
 
public static class MyExtension
{
        public static string GetUrl(this string url)
        {
               …
        }
        ...
}
 
보시다시피 클래스와 메서드는 모두 static 으로 선언이 되었다.
 
MyExtension 클래스의 GetUrl 은 this string url 과 같이 1개있는 메서드다.
이것은 메서드를 확장할 타입이다. 즉, 우리는 string 을 확장한다는 표현인 것이다.
 
그렇다면 인자값을 받는 메서드를 확장해 보자.
 
 
확장 메서드는 반드시 static class 로 만들어야 한다.
그렇지 않으면 다음과 같은 오류 메시지를 맛보게 될 것이다.
 
 
보다시피, this string url 은 확장할 타입이라고 말한 바 있다.
바로 그 뒤로부터 비로서 확장 메서드의 인자값을 받을 수 있다.
 
 
그렇다면 어디까지 확장이 가능한가?
 
“단순히 클래스의 메서드만을 확장하는 것일까” 하고 이부분이 가장 궁금했다.
바로 delegate….
물론 delegate 란 것이, 특정 메모리 상의 위치를 참조하고 있고, 추가적인 동기/비동기 메커니즘 메서드를 제공하고 있다.
 
우선 답은 ‘가능하다’ 이다…
그렇다면 소스를 보자.
 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
 
namespace ExtensionAsyncMethod
{
        public delegate object BeginRunHandler();
        public partial class Form1 : Form
        {
               public Form1()
               {
                       InitializeComponent();
               }
 
               private void button1_Click(object sender, EventArgs e)
               {
                       // 확장 델리게이트 생성
                       BeginRunHandler handler = new BeginRunHandler(Running);
                       // 확장 메서드 호출
                       handler.BeginRun(new AsyncCallback(EndCallback), handler);
                      
               }
 
               private object Running()
               {
                       // 특정 작업
                       System.Threading.Thread.Sleep(1000);
 
                       return "작업완료";
               }
 
               // 콜백 메서드
               private void EndCallback( IAsyncResult result )
               {
                       BeginRunHandler handler = (BeginRunHandler)result.AsyncState;
                       object objResult = handler.EndRun(result);
 
                       MessageBox.Show(objResult.ToString());
               }
        }
 
        public static class MyExtension
        {
               public static IAsyncResult BeginRun(this BeginRunHandler handler, AsyncCallback callback, object obj)
               {
                       return handler.BeginInvoke(callback, obj);
               }
 
               public static object EndRun(this BeginRunHandler handler, IAsyncResult result)
               {
                       return handler.EndInvoke(result);
               }
        }
}
[코드2] 델리게이트 확장 메서드

 
델리게이트를 생성 후에,
 
handler.BeginRun(new AsyncCallback(EndCallback), handler);
 
 
구문을 통해 델리게이트의 확장 메서드를 호출 할 수 있는 것을 볼 수 있다.
 
 
마치며…
 
정말이지 C# 2.0 에서 3.0 의 변화에서만 볼 수 있듯이, 이렇게 까지 언어적인 설계가 가능한 것만 보아도, 엄청난 아키텍쳐 포스가 느껴진다.
Framework 3.0… 다가갈수록 그 매력이 느껴지지 않는가?
너무나무 눈깜짝할 사이 Framework 3.5 가 떡하니 기다리고 있지만,
차근차근 스텝바이스텝… 기다리거라^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요

윈도우 표준 사운드 파일 형식으로 WAVE 가 있다. .wav 형식의 확장자를 사용하며, 시스템이 사용하는 WAVE 파일은 C:\Windows\Media 폴더에 보면 있다.
 
이러한 .wav 파일을 재생하는 방법이 .NET Framework 2.0 에 추가되었다. SoundPlayer 라는 클래스를 사용하고, 이 클래스를 이용하여 사운드를 재생하는 방법을 소개한다.
 
 
동기적으로 .wav 파일 재생하기
 
동기적으로 .wav 파일을 재생하려면, SoundPlayer 의 PlaySync 메서드를 호출하면 된다. 생성자에 .wav 파일의 경로를 적어주는 것으로 객체를 생성하면 된다.
 
다음은 PlsySync 를 이용한 .wav 파일 재생 샘플이다.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Media;
 
namespace ConsoleTest2
{
         class Program
         {
                  static void Main(string[] args)
                  {
                           SoundPlayer player = new SoundPlayer(@"C:\Windows\Media\tada.wav");
                           player.PlaySync();
 
                           Console.WriteLine("재생 완료");
                  }
         }
}
 
[예제1] 동기적으로 .wav 파일을 재생한다
 
샘플을 실행하면 “타다~~” 라는 음향이 들리고, 재생이 완료되면 “재생 완료” 라는 텍스트가 출력된다.
즉, PlaySync 메서드의 호출로 재생이 완료되기 전 까지는 다음 코드가 실행되지 않는다.
 
 
비동기로 .wav 파일 재생하기
 
비동기적으로 사운드를 재생하기 이해서 LoadAsync 와 Play 메서드를 제공한다. .wav 파일을 완전히 로드하기 위해 약간의 딜레이가 필요하고, 사운드 파일이 완전히 로드 되었다는 이벤트로 LoadCompleted 이벤트를 제공한다.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Media;
 
namespace ConsoleTest2
{
         class Program
         {
                  static SoundPlayer player = new SoundPlayer(@"C:\Windows\Media\tada.wav");
 
                  static void Main(string[] args)
                  {
                           player.LoadCompleted += new System.ComponentModel.AsyncCompletedEventHandler(player_LoadCompleted);
                           player.LoadAsync();
 
                           Console.WriteLine(".WAV 파일이 로드가 되고 있습니다.");
                           System.Threading.Thread.Sleep(1000);
                           Console.WriteLine("소리를 재생합니다.");
                  }
 
                  static void player_LoadCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
                  {
                           if( player.IsLoadCompleted )
                                   player.Play();
                  }
         }
}
 
[예제2] 비동기적으로 .wav 파일을 재생한다.
 
첫번쩨 [예제1] 과는 약간 다른 결과이다. [예제1] 에서는 재생이 완료되기전까지 다음줄의 코드가 실행되지 않았다.
 
하지만, [예제2]의 비동기 재생에서는 LoadAsync 메서드로 비동기적으로 사운드 파일을 읽어, LoadCompleted 이벤트가 발생하는 시점에 사운드가 재생된다.
Console Project 이므로, 어플케이션 쓰레드가 종료되는 것을 방지하기 위해
 
Thread.Sleep(1000);
 
을 하였다.
 
.NET Framework 2.0 이 제공하는 SoundPlayer 의 몇가지 메서드나 이벤트를 살펴보면, 안타깝게도 재생이 완료되는 시점의 이벤트가 제공되지 않는 것이 아쉽다.
 
.NET Framework 3.5 에서의 SoundPlayer 클래스를 살펴봤지만, 앞으로도 크게 변화는 없을 것 같다.

# 위 글은 http://www.atmarkit.co.jp/ 를 토대로 새로이 각색하였습니다.