동적 이벤트 처리.. 어디에 써먹으면 좋은까..
 
우선 이에 앞서 리플랙션 이야기를 잠시만 언급하겠습니다.
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

댓글을 달아 주세요