HashTable 클래스와 Dictionary 클래스의 비호환성
제네릭 컬렉션은 용이하게 사용할 수 있어 당장이라도 사용하고 싶을 것이다.
 
그러나, 기존 C# 1.x 버전에서 제네릭 버전으로 치환하면서 대체할 수 없는 경우도 있다. 특히 심각한 것은, HashTable 클래스와 Dictionary 클래스의 비호환 동작의 경우다. 실제로 다음과 코드를 보자.
 
using System;
using System.Collections;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    try
    {
      // Hashtable 클래스(종래의콜렉션·클래스)
      Hashtable hashtable = new Hashtable();
      Console.WriteLine(hashtable[0] == null);
 
      // Dictionary 클래스(제네릭·콜렉션)
      Dictionary<int, object> dictionary
                                 = new Dictionary<int, object>();
      Console.WriteLine(dictionary[0] == null); // 예외가발생
    }
    catch (Exception e)
    {
      Console.WriteLine(e.ToString());
    }
 }
}
리스트9 Hashtable 클래스와 Dictionary 클래스의 비호환 동작 예
 
True
System.Collections.Generic.KeyNotFoundException: 지정된키는디렉토리내
에존재하지않았습니다.
   장소System.ThrowHelper.ThrowKeyNotFoundException()
   장소System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   장소Program.Main(String[] args) 장소……\Program.cs: 15
 
리스트9 실행 결과
 
존재하지 않는 키에 대한 엑세스는, 기존의 Hashtable 클래스에서는 null을 돌려주지만, Dictionary 클래스에서는 System.Collections.Generic.KeyNotFoundException 예외를 던진다.
 
어느 키에 대한 값이 존재 하는지 아닌지를 판정하는 처리는 생각보다는 자주 있지만, 만약 위와 같은 샘플 코드의 방법으로 판정하고 있는 경우는 고쳐 쓸 필요가 있다.
 
어느키가 컬렉션에 포함되어 있을까는, ContainsKey 메서드로 판정할 수 있다. 또, 키의 존재 체크와 값을 취득을 1회 실시하는 경우는, TryGetValue 메서드라고 하는 편리한 메서드도 있다.
 
If( Dictionary 오브젝트.TryGetValue(키, out value)) { xxxx }
 
그렇다고 키가 존재하는 경우에게만 변수 value에 값이 들어와, if 문장의 조건이 성립하므로, xxxx 부분에 value 의 값을 사용하는 코드를 안전하게 쓸 수 있다.
 
그러나 왜 이러한 사양 변경을 한 것일까. 잘 생각하면 알것이다.
Dictionary 클래스의 사양은 너무나도 합리적이다.
 
예를 들면, C# 2.0 의 신기능, nullable 을 사용하면, 값으로 null도 대입할 수 있다. int 형에 null 을 넣어 용이하게 사용할 수 있다. 이러한 형태의 값을 컬렉션에 추가했을 때, 당연히 null 도 유효한 값이라고 보여지지 않으면 안된다.
 
이것은 다음의 코드를 보자( int? 는 nullable 형의 int 형태 )
using System;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    Dictionary<int, int?> dictionary = new Dictionary<int, int?>();
    dictionary[0] = null;
 
    Console.WriteLine(dictionary.Count); // 출력:
    Console.WriteLine(dictionary[0] == null); // 출력:True
 }
}
리스트10 null 이지만 값이 되는 컬렉션
 
이 프로그램은 컬렉션에 1개의 키와 값을 넣고 있다. 또한 dictionary.Count 의 값이 1 인것을 확인할 수 있다. 그러나 값을 꺼내보면 null 이다.
 
만약, 키가 존재하지 않을 때에 얻을 수 있는 값도 null 이면, 들어있는 값이 null일 때, 키가 존재하는지 그렇지 않은지 구별할 수 없게 된다. 키가 없을 때는 예외를 던지는 것은, 구별을 명확하게 하기 위해 필요하게 되는 대처가 되는 것이다.
 
하지만, 예외는 무거운 처리이므로, 예외를 사용하는 것보다 ContainsKey 메서드로 알아보는 것이 좋을 것이다.
 
제네릭 클래스를 만들자
제네릭 클래스를 만드는 예를 보자. 여기에서는, 임의의 타입을 변수에 저장하는 클래스를 작성해 보자.
using System;
 
// 제네릭인MyClass 클래스의정의( T ()는형태파라미터의이름)
public class MyClass<T>
{
 public T Value;
 
 public MyClass(T v)
 {
    Value = v;
 }
}
 
class Program
{
 static void Main(string[] args)
 {
    MyClass<int> i = new MyClass<int>(0);
    Console.WriteLine(++i.Value); // 출력:
 
    MyClass<string> s = new MyClass<string>("abc");
    Console.WriteLine(s.Value.ToUpper()); // 출력:ABC
 }
}
리스트11 제네릭 클래스
 
먼저 클래스의 이름 뒤에 < > 로 둘러싸 형 파라메터의 이름을 쓴다. 여기에 쓰는 이름은 실제로 존재하지 않는 형태의 이름이 좋다. 사용할 땐, 이 이름이 실제로 지정된 형으로 변환되기 때문이다. 덧붙여, 여기에서는 <T> 또는 T로 시작되는 이름 <TValue>를 사용하는 것이 일반적이다.
 
그리고, 선언된 형 파라메터의 이름을 마치 실제로 존재하는 형의 이름인 것 처럼 사용해 클래스를 구현한다.
 
예를 들면 public T Value; 라고 하는 변수 선언은, 마치 T 가 형이 있는 것 같이 기술하고 있지만, 실제로 사용되는 경우에는 <T> 로 지정된 int 나 string 으로 옮겨지게 된다.
 
Posted by 땡초 POWERUMC
TAG c#, generic

댓글을 달아 주세요

제네릭 컬렉션의 사용법
 
제네릭 컬렉션(=제네릭의 기능을 사용하고 있는 컬렉션 클래스)의 사용법은 어려운 것이 없다.
 
기본적인 사용법만 얘기한다면, 다음의 원칙을 기억하면 좋다.
 
★ 제네릭 컬렉션의 형명의 뒤에 취급하는 데이터의 형명을 < > 로 둘러싸서 기술한다( 복수의 데이터형을 취급하는 경우에는 콤마로 구분한다)
 
즉, List<string>
 
과같이 사용하면 된다. new ArrayList() 와 같이 사용하듯, new List<string>() 과 같이 사용하면 된다.
 
Dictionary 클래스와 같이, 키(key)와 값(value)의 2개의 형태를 이용하는 클래스의 경우는, < > 안에 형태를 2개 지정한다.키가 정수이고 값이 문자열이라면,
 
Dictionary<int, string>
 
과 같이 사용한다. 그리고, 기존의 컬렉션과 취급방식은 큰 차이가 없다.
다음은 이러한 클래스의 사용예이다.
 
using System;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    // 문자열을요소로서취급한다List 클래스
    List<string> list = new List<string>();
    list.Add("Sample List");
 
    Console.WriteLine(list[0]); // 출력:Sample List
 
    // 키가정수로값이문자열의요소를취급한다Dictionary 클래스
    Dictionary<int, string> dic = new Dictionary<int, string>();
    dic[0] = "Sample Dictionary";
 
    Console.WriteLine(dic[0]); // 출력:Sample Dictionary
 }
}
리스트4 List 클래스와 Dictionary 클래스의 사용예
 
물론, < > 로 둘러싸 지정한 형은, 그것을 상속한 형 또한 받아 들인다. 인터페이스를 지정했을 경우는, 그 인터페이스를 상속한 형 또한 받아 들인다.
 
using System;
using System.Collections.Generic;
 
// 인터페이스의정의
public interface ISample
{
 void SayISample();
}
 
// 추상클래스의정의
public abstract class SampleBase
{
 public abstract void SaySample();
}
 
// SampleBase 클래스를계승해,ISample 인터페이스를실장한클래스
public class Sample : SampleBase, ISample
{
 public override void SaySample()
 {
    Console.WriteLine("Sample!");
 }
 public void SayISample()
 {
    Console.WriteLine("ISample!");
 }
}
 
class Program
{
 static void Main(string[] args)
 {
    // SampleBase 형태의콜렉션에Sample 오브젝트를추가
    List<SampleBase> list1 = new List<SampleBase>();
    list1.Add(new Sample());
 
    list1[0].SaySample(); // 출력:Sample!
 
    // ISample 형태의콜렉션에Sample 오브젝트를추가
    List<ISample> list2 = new List<ISample>();
    list2.Add(new Sample());
 
    list2[0].SayISample(); // 출력:ISample!
 }
}
 
리스트5 상속과 인터페이스를 사용한 예
 
제네릭 메서드와 형 추론
 
제네릭은 클래스 이외에도 사용할 수 있다. 구조체나 인터페이스에도 사용할 뿐만 아니라, Delegate 나 메서드에도 사용할 수 있다.
 
여기에서는, 특히 제네릭을 사용한 메서드인 [제네릭 메서드]에 주목하자. 이것은, 바로 사용할 수 있는 편리한 기능이 있지만, 주의를 필요로 하는 부분도 있기 때문이다.
 
클래스 라이브러리가 제공하는 제네릭 메서드중에서, 특히 빈번히 사용한 수 있는, 배열을 소트하는 Array 클래스의 Sort 메서드일 것이다. 사용법은 클래스와 같이 메서드의 이름뒤에 < > 로 둘러싸 형명을 쓰면 된다.
 
using System;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    int[] array = { 3, 2, 1 };
    Array.Sort<int>(array); // 제네릭·메소드의호출
 
    foreach (int i in array) Console.WriteLine(i);
    // 출력:
    // 1
    // 2
    // 3
 }
}
리스트6 제네릭 메서드의 사용예
 
<int>를 지정한 Sort<int> 메서드는 인수의 int 형태의 배열을 받아, 그 요소를 정렬하기 위한 메서드다.
 
그러나, Array.Sort 의 뒤엔 <int> 가 있다. 여기엔 큰 의미가 없다. 왜냐하면, 인수로 지정한 배열 변수(array)의 형이 int[] 이기 때문에, 통상 <int> 를 기술하는 것은 의미가 없기 때문이다.
 
여기에서, [형 추론] 이라고 하는 기능을 사용해 C# 컴파일러에 형 추리를 시킬 수 있다. 이 기능이 사용하기 위해 <int> 를 생략해도 의도대로 동작한다.
 
using System;
 
class Program
{
 static void Main(string[] args)
 {
    int[] array = { 3, 2, 1 };
    Array.Sort(array); // <int> ()를생략하고있다
 
    foreach (int i in array) Console.WriteLine(i);
    // 출력:
    // 1
    // 2
    // 3
 }
}
리스트7 형 추론에 의해 제네릭 메서드를 사용한 예
 
이 코드는 언뜻, 기존 C# 1.x 의 Sort 메서드의 호출과 구별이 되지 않는다.
 
그러나, ildasm 이나 .NET Reflector 에 의해 생성된 코드를 보면, 제네릭이 아닌 기존의 Sort 메서드가 아니고, 형 추론을 해 Sort<int> 메서드가 호출되는 것을 알 수 있다.
 
그런데, 형 추론은 강력하기는 하지만, 보기 드물게 형 추론을 사용 할 수 없는 경우도 있다.
 
예를 들면, IComparable 인테페이스를 구현한 base class 와 상속 클래스가 있는 경우, 그것을 명시적으로 구분하여 사용하지 않으면 결과가 바뀌어 버리는 일이 있다. 아래의 예를 보자.
 
using System;
 
public class ClassA : IComparable<ClassA>
{
 public int Value;
 public ClassA(int v)
 {
    Value = v;
 }
 
 public int CompareTo(ClassA other)
 {
    return Value.CompareTo(other.Value);
 }
}
 
public class ClassB : ClassA, IComparable<ClassB>
{
 public ClassB(int v) : base (v) { }
 
 public int CompareTo(ClassB other)
 {
    return other.Value.CompareTo(Value);
 }
}
 
class Program
{
 static void Main(string[] args)
 {
    ClassB[] array = {
      new ClassB(3),
      new ClassB(2),
      new ClassB(1),
    };
 
    Array.Sort<ClassA>(array);
    foreach (ClassB b in array) Console.WriteLine(b.Value);
    // 출력:
    // 1
    // 2
    // 3
 
    Array.Sort<ClassB>(array);
   foreach (ClassB b in array) Console.WriteLine(b.Value);
    // 출력:
    // 3
    // 2
    // 1
 }
}
 
리스트8 형 추론을 사용할 수 없는 예
 
이 예제에 대해, 형 지정을 생략해 Array.Sort(array); 라고 기술하면, Array.Sort<ClassB>(array)라고 추론되지만, 그 실행 결과는 Array.Sort<ClassA>(Array); 가 실행된다.
 

Posted by 땡초 POWERUMC
TAG c#, generic

댓글을 달아 주세요


제네릭이란 무엇인가?
 
C# 1.x 프로그래밍의 불만이란 무엇일까.
 
구체적으로는 여러 가지가 있다고 생각하지만, 아마 아래의 내용에 대해서는, 대부분 C# 1.x 프로그래머가 불만이라고 느끼고 있을 것이다.
 
★ 컬렉션의 요소를 엑세스 할 때, 캐스트가 요구된다.
 
구체적으로 말하면, 다음과 같다.

using System;
using System.Collections;
 
class Program
{
 static void Main(string[] args)
 {
    ArrayList list = new ArrayList();
    list.Add("Hello!");
 
    Console.WriteLine(((string)list[0]).ToUpper()); // 캐스트필요
    // 출력:HELLO!
 }
}
리스트1 C# 1.x 에서 컬렉션을 사용한 예

 
이 프로그램에서는, 가변 사이즈 1차원 리스트 컬렉션을 구현했다.
ArrayList 클래스에서 문자열을 넣고 나서 꺼내고 있다. 여기서, 아무일도 없어 문자열을 컬렉션에 넣을 수 있는데, 꺼낼 때는
 
(string)list[0]
 
이러한 캐스트가 필요하게 된다. 그리고, 이 캐스트 작업은 귀찮다.
 
우선, 쓰지 않으면 안되는 문자가 증가해 귀찮다. 본질적으로 필요가 없는 문자가 소스 코드상에 증가하면, 그만큼 기독성도 나빠져, 점검성도 떨어진다. 하지만, 이것은 아직 시초에 지나지 않는다.
 
캐스트를 사용하면, 그만큼 실행 속도가 저하된다. 캐스트는 무거운 처리인 것이다. 덧붙여서, C# 의 캐스트보다 as 연산자를 사용하는 것이 더 빠르지만, as 연산자는 캐스팅을 할 수 없을 때 null 이 리턴되고, 이 null 을 비교해야 하기 때문에 취급하기 어렵다.
 
그리고, 캐스트를 임용하면, 형변환의 타당성 체크를 컴파일시에 실시할 수 없게 되어, 성능이 저하된다. 이것을 [객체의 형변환은 실행시에 체크되기 때문에 문제 없다] 라고 주장하는 사람도 있지만, 본질적으로 이것은 무엇이 문제일까를 잃은 발언이라고 할 수 있다. 형변환이 런타임시 체크되는 것은, 체크가 지연된다는 것이 문제가 아니고, 형변환 체크가 [개별의 값]에 대해서 행해진다고 하는 문제인 것이다.
 
보다 구체적으로 보자. 캐스트를 임용하면, 우선 이하와 같은 상황이 발생한다.
 
★ 테스트를 할 때, 모든 행이 실행된다고는 할 수 없다. 즉, 실행되지 않은 행에 쓰여진 캐스트의 타당성은 체크되지 않는다.
 
이것은 테스트 지원 툴등을 이용하여, 커버리지(Coverage)가 100%를 목표로 하면 해소할 수 있을 지도 모른다.
 
★ 우연히 테스트 실행시에 주어진 값에 대해서 캐스트가 타당하다고 확인하지만, 실행시에 모든값에 대해서, 캐스트가 타당한가는 체크되지 않는다.
 
이러한 문제는, 작은 프로그램에서도 버그가 생길 수도 있고, 큰 규모의 프로그램에서는 상당한 리스크가 된다. 예를 들면, 컬렉션에 생각할 수 도 없는 형태의 값이 들어가 있고, 잘 되어야할 캐스트가 실패한다라고 가정하에, 사양 변경이 많은 프로젝트나, 의사소통이 불충분한 개발 프로젝트에서는 큰 충격이 될 수 밖에 없다.
 
이러한 문제는, 가능한 여러 형타입을 컴파일시에 실시하는것으로 해결할 수 있다.
 
이러한 작업이 큰 불만이 아닐 수도 있다. 그러나, C++ 의 template 이라 불리는 기능은, 컬렉션이나 클래스의 캐스트 지옥에서 벗어날 수 있었다. 때문에 C# 1.x 로서는 큰 불만이 될 수 있을거라 생각한다.
 
그리고, C# 2.0 은 이러한 문제를 해소할 수 있는 기능이 추가 되었다. 그것은 C++ 의 template 기능과 다른, 제네릭이라고 하는 비슷하면서도 다른 기능이다.
 
이 기능을 사용하면, 리스트1 예제로부터 캐스트를 추방할 수 있는 것을 알 수 있다.

using System;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    List<string> list = new List<string>();
    list.Add("Hello!");
 
    Console.WriteLine(list[0].ToUpper()); // 캐스트불요
    // 출력:HELLO!
 }
}
리스트2 C# 의 제네릭을 사용한 예

 
여기서 list 변수를 선언할 때,
 
List<string>
 
과 같이, string 이라는 타입을 명시하는 것으로써, 소스코드에서 캐스트를 사용하지 않는다.
 
새로운 컬렉션의 소개
 
리스트1에서 사용하고 있는 List 클래스는, 기존의 ArrayList 에 상응하는 컬렉션 클래스다. 덧붙여서 List 클래스가 속하는 네임스페이스는 System.Collections 가 아니고 System.Collections.Generic 이다.
 
비쥬얼스튜디오 2005에서는 코드파일을 만들게 되면, 기본적으로
 
using System.Collections.Generic;
 
위와 같이 작성된다.
 
종래의 컬렉션
제네릭의 컬렉션
기능
ArrayList
List
가변 사이즈의1 차원 리스트
Hashtable
Dictionary
키/치 페어의 컬렉션
Queue
Queue
선입처 내밀기 컬렉션
SortedList
SortedList
키로 소트 된 키/치 페어의 컬렉션
Stack
Stack
후입선출 컬렉션
컬렉션·클래스의 대응표
제네릭의 컬렉션·클래스는.NET Framework 2.0 그리고 클래스·라이브러리에 추가된 것.물론 이것에는 종래의 컬렉션·클래스도 포함되어 있다.
 
덧붙여서, Stack 클래스와 같이 기존의 이름과 같은 제네릭 클래스가 있는것에 주의가 필요하다. 이것은 다른 네임스페이스에 속하는 다른 클래스다. 그중, 특히 큰 문제는 다음에 설명한다.
 
그런데, ArrayList 로부터 List 로의 이름 변경은, 자주 사용되는 클래스의 이름이 짧아졌다고 하는 것으로 사용하기 편해졌다고 말할 수 있을 것이다.( ArrayList 와 상응하는 List<string> 처럼, 짧아졌다고는 말하기 어렵지만…)
 
한편, HashTable 은 Dictionary 로의 이름변화는, 기존 이름과 전혀 다른 이름변화라고 할 수 있다. 이름으로 유추할 수 없고, 새로운 클래스명을 암기 할 수 밖에 없을 것이다.
 
새로운 컬렉션 클래스 – LinkedList 클래스
 
LinkedList 클래스는 1차원적인 배열만 취급한다고 하는 의미에서 ArrayList/List 클래스와 닮을 기능을 가지고 있지만, 다른 성질을 가지고 있다.
 
주된 차이점은 두가지 이다. 첫째, 인덱스에 의한 엑세스를 할 수 없다. 즉, 인덱서에 번호를 지정하여 엑세스 할 수 없는 것이다. 둘째, 리스트 중간에 다른 요소를 삽입/삭제를 빠르게 할 수 있다.
 
여기서, List 클래스와 LinkedList 클래스의 삽입 성능의 차이를 보자. 아래는 2개의 요소를 가지는 리스트의 중간에 1만개의 요소를 삽입하는 작업을 100회 반복하는 샘플 코드이다.
 

using System;
using System.Collections.Generic;
 
class Program
{
 static void Main(string[] args)
 {
    // List 클래스의이용
    DateTime start1 = DateTime.Now;
    for (int i = 0; i < 100; i++)
    {
      List<string> list1 = new List<string>();
      list1.Add("First");
      list1.Add("Last");
      for (int j = 0; j < 10000; j++)
      {
        list1.Insert(1, "Inserted");
      }
    }
    DateTime end1 = DateTime.Now;
    Console.WriteLine(end1 - start1);
 
    // LinkedList 클래스의이용
    DateTime start2 = DateTime.Now;
    for (int i = 0; i < 100; i++)
    {
      LinkedList<string> list2 = new LinkedList<string>();
      list2.AddFirst("First");
      list2.AddLast("Last");
      for (int j = 0; j < 10000; j++)
      {
        list2.AddAfter(list2.First, "Inserted");
      }
    }
    DateTime end2 = DateTime.Now;
    Console.WriteLine(end2 - start2);
 }
}
리스트3 List와 LinkedList 의 삽입 성능의 차이
Pentium D 3.2Ghz CPU 로 디버그 빌등에 의한 실행 결과

 

00:00:05.3480694 (List 클래스이용시)
00:00:00.0820164 (LinkedList 클래스이용시)
리스트3의 실행결과

 
위의 결과를 통해, 엄청난 성능 차이가 나고 있다.
 
LinkedListNode 클래스의 인스턴스는 리스트에 포함하거나 제외하거나 하는 작업에 용이하고, 그 특징을 잘 활용하면 리스트를 분해해 재구축하는 처리에 좋은 성능을 낸다. ArrayList/List 클래스를 사용하고 성능이 나오지 않는 경우는 LinkedList 클래스로 테스트해 보면 좋을 것이다.
 
다만, 메모리의 사용량(확보되는 오브젝트 수)는 LinkedList 클래스가 더 커진다고 하는 디메리트도 있다. 위의 프로그램을 따로따로 분리해 실행시키면, 프로그램 종료시점으로 List 클래스는 약 6Mbytes 정도의 메모리를 소비하고 있는데 비해, LinkedList 클래스는 보다 많은 약 11Mbytes 정도의 메모리를 소비하고 있다.
 
새로운 컬렉션 클래스 – SortedDictionary 클래스
 
C# 2.0 에서 추가된 SortedDictionary 클래스는 SortedList 클래스와 기능이 비슷하다. 그러나 완전히 같지는 않다. SortedDictionary 클래스와 SortedList 클래스의 관계는, LinkedList 클래스와 List 클래스의 관계와 비슷하다.
 
SortedDictionary 클래스와 SortedList 클래스의 차이는, 메모리의 사용 방법과 삽입 및 삭제의 속도이다. 주된 차이점은 다음과 같다.
 
SortedList 클래스는 SortedDictionary 클래스만큼 메모리를 사용하지 않는다.
 
SortedDictionary 클래스에는 정렬되지 않은 데이터에 대해서 고속 삽입 및 삭제가 가능
 
모든 리스트의 데이터를 한번에 꺼내는 경우 SortedList 클래스가 SortedDictionary 클래스보다 빠른 성능을 보인다.
 
Posted by 땡초 POWERUMC
TAG c#, generic

댓글을 달아 주세요

우리는 가끔씩 리플랙션을 사용한다.
사용하는 목적 또한 다양하고 리플랙션의 장점 또한 무궁무진 하다.
 
오늘 이야기할 내용은 어셈블리는 리플랙션을 하는데 있어 무척 도움이 될만한 내용을 살펴보겠다.
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;
 
namespace ConsoleTest1
{
       class Program
       {
             static void Main(string[] args)
             {
                    try
                    {
                           BindingFlags flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
                           // 실행중인 응용프로그램의 경로를 표시한다.
                           Console.WriteLine(Environment.CurrentDirectory);
 
                           // 빌드된 웹페이지의 dll 을 로드한다.
                           Assembly asm = Assembly.LoadFrom(@"C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\UmcTest\ConsoleTest1\bin\Debug\App_Web_articlecontrol.ascx.51af1afc.dll");
 
                           // 모듈을 로드한다.
                           foreach (Module module in asm.GetModules())
                           {
                                 // 모듈 이름 출력
                                 Console.WriteLine(module.Name);
 
                                 // 클래스의 Type 을 가져온다.
                                 Type type = asm.GetType("WebAdmin_05Article_Controls_ArticleControl");
                           }       
                    }
                    catch (Exception ex)
                    {
                           Console.WriteLine( ex.Message );
                    }
             }
       }
}
 
 
우선 위와 같은 간단한 리플랙션 소스를 놓고 차근차근 살펴보겠다.
 
위 소스는 실행결과 아무런 예외도 내뱉지 않는다.
 
 
어셈블리에 위와 같은 WebAdmin_05Article_Controls_ArticleControl 타입은 분명 존재한다. 그래서 에러가 나지 않는 것일까?
 
결론부터 말하면 절대 아니다.
참고로 위 App_Web_articlecontrol.ascx.51af1afc.dll 어셈블리는 Umc.Core.Dll 을 참조하고 있으며, Umc.Core.Dll 이 있어야 정상적으로 작동할 수 있다.
 
App_Web_articlecontrol.ascx.51af1afc.dll 어셈블리와 Umc.Core.Dll 은 같은 폴더에 놓이게 되면 닷넷 어셈블리는 자동으로 App_Web_articlecontrol.ascx.51af1afc.dll이 로드될 때 Umc.Core.Dll 어셈블리를 참조하게 된다.
 
그럼 좀더 예외상황을 자세히 보도록 하기 위해 다음의 구문에 인자값을 추가해 주자.
 
// 클래스의 Type 을 가져온다.
Type type = asm.GetType("WebAdmin_05Article_Controls_ArticleControl", true, false);
 
위와같이 수정되었으니 다시 한번 실행해 보겠다.
 
 
보시다시피 뭐라뭐라 Exception 메시지가 떠 버렸다.
 
위에서 설명한대로 참조한 어셈블리(Umc.Core.Dll) 이 존재하지 않기에 어셈블리를 정상적으로 로드하지 못한 결과다.
 
만약, 하나의 응용프로그램에 참조된 어셈블리는 한 두개가 아닌, 수개 내지 수십개라고 가정해 볼 때 리플랙션을 수행하는 개발자 입장에선 곤욕이 따로 없을 것이다.
 
그럼 이와 같은 예외가 발생할 때 어떻게 참조된 어셈블리는 알아낼 수 있는지 알아보자.
 
그러기 위해 약간의 소스를 변경해 보겠다.
                                    …
                                    …
                                    …
                           // 모듈을 로드한다.
                           foreach (Module module in asm.GetModules())
                           {
                                 // 모듈 이름 출력
                                 Console.WriteLine("Module Name : " + module.Name);
 
                                 // 클래스의 Type 을 가져온다.
                                 Type[] type = asm.GetTypes();
                           }
                    }
                    catch (ReflectionTypeLoadException ex)
                    {
                           Console.WriteLine( ex.Message );
                    }
 
단순히 어셈블리의 모듈별로 Types 를 가져오도록 했다.
 
 
위에서 말한대로 참조된 어셈블리를 로드할 수 없어 ReflectionTypeLoadException이 떠버렸다.
 
그럼 참조하지 못한 수개 또는 수십개의 어셈블리를 나열해 보자.
 
다음과 같이 소스를 수정해 보기 바란다.
 
catch (ReflectionTypeLoadException ex)
{
       Console.WriteLine("-----------------------------------------------------------");
       foreach (Exception ex1 in ex.LoaderExceptions)
       {
             FileNotFoundException fex = ex1 as FileNotFoundException;
             if (fex != null)
             {
                    Console.WriteLine(fex.FileName);
             }
       }
}
 
 
하하하.. 결과는 대만족이다.
만약 여러 서드파티 제품 등의 어셈블리, 또는 기타 어셈블를 참조할 경우 참조하지 못한 어셈블리들의 리스트가 쭈욱 뜬다.
 
위와같이 참조하지 못한 어셈블리에 대해 재귀호출을 통하여Environment.CurrentDirectory 로 어셈블리를 복사하면서 수행하게 되면 리플랙션을 잘 작동 될 것이다.
 
그럼 이만 ^^;
Posted by 땡초 POWERUMC
TAG c#, reflection

댓글을 달아 주세요


동적 이벤트 처리.. 어디에 써먹으면 좋은까..
 
우선 이에 앞서 리플랙션 이야기를 잠시만 언급하겠습니다.
System.Reflection 네임스페이스와 System.Type을 사용하면 우리가 원하는 어셈블리의 클래스, 인터페이스, 프로퍼티 와 맴버에 대한 정보를 얻을 수 있습니다.
간단히 말하면, 런타임으로 동적으로 다양한 작업을 하고자 할 때 사용됩니다.
 
대부분 다음과 같은 경우에 자주 사용됩니다.
 
1) 실제 코드가 아닌 정보들, 그 파일에 따라다니는 정보들 등 어플케이션의 메타 정보를 얻어서 유지보수에 도움을 받기도 합니다.
 
2) 어셈블리의 내용을 알고자 할 때 사용할 수 있습니다.
Assembly asm = Assembly.Load("Mscorlib.dll");
Type[] types = asm.GetTypes();
foreach(Type t in types)
{
Console.WriteLine("Type : {0}", t)
}
 
3) Design Pattern 의 Abstract Factory Pattern 의 단점을 보완할 수 있습니다. Factory Class가 추가될 때 마다 본문의 내용이 수정이 불가피 하지만, 리플랙션을 이용하면 Class명만으로도 이것을 쉽게 해결 할 수 있습니다.
 
4) Com 개체를 Late-Binding 으로 불러 사용할 수 있습니다.
private void OpenExcel()
{
Type excel = Type.GetTypeFromProgID("Excel.Microsoft");
 
object objExcel = Activator.CreateInstance(excel)
object[] param = object[1];
param[0]=true;
excel.InvokeMember("Visible", BindingFlags.SerProperty, null, objExcel, param);
}
 
대부분 리플랙션은 접근제한 맴버 또는 프로퍼티, 메서드 호출 등에 사용되거나, 참조되지 않은 어셈블리를 제어할 때 자주 사용되곤 합니다.
 
하지만, 이번에는 자주 사용되지 않는 Event 제어를 해 보도록 하겠습니다.
 
소개될 예제는 WinForm 을 기준으로 작성되었지만, WebForm 에서도 약간의 소스를 변경하면 정상적으로 작동합니다.
 
우리가 즐겨 사용하는 컨트롤중 Button 컨트롤이 있습니다. 모든 닷넷 컨트롤은 Control 을 상속하므로 기본적인 많은 Event 가 존재하고, 컨트롤의 기능에 따라 CustomEvent 또한 존재할 것입니다.
 
우선 컨트롤의 이벤트 목록을 얻어보도록 하겠습니다.
string msg = string.Empty;
EventDescriptorCollection events = TypeDescriptor.GetEvents(button1);
foreach (EventDescriptor ed in events)
{
             msg += ed.Name + "\r\n";
}
MessageBox.Show( msg );
 
실행결과)
 
EventDescriptor 클래스는 이벤트 이름, 특성, delegate 등으로 구성되어 있습니다.
이 클래스의 주요 메서드 중 AddEventHandler 와 RemoveEventHandler 가 존재합니다.
이것을 통해 이벤트를 제어할 수 있습니다.
 
그럼, 이제 우리가 이벤트를 제어해야할 가상의 상황을 만들도록 하겠습니다.
public Form1()
{
             InitializeComponent();
             button1.Click += new EventHandler(button1_Click);
}
 
private void button1_Click(object sender, EventArgs e)
{
             System.Threading.Thread.Sleep(1000);
             MessageBox.Show("처리되었습니다");
}
 
위와같이 샘플로 만든 폼은 button1 을 클릭하면 가상의 1초의 작업 후 MessageBox 를 띄우는 경우입니다.
 
하지만 갑자기 변해버린 요구사항엔 ‘주요 몇몇 공통버튼에 대해 “Loading…” 텍스트나 “작업중입니다” 라는 메시지를 보여달라’는 변경사항이 있을 수 있습니다.
단지 몇 개의 폼이라면 수작업으로 간단히 해결할 수 있지만, 몇백개에 해당하는 폼이라면 사정은 달라지게 됩니다. Otl
그렇다면 당신은 어떻게 하겠습니까?
 
우선 현재 Form 의 Type 과 Loading… 텍스트를 뿌려줘야 하는 Button Type 을 알아와야 합니다
Type thisType                   = this.GetType();
Type btn1                          = button1.GetType();
주의할 점은, button1 이 실행중인 어셈블리가 아니라면 다른 어셈블리에서 PropertyInfo 를 통해 Type을 찾아와야 합니다
 
그리고 Loading.. 이벤트와 Button_Click 이벤트의 메서드가 다음과 같이 구현되어 있습니다.
private void OnLoadingHandler(object sender, EventArgs e)
{
        lblLoading.Visible = true;
        this.Refresh();
}
 
private void OffLoadingHandler(object sender, EventArgs e)
{
        lblLoading.Visible = false;
}
 
private void button1_Click(object sender, EventArgs e)
{
        System.Threading.Thread.Sleep(1000);
        MessageBox.Show("처리되었습니다");
}

위  이벤트의 메서드를 MethodInfo 를 통해 메서드의 정보를 찾습니다.
MethodInfo thisMi = thisType.GetMethod("button1_Click", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
MethodInfo onLoadMi         = thisType.GetMethod("OnLoadingHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
MethodInfo offLoadmi= thisType.GetMethod("OffLoadingHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
여기에서도 Button1_Click 이 실행중인 어셈블리가 아니라면 thisType 은 button1_Click 이 구현되어 있는 Class의 Type 이 되어야 합니다.
 
이제 Button 의 Click 이벤트를 가져와야 합니다.
EventInfo ei          = btn1.GetEvent("Click" , BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
으읔…. 정말 간단하네요 -_-;
 
위의 처음 샘플의 EventDescriptor.AddEventHandler 의 메서드는 EventInfo 에도 존재합니다.
 
아시다시피, 모든 이벤트는 delegate 의 대리자를 통해 추가 또는 삭제할 수 있습니다.
그럼 우리는 이벤트의 delegate 를 만들어야 합니다.
 
Delegate d                         = Delegate.CreateDelegate( ei.EventHandlerType, this, thisMi );
Delegate onLoadD = Delegate.CreateDelegate( ei.EventHandlerType, this, onLoadMi );
Delegate offLoadD = Delegate.CreateDelegate( ei.EventHandlerType, this, offLoadmi);
 
각각 d 는 기존의 Click Event 이고, onLoadD, offLoadD 는 Loading.. 메시지를 보여주고, 없애는 delegate 가 됩니다.
 
이벤트는 += 와 -= 연산을 통해 추가, 또는 삭제할 수 있습니다. 이것을 리플랙션에서 RemoveEventHandler 메서드를 통해 구현할 수 있습니다.
ei.RemoveEventHandler( button1, d );
여기까지 샘플을 실행하게 되면 이미 Event 가 연결되어 있음에도 불구하고, 더 이상 Click 이벤트가 발생하지 않게 됩니다.
 
그럼 다음과 같이 AddEventHandler 메서드를 사용하여 delegate 를 추가합니다.
ei.AddEventHandler( button1, onLoadD );
ei.AddEventHandler( button1, d );
ei.AddEventHandler( button1, offLoadD );
우리는 Button_Click Event 를 제거한 상태입니다. 즉, 아무 반응이 없어 지지요.
Event 는 += 연산을 통해 여러 개의 delegate 를 추가할 수 있습니다.
바로 그것입니다.
Click 이벤트를 제거한 후 On Loding Event -> Click Event -> Off Loading Event 가 발생하도록 Event 를 조작하게 되었습니다.
 
그렇게 많은 라인을 추가하지 않았음에도, 리플랙션을 통해 이벤트를 조작함으로써 해결하였네요^^
 
대부분의 사람들은 “리플랙션” 이라고 하면 느리다고 합니다. 몇몇 사람들은 리플랙션 코드를 보는 순간, “느려집니다” 라고 단언합니다.
리플랙션을 사용하면 최대 1000배의 성능저하가 있다고 들은바 있습니다. 하지만 그것은 Invoke 를 하였을 때의 이야기 입니다.

하지만, Late-Binding 이므로 분명 눈에보이지는 않지만 성능저하는 있을 수 있습니다.
이것을 어떻게 적절히 사용하느냐가 문제인 것 같습니다. 분명 리플랙션은 OOP 의 상속성과 객체지향성을 거스르고, 퍼포먼스를 떨어 뜰일 수 있을테니까요.
 
오늘도, 내일도.. 바쁘시더라도 모두 좋은 하루 되시길 바랍니다. ^^
Posted by 땡초 POWERUMC

댓글을 달아 주세요




간단히 Request를 받아서 Xslt 로 처리 하였습니다. VS.NET 2003 에서 실행 가능합니다.


프로그램 내에 인증키를 넣어주세요:
Posted by 땡초 POWERUMC
TAG c#, Open API

댓글을 달아 주세요

CDO.IMessage mail=new CDO.MessageClass();
CDO.Configuration iConfig=new CDO.ConfigurationClass();
ADODB.Fields flds=iConfig.Fields;
flds[CDO.CdoConfiguration.cdoSendUsingMethod].Value=2; //인증
CDO.CdoConfiguration.cdoSMTPServer].Value="smtp.mail.yahoo.co.kr";
flds[CDO.CdoConfiguration.cdoSendUserName].Value="umjunil";
flds[CDO.CdoConfiguration.cdoSendPassword].Value="xxxxxx";
flds[CDO.CdoConfiguration.cdoSMTPAuthenticate].Value=1;//보내는메일인증
ds.Update();

mail.Configuration=(CDO.Configuration)iConfig;
mail.To=tomail;????
mail.From=frommail;
mail.Subject=title;
mail.HTMLBody=content.Replace("\r\n","
");
try{
mail.Send();
}

catch (Exception e) { 예외 처리 }

finally { mail=null; iConfig=null; }

야후에서는 무료로 smtp 가 지원된다.
위 빨간부분은 보내는 메일의 인증이다.
없어도 되는 서버가 있는가 하면... 저 설정을 하지않으면
보내는 메일을 인증할 수가 없다.

접근에 대한 인증도 있지만, 보내는 메일의 인증이란 부분도 있었다.
야후 하믄서 처음 알앗다...
저것때매 2일정도를 삽질 했다는...;;

Posted by 땡초 POWERUMC
TAG c#

댓글을 달아 주세요