'reflection'에 해당되는 글 2건

  1. 2007.06.02 참조된 어셈블리의 Reflection 트러블 슈팅
  2. 2007.05.17 Reflection 을 통한 Event 제어
우리는 가끔씩 리플랙션을 사용한다.
사용하는 목적 또한 다양하고 리플랙션의 장점 또한 무궁무진 하다.
 
오늘 이야기할 내용은 어셈블리는 리플랙션을 하는데 있어 무척 도움이 될만한 내용을 살펴보겠다.
 
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

댓글을 달아 주세요