RSS구독하기:SUBSCRIBE TO RSS FEED
즐겨찾기추가:ADD FAVORITE
글쓰기:POST
관리자:ADMINISTRATOR
WSE3 는 VisualStudio 2005 버전까지만을 공식적으로 지원합니다. 2008 이후 버전에는 WCF가 탑재되면서 WSE의 기능을 대부분 수용하고 있기 때문이 아닌가 짐작되네요-_ㅠ

하지만, 기존 프로젝트를 유지보수 해야한다던지, 당장 WCF를 도입하기 어려운 경우지만 VS2008을 사용하고 있다면, 단순히 WSE3 를 설치하는것만으로 사용할 수가 없는데, 이 문제는 WSE의 기능들이 VisualStudio에 Addin 형태로 지원되는데, 이 Addin의 버전이 VS2005 기준이기 때문이죠.

◎ 문제 해결을 위한 방법.

C:\ProgramData\Microsoft\MSEnvShared\Addins\WSESettingsVS3.Addin

위 파일에 WSE의 애드인 정보가 설치되어 있습니다. 내용을 열어서 확인해보면,


<?xml version="1.0" encoding="utf-16" standalone="no"?>
<Extensibility xmlns="http://schemas.microsoft.com/AutomationExtensibility">
  <HostApplication>
    <Name>Microsoft Visual Studio Macros</Name>
    <Version>8.0</Version>
  </HostApplication>
  <HostApplication>
    <Name>Microsoft Visual Studio</Name>
    <Version>8.0</Version>
  </HostApplication>
  <Addin>
    <FriendlyName>WSE Settings 3.0...</FriendlyName>
    <Description>WSE Settings Tool.</Description>
    <Assembly>C:\Program Files\Microsoft WSE\v3.0\Tools\WseSettingsVS3.dll</Assembly>
    <FullClassName>WseSettings.Connect</FullClassName>
    <LoadBehavior>1</LoadBehavior>
    <CommandPreload>0</CommandPreload>
    <CommandLineSafe>0</CommandLineSafe>
  </Addin>
</Extensibility>


위와 같은데, 두군데, <Version>8.0</Version> 이라는 앨리먼트가 보이죠? Visual Studio는 2005, 2008, 2010 버전들이 순서대로 8, 9, 10 과 같은 넘버를 가집니다. 따라서 위의 애드인 정보는 2005에 맞도록 되어 있기때문에, 2008에서 사용하려면, 9.0으로, 2010에서 사용하려면 10.0 으로 수정하면 되는것이죠.

수정후에 해당파일을 Addin 을 읽어 들이는 폴더에 복사해 넣습니다. 기본적으로 내문서의 하위 폴더에 Addin 폴더가 VisualStudio 가 읽어들이는 폴더중에 하나입니다.

C:\Users\[윈도우 사용자명]\Documents\Visual Studio 2008\Addins

저는 윈도우7을 사용하고 있기때문에 위 폴더로 되어 있는데, 다른 OS면 다른 경로 읽수도 있습니다. 정확하게 알아 보시려면, Visual Studio 의 옵션의 '환경>추가기능/매트로 보안' 항목을 보시면 됩니다. (한군데가 아닌, 몇군데가 되지요.)

사용자 삽입 이미지


복사한 이후에 VisualStudio를 재시작하면, 위와 같이 WSE Addin이 활성화 되고, 솔루션 탐색기의 프로젝트명을 우클릭하면, 해당 툴을 사용할 수 있습니다~ :)
2011/12/16 14:04 2011/12/16 14:04
http://lemonwidz.com/tc/trackback/26
닷넷에는 기본적으로 몇가지 DB 프로바이더가 제공되고, 제공되는 것이 외의 것은 OleDB등의 방법으로 접속할 수 있습니다. 하지만, 포스트그레스는 기본 방법으로는 접속할 수 없지요.

하지만, 검색을 해보니..


위 링크에서 포스트그레스 접속을 위한 오픈소스 프로젝트가 진행중이고, 어셈블리 두개만 프로젝트에 참조하여 쉽게 접속할 수 있습니다. 관심이 있으신분은 위 링크를 찬찬히 살펴보시면, 해당 어셈블리는 모두 닷넷으로 작성되어졌고, 소스도 지원된다는것을 알 수 있습니다.

사용자 삽입 이미지


저는 위의 소스에서 Npgsql2.0.11.92-bin-ms.net.zip 파일을 받아서 설치했는데, 제가 받은것이 닷넷2.0 기준이고, 나머지 버전 정보가 붙은것이 해당 프레임웤버전으로 작성된것이 아닐까 생각됩니다. (확인은 못해봤네요-_-)

여튼 위 파일을 받으면 몇개의 파일이 압축되어 있는데, 그 중

Npgsql.dll, Mono.Security.dll
위 두 파일만 참조하여 사용하면 됩니다.
실제 구현부는 기존의 방법과 같이 커맨드를 생성하고, 리더를 통해서 읽어오던지 어댑터를 통해서 한번에 부어 담던지 별다를바 없습니다.
public DataTable InvokeQuery(string connectionString, string queryString)
{
    NpgsqlConnection con = new NpgsqlConnection(connectionString);
    try
    {
        con.Open();
        NpgsqlCommand cmd = con.CreateCommand();
        cmd.CommandText = queryString;
        cmd.CommandType = System.Data.CommandType.Text;
        NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(cmd);
        DataTable result = new DataTable();
        adapter.Fill(result);
        return result;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        con.Close();
        con = null;
    }
}

제가 구현한 코드는 위와 같습니다.
2011/12/12 09:05 2011/12/12 09:05
http://lemonwidz.com/tc/trackback/25
- 시작전 잡설 : 참으로 오랜만의 포스팅이네요^^; 요 근래 두어달간 몇가지 흥미진진한(그만큼 구글링도 엄청한) 프로젝트를 진행하느라 좀 바빴는데, 다시 블로그에 관심을 가져줄까 합니다. (뭐, 기다리시는분은 없었겠지만-_-;;)

- 상황 : 부서내 다른팀(개발이 아닌)에게 업무에 사용할 툴을 만들어 주는데, 이것저것 참조된 어셈블리를 같이 배포했더니, 불편하다고 파일하나만 가지고 실행할 수 없겠냐고 합니다. (엑셀에, DB접속에, 각종 UI들까지 몇가지 있깄했죠.) 그래서 구글님께 물어보니, 아니나 다를까 ILMerge 라는 툴이 있었고, Microsoft 공식 사이트에서 배포중이었습니다.

- 다운로드 : ILMerge.msi

- ILMerge 를 설치하면 기본적으로 c:\Program Files\Microsoft\ILMerge 에 설치가 되며, ILMerge.exe 라는 단독 콘솔형 실행파일 하나만 존재합니다.

- 기본적인 사용법만을 알려드리자면, 메인이 될 dll 명을 맨 앞에 적어주고, 나머지를 뒤에 줄줄이 적어주면 됩니다. 그리고 결과물이 생성될 파일명을 /out 파라메터와 함께 적어줍니다.

ILMerge.exe "/out:MergedAssembly.exe" "MergeTest.exe" "c:\Program Files\Microsoft Visual Studio 9.0\Visual Studio Tools for Office\PIA\Office11\Microsoft.Office.Interop.Excel.dll"

- 위는 엑셀을 참조추가한 경우에 인터롭어셈블리를 같이 배포하지 않아도 되도록 묶어본 예입니다.(실제 사용시에는 배치파일로 만들어두면 편하겠죠)

- 이 툴이 유용한것중 하나는 pdb 파일도 함께 생성해주기 때문에, 디버깅에도 유용하다는 점입니다.

- 또 한가지, 이 툴의 실행파일또한 닷넷 어셈블리이기 때문에 참조추가하여 사용할 수 있는데, 참조추가하게 되면, ILMerge.exe 가 제공해주는 모든 기능을 프로그래밍적으로 사용할 수 있습니다. (해당 내용은 설치시 제공되는 문서에 자세히 나와있습니다. 본 포스팅에서는 다루지 않습니다.)

- 제공되는 문서의 서론에서 언급하고 있는것들 몇가지
  - ILMerge.exe 는 여러개의 닷넷 어셈블리를 하나로 묶어준다.
  - 비관리 코드를 사용하는 일부 어셈블리는 묶지 못할 수도 있다.
  - PEVerify 유틸(닷넷 기본제공)을 통해 어셈블리가 유효한지 검증한 후 사용할 것을 권장한다.
  - 윈도우즈 플랫폼만 지원한다.

- 사용법또한 간단해서 소개하는 수준에서 마무리합니다. 몇몇 돌발상황(?)에 유용하게 쓰였으면 하네요^^
2009/07/27 13:52 2009/07/27 13:52
http://lemonwidz.com/tc/trackback/24

텍스트 박스에 값을 입력하지 않으면 배경에 특정 메시지를 보여주고, 값을 입력하면 보이지 않게 해달라는 요청을 받았습니다.

그런데!!

텍스트박스는 OnPaint를 기본적으로 쉽게 사용할 수 없습니다. SetStyle 메서드에서 UserPaint 플래그를 설정하면 가능하지만, 글씨 같은 부분도 모두 처리해 줘야되기 때문에 여간 번거로운게 아닙니다. 제가 원하는건 오직 배경에 뭔가 처리!’ 그것 뿐이라구요..ㅠㅡㅠ

 

그래서 WndProc 를 재정의해서 이벤트를 받아오는 방법을 사용했습니다. 주의라고 까진 아니지만, 유심히 보실 부분은 WM_KILLFOCUS 도 처리해주고 있는것인데요. 텍스트 박스를 멀티라인 지원으로 해놓으면, WM_PAINT 가 발생을 하지 않더군요. 그래서 WM_PAINT 에서도 처리를 해주었습니다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace TextBoxPaintSample.Controls
{
    public class TextBoxExt : TextBox
    {
        private string waterMarkText = string.Empty; // 워터마크로 사용할 문자열
        private Color waterMarkColor = Color.Gray;   // 워터마크로 사용할 문자색

        protected override void WndProc(ref Message m)
        {
            // base.WndProc 중복 호출을 피하기 위해서
            bool isCallAlready = false;

            // WM_PAINT 메세지를 받아서 처리
            if (m.Msg == 0x000F) // WM_PAINT = 0x000F
            {
                // 원래 처리해야될 로직을 먼저 호출해서 처리해줌
                base.WndProc(ref m);
                isCallAlready = true;

                DrawWaterMarkText();
            }
            // Multiline == true 일때는 포커스 빠질때 WM_PAINT가 발생 안하므로
            else if (m.Msg == 0x0008 && this.Multiline) // WM_KILLFOCUS = 0x0008
            {
                DrawWaterMarkText();
            }

            if (false == isCallAlready)
                base.WndProc(ref m);
        }

        // 텍스트박스의 크기를 계산해서 워터마크를 그려줌
        private void DrawWaterMarkText()
        {
            if (string.IsNullOrEmpty(this.Text) &&
                false == string.IsNullOrEmpty(this.WaterMarkText) &&
                this.IsHandleCreated &&
                false == this.Focused &&
                this.Visible)
            {
                using (Graphics g = Graphics.FromHwnd(this.Handle))
                {
                    // 텍스트의 vertical 정렬을 하기 위한 계산들
                    StringFormat sf = new StringFormat();
                    float textHeight = g.MeasureString(this.WaterMarkText, this.Font, this.Width, sf).Height;
                    float textY = ((float)this.Height - textHeight) / (float)2.0;
                    RectangleF bounds = new RectangleF(
                        0, textY, (float)this.Width, (float)this.Height - (textY * (float)2.0));

                    g.DrawString(this.WaterMarkText, this.Font, new SolidBrush(this.WaterMarkColor), bounds, sf);
                }
            }
        }

        public string WaterMarkText
        {
            get { return waterMarkText; }
            set { waterMarkText = value; }
        }

        public Color WaterMarkColor
        {
            get { return waterMarkColor; }
            set { waterMarkColor = value; }
        }

    }
}


실행 결과
사용자 삽입 이미지

이러한 코드를 바탕으로 텍스트 박스의 배경으로 이미지를 넣는다던지 하는 처리도 가능하리라 생각됩니다.

소스코드 전체를 첨부합니다. VS2005에서 작성 및 테스트되었습니다.


ps. 사실, 이 포스트의 내용은 제가 원래 작성하고자 했던 내용은 아닙니다만, 문제해결의 과정을, 혹은 진짜 이 내용이 필요하신분을 위해서 먼저 작성하였습니다. 다음 포스트에서는 TextBox를 상속받을 수 없는경우, 즉 다른 사람이 만든 TextBox에서 컨트롤만 얻어올 수 있을때의 대처 방법을 포스팅 하겠습니다.


2009/04/08 21:23 2009/04/08 21:23
http://lemonwidz.com/tc/trackback/23
kwangho  | 2009/04/28 16:44
오오~~ 훈스타고 왔는 데 워터마크 기능이되네요.
잘은 모르겠으나 재정의하는 거 같은데 신기하네요
지송  | 2010/07/15 00:06
안녕하세요. ^^;
다름이 아니오라. 위 워터마크효과를 이용해서 제 텍스트박스기능에 붙였는데...
다른창이 텍스트박스 위로 올라갔다 내려갔다를 몇차례 반복하면 잔상이 남게 되는 현상이 발견되어서
해결 방법을 찾아서 댓글 남겨드립니다.

TextRenderer.DrawText(g, waterMarkText, Font, Rectangle.Truncate(bounds), waterMarkColor, BackColor, TextFormatFlags.Default);

글을 이렇게 쓰시면 잔상이 생기진 않더라고요. ^^ 더운 여름 잘 보내시고요. 컨트롤 잘쓸께요.
유쾌한냐옹이  | 2012/02/28 09:33
훈스에서 퇴근5초전 님 추천받아왔는데 정말 유익하군요.

왔사 겁내 저한테 필요한 정보에욬ㅋㅋㅋㅋㅋㅋㅋㅋㅋ
have a gd day 에용~
멋져부러
유쾌한냐옹이  | 2012/02/28 10:33
이미지를 올려놓을 경우 엄청 뻔쩍거리네요..;;ㅠㅠ
리플렉션을 통해서 타입을 컨트롤하는 것은 일반 개발자(컴파일러/프레임웍 등의 개발자가 아닌)에게는 거의 필요가 없거나, 사용을 권장하지 않는 기능입니다. 하지만, 실무를 접하다 보면 부득이하게 필요한 경우가 발생하기 마련이고, 제한적으로 사용하면 도움이 되기에 이러한 팁을 작성해 보았습니다.


// 테스트를 위한 클래스
public class IHavePrivateMember
{
    public IHavePrivateMember()
    {
        this.mySecretName = "Lemon";
    }

    private string mySecretName;
    private void PrintSecretName()
    {
        Console.WriteLine("My secret name is {0}.", this.MySecretName);
    }

    private string MySecretName
    {
        get { return this.mySecretName; }
    }
}


위와 같은 클래스의 private 멤버인 mySecretName 변수, MySecretName 프러퍼티, PrintSecretName 메서드에 접근해 보도록 하겠습니다.

IHavePrivateMember 클래스의 타입을 얻어오는 것에서부터 리플렉션이 시작됩니다. CLR에서 관리되는 모든 타입은 System.Type 클래스의 인스턴스인데, 이 클래스는 객체의 타입을 유지, 관리해주는 많은 멤버들을 가지고 있습니다. 그 중 GetMembers 메서드를 통해서 해당 타입의 멤버들을 MemberInfo 클래스로 가져올 수 있습니다.

MemberInfo[] privateMembers = typeof(IHavePrivateMember).GetMembers(
                BindingFlags.Instance | BindingFlags.NonPublic);


MemberInfo 클래스는 다음과 같은 상속 구조를 가지면서 다른 구체화된 멤버정보 클래스들의 베이스가 됩니다.

사용자 삽입 이미지
GetMembers는 위에서 보이는 MemberInfo를 상속받는 클래스들을 MemberInfo클래스의 배열로 리턴해줌으로써 MemberInfo.MemberType을 살펴보고 이를 적절히 캐스팅해서 사용할 수 있습니다.

if (privateMembers[i].MemberType == MemberTypes.Field)
{
}


이와 같이 타입을 확인후에 해당 클래스로 캐스팅하여 적절한 액션을 취하면 되겠지요. 필드를 대상으로 취할 수 있는 액션은 값을 가져오는 것이니깐 GetValue 정도의 메서드가 적절할 듯 싶습니다. 그런데, GetValue 메서드는 파라메터를 하나 요구하는데요, 이 멤버가 Static 멤버가 아닌, 인스턴스멤버이기 때문에 값을 가져오려면 반드시 인스턴스가 필요하기 때문입니다.

IHavePrivateMember secret = new IHavePrivateMember();
Console.WriteLine("멤버변수 {0} / 값 : {1}",
    privateMembers[i].Name,
    ((FieldInfo)privateMembers[i]).GetValue(secret)
);


이렇게 값을 얻어올 실제 인스턴스를 넘겨줍니다.

하나를 알면 열을 안다고 했던가요? 나머지 타입들도 같은 맥락으로 사용하실 수 있습니다. 메서드는 실행이니까 Invoke, 프러퍼티도 값을 가져오니까 GetValue 이런식으로 사용하시면 됩니다.

콘솔응용프로그램으로 실행되는 전체 소스코드는 아래와 같습니다.

using System;
using System.Reflection;

namespace AccessPrivateMemberSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IHavePrivateMember secret = new IHavePrivateMember();

            // 멤버로 검색할 조건을 인자로써 지정해줌
            MemberInfo[] privateMembers = typeof(IHavePrivateMember).GetMembers(
                BindingFlags.Instance | BindingFlags.NonPublic);

            for (int i = 0; i < privateMembers.Length; i++)
            {
                if (privateMembers[i].MemberType == MemberTypes.Field)
                {
                    Console.WriteLine("멤버변수 {0} / 값 : {1}",
                        privateMembers[i].Name,
                        ((FieldInfo)privateMembers[i]).GetValue(secret)
                    );
                }
                else if (privateMembers[i].MemberType == MemberTypes.Property)
                {
                    Console.WriteLine("프러퍼티 : {0} / 값 : {1}",
                        privateMembers[i].Name,
                        ((PropertyInfo)privateMembers[i]).GetValue(secret, null)
                    );
                }
                else if (privateMembers[i].MemberType == MemberTypes.Method)
                {
                    MethodInfo mi = privateMembers[i] as MethodInfo;
                    if (mi != null && mi.GetParameters().Length == 0)
                    {
                        Console.WriteLine("메서드 {0} 실행 ----------", mi.Name);
                        mi.Invoke(secret, null);
                        Console.WriteLine("메서드 {0} 실행 끝 --", mi.Name);
                    }
                }
            }
        }
    }

    // 테스트를 위한 클래스
    public class IHavePrivateMember
    {
        public IHavePrivateMember()
        {
            this.mySecretName = "Lemon";
        }

        private string mySecretName;
        private void PrintSecretName()
        {
            Console.WriteLine("My secret name is {0}.", this.MySecretName);
        }

        private string MySecretName
        {
            get { return this.mySecretName; }
        }
    }
}


결과
메서드 PrintSecretName 실행 ----------
My secret name is Lemon.
메서드 PrintSecretName 실행 끝 --
메서드 get_MySecretName 실행 ----------
메서드 get_MySecretName 실행 끝 --
메서드 Finalize 실행 ----------
메서드 Finalize 실행 끝 --
메서드 MemberwiseClone 실행 ----------
메서드 MemberwiseClone 실행 끝 --
프러퍼티 : MySecretName / 값 : Lemon
멤버변수 mySecretName / 값 : Lemon
계속하려면 아무 키나 누르십시오 . . .

2009/03/31 20:27 2009/03/31 20:27
http://lemonwidz.com/tc/trackback/21
공통 컴포넌트를 개발하다 보면, 이벤트 순서를 제어하고 싶을 때가 있습니다. 제가 등록하지 않은(누군가가 먼저 등록한) 이벤트를 말이죠.

저는 어떤 경우냐면, 공통 컴포넌트 개발팀에서 만든 A 컴포넌트가 있고, 이걸 저희 팀에 맞게 제가 A를 이용해서 B라는 일종의 헬퍼 컴포넌트를 만듭니다. 그런데, 기존 팀원들은 A를 이미 다른 곳에 (많이)적용한 후 라는 거죠.

여기서 문제가 발생하게 됩니다. A 컴포넌트의 특정 이벤트는 제가 만든 B 컴포넌트에서만 사용해야 된다거나, A컴포넌트의 특정이벤트를 다른 팀원이 사용하고 있다 하더라도, 꼭 B 컴포넌트에서 먼저 호출해야 되는 경우가 생깁니다.

그렇다고 기존에 A 컴포넌트를 사용했던 클래스를 하나하나 수정하거나, A컴포넌트 자체에 대한 변경을 요청할 수도 없는 상황입니다.

자, 이런 드물지만 절박한 상황에서 우리의 리플렉션님을 사용하게 되는 거죠.

아래에서 Form 클래스의 MouseClick 이벤트를 가로채는 예제를 하나하나 만들어 가면서 설명하겠습니다.

[코드 1]
using System;
using System.Windows.Forms;

namespace EventCollectionSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.Text = string.Empty;
            this.MouseClick += new MouseEventHandler(Form1_MouseClick);

            EventChanger();
        }
        private void Form1_MouseClick(object sender, MouseEventArgs e)
        {
            this.Text += "0";
        }
    }
}


[코드1]은 단순히 폼에 마우스 클릭이벤트가 일어났을 때, 타이틀바에 ‘0’을 계속해서 찍어주는 윈도우폼입니다.

이 코드중 생성자에 등록되는 MouseClick 이벤트를 가로채서 다른 이벤트 핸들러를 먼저 호출하도록 해보겠습니다. EventChanger 메서드를 주목해 주세요. MouseClick 이벤트가 등록된 이후(!)에 호출되는 메서드임을 주의 깊게 보시면서 이제 이 메서드의 내용을 채워보도록 하죠.

프로그램이 CLR에 의해 메모리에 로딩되게 되면, 객체들은 사용되지 않더라도 메모리(그중에서 힙)의 특정 부분에 객체의 정보(접근자, 멤버, 메서드 등등)를 기록해 놓습니다. 이 정보가 일종의 신상명세(?)라고 할 수 있는데, 이게 바로 System.Type 클래스의 인스턴스로서 System.Object 클래스의 GetType 메서드로 알아낼 수 있습니다.
(여기에 대한 자세한 설명은 길어지고, 본문의 내용과 살짝 거리가 있으니, 다음에 한번 다루도록 하겠습니다.)

어쨌든, Type 을 얻어오면, GetMethod 라는 메서드를 통해서 해당 타입에 속한 메서드를 가져올 수 있습니다. 자, 이벤트 목록을 한번 가져와 봅시다. 근데, 이벤트 목록은 어떻게 가져와야 될까요?

일단 우리가 이벤트를 가져오려고 하는 클래스를 봅시다. 이 클래스는 Form 클래스인데, 상속 트리를 거슬러 올라가면, System.ComponentModel.Component 클래스를 상속받고 있음을 알고 있습니다. 바로 이곳에 우리가 찾을 protected EventHandlerList Events 라는 프러퍼티가 있습니다. 요놈이 자손대대(!)로 사용되어 Form 클래스에서도 사용되는 것이지요.

잠깐 언급을 하자면, EventHandlerList는 다수의 이벤트를 효과적으로 관리하기 위해 사용하는 클래스입니다.

마지막으로 하나더, 프러퍼티는 런타임에서 get_, set_ 라는 접두어를 가지는 인자 없는 메서드로 바뀌는걸 알고 계십니까? 위의 protected EventHandlerList Events 는 실제 런타임시에 메서드로 변환이 됩니다. 아래와 같이 말이죠.

protected EventHandlerList Events
{
    get
    {
        if (events == null)
        {
            events = new EventHandlerList(this);
        }
        return events;
    }
}


        ↓ ↓ ↓

protected EventHandlerList get_Events()
{
    if (events == null)
    {
        events = new EventHandlerList(this);
    }
    return events;
}



자, 이제 어떤 메서드를 찾아야 하는지 알게 되었네요, “get_Events” 가 우리가 찾을 메서드 이름입니다.

자, 아래와 같이 메서드 정보를 얻어올 수 있습니다.

MethodInfo mi = this.GetType().GetMethod("get_Events", BindingFlags.NonPublic | BindingFlags.Instance);


아까 찾은 메서드명을 “get_Events”로 넘겨주는 것을 볼 수 있구요, 두 번째 인자는 Flag형태의 열거형인데, 꽤 많은 종류가 있으나, 두 종류만 설명 드리자면, NonPublic, PublicInstance, Static 이 크게 두 분류로 나뉠 수 있습니다. 클래스의 멤버는 크게 인스턴스멤버(Instance)인지, 정적멤버(Static)인지로 나뉠 수 있습니다. 그리고, 한정자의 경우 공개인지(Public), 비공개(NonPublic)인지로 나뉠 수 있습니다. 그래서 위의 두 분류 중 하나씩은 꼭 지정해 주셔야지, 멤버 검색에 성공할 수 있습니다. 지금은 Events는 비공개이고, 인스턴스멤버라는 것을 알고 있기 때문에 두 개만 한정적으로 지정해 주었습니다. 그러나, 공개여부나 이런 것을 알 수 없을 때라던지, 범용적인 목적으로 구성하실 때는 웬만한 조건을 다 걸어주시는 것이 좋겠죠.

자, 우리가 왜 메서드 정보를 얻어왔을까요? MethodInfo 클래스에는 Invoke 라는 강력한 메서드가 있기 때문에 위의 예에서처럼 비공개 메서드도 실행시킬 수 있습니다. Events 프러퍼티가 공개였다면, 이 고생(?)을 하지 않아도 되겠죠^^; 자 그럼 현재 폼의 실제 Events 값을 얻어보도록 하겠습니다.

EventHandlerList evtList = mi.Invoke(this, new object[] { }) as EventHandlerList;


첫 번째 인자는 메서드를 실행시킬 기준이 되는 인스턴스를 넘겨줍니다. 우리는 현재 폼의 Events를 얻고 싶은 거죠? 그래서 this를 넘겨줍니다. 두 번째 인자는 해당 메서드가 인자를 가질 때 넘겨줄 인자목록입니다. 아까 얘기했듯이 프러퍼티는 메서드로 변환될 때 인자 없는 메서드로 바뀐다고 했었죠? 그래서 비어있는 배열을 넘겨 줍니다. 마지막으로 Invoke 메서드는 반환 값을 object 로 넘겨주는데, 우리는 Events 프러퍼티가 EventHandlerList 클래스를 넘겨준다는 것을 알고 있습니다. 따라서 적절하게 캐스팅해줍니다.

이제, EventHandlerList에 대해서 잠깐 알아보겠습니다. EventHandlerList는 키, 값 쌍으로 구성되는 컬렉션인데요, 키로 object를, 값으로 Delegate를  사용합니다. 닷넷컨트롤의 경우 대부분의 이벤트가 여기에 모두 등록되어있습니다. 우리가 찾고자 하는 MouseClick도 마찬가지라는 거죠. 그런데 키값을 알아야 찾을 텐데, 키값은 어디에 있죠? 잠시 닷넷 프레임웍 소스의 Control.cs를 살펴보죠.

Control.cs 일부
private static readonly object EventMouseClick                = new object(); 
private static readonly object EventMouseDoubleClick          = new object();
private static readonly object EventMouseCaptureChanged       = new object(); 
private static readonly object EventMove                      = new object();
private static readonly object EventResize                    = new object();


일부만 보여드렸는데, 감이 오십니까? 네, 일단은 이놈도 비공개 이고, 정적이며, 값으로 객체 인스턴스를 사용하고 있네요. 그리고 네이밍룰이 Event+’이벤트이름’ 이라는 것도 알 수 있는데요, 나중에 범용클래스로 만들 때 써먹으면 좋겠네요^^;

비공개 이기 때문에 아까처럼 타입에서 찾아야 되는데요. 아까와는 접근 제한자가 틀리죠? 아까는 protected였기 때문에 자손대대로 내려와서 현재 클래스(Form을 상속받은)에도 있었지만, private은 해당 클래스인 Control에만 존재합니다. 즉 현재클래스(this)의 부모에게서 값을 얻어와야 합니다. 이때 써먹으라고 Type 클래스에는 BaseType 이라는 멤버가 있는데, 이 멤버는 현재 타입이 어떠한 타입을 상속받은 경우 해당 타입을 가리키게 되어있습니다. 자 그럼 우리의 클래스(this)는 Form -> ContainerControl -> ScrollableControl -> Control 이와 같이 총 네 번의 상속을 받으므로 아래와 같이 해줍니다.

FieldInfo fi = this.GetType().BaseType.BaseType.BaseType.BaseType.GetField("EventMouseClick", BindingFlags.NonPublic | BindingFlags.Static);


....=ㅂ=;; 완전 무식하게 예를 보여드렸습니다만, 실제 구현에서는 재귀호출로 탐색해나가는 구조면 좀더 깔끔할 듯 합니다. (예제는 예제일 뿐!!)
아까와 다르게 우리가 찾으려는 EventMouseClick 가 정적멤버이기 때문에 플래그 중에 Instance가 Static으로 바뀌었구요, 또한 우리가 찾을 놈이 변수이기 때문에 GetField 메서드를 사용했습니다. 반환 값은 FieldInfo 클래스이며, 메서드 사용은 GetMethod와 동일합니다.

아까 메서드의 Invoke를 사용해서 해당 메서드를 실행시켰다면, 필드는 실행이 아니라, 값을 얻어오는 것이죠? 따라서 GetValue라는 메서드를 사용합니다. 아까와 같이 해당 필드값을 가져올 인스턴스를 인자로 넘겨줍니다.

object keyMouseClick = fi.GetValue(this);


그러면 해당 인스턴스(this)의 원하는 필드(fi)의 값이 object로 반환됩니다.

자, 다시 조금 전의 상황을 다시 떠올려 봅시다.

특정 필드(그것도 private)의 값이 왜 필요했죠? EventHandlerList 의 키값으로 써먹기 위해서였죠. 그럼 이제 키값을 알았으니, 키값을 통해 MouseClick 이벤트의 Delegate를 얻어와 볼게요.

Delegate del = evtList[keyMouseClick];


아까 얻어온 this의 이벤트 리스트(evtList) 컬렉션에서 MouseClick 키값(fi.GetValue(this))으로 Delegate를 가져왔습니다. 시험 삼아서 한번 호출해 볼까요?

del.DynamicInvoke(this, null);


대리자를 직접 호출해줬더니, 우리가 처음 [코드1]에서 등록했던 이벤트 핸들러인 Form1_MouseClick 가 호출되는 것을 볼 수 있습니다.

이제 우리가 바꿔 치거나, 지우거나, 순서를 바꿀 이벤트를 찾았으니, EventHandlerList 클래스를 통해서 쉽게 요리가 가능해졌습니다. EventHandlerList에는 AddHandler, RemoveHandler 메서드가 있기 때문에, 특정 이벤트의 추가나 삭제를 쉽게 할 수 있습니다.

그럼 일단 기존 이벤트는 지우고,

evtList.RemoveHandler(keyMouseClick, del);


우리가 원하는 새로운 이벤트를 추가해줍니다. 우선 이벤트 핸들러로 사용할 메서를 만들고,

private void Form1_MouseClickV2(object sender, MouseEventArgs e)
{
    this.Text += "1";
}


추가해 줍니다.

evtList.AddHandler(keyMouseClick, new MouseEventHandler(Form1_MouseClickV2));


여기까지 하면, 우리가 새롭게 정의한 이벤트 핸들러로 교체되구요, 순서를 바꾸고 싶었다면, 다시 아까 삭제했던 이벤트를 추가해 줍니다. EventHandlerList 는 추가된 순서대로 처리하기 때문에, 새로운 이벤트를 뺐다가, 다른 뭔가를 넣고, 다시 넣게 되면 처리 순서가 바뀌겠죠.

evtList.AddHandler(keyMouseClick, del);


자, 이제 우리가 필요로 하는 단편적(!)인 기능은 완성됐습니다. 실행해서 결과를 보게 되면, Form1_MouseClickV2 가 먼저 실행되고, 나중에 Form1_MouseClick 이 실행되는 것을 볼 수 있습니다.

지금까지 작성한 코드는 아래와 같습니다.

완성된 EventChanger 메서드
private void EventChanger()
{
    // 메서드(프러퍼티) 정보를 가져옴
    MethodInfo mi = this.GetType().GetMethod("get_Events", BindingFlags.NonPublic | BindingFlags.Instance);

    // 메서드정보와 인스턴스를 통해 실제 메서드를 실행시켜 결과 값을 받음
    EventHandlerList evtList = mi.Invoke(this, new object[] { }) as EventHandlerList;

    // 필드 정보를 가져옴
    FieldInfo fi = this.GetType().BaseType.BaseType.BaseType.BaseType.GetField("EventMouseClick", BindingFlags.NonPublic | BindingFlags.Static);

    // 필드 정보와 인스턴스를 통해 실제 값을 가져옴
    object keyMouseClick = fi.GetValue(this);

    // 이벤트 목록에서 등록된 이벤트를 가져옴
    Delegate del = evtList[keyMouseClick];

    // 테스트를 위해 실행시켜봄
    // del.DynamicInvoke(this, null);

    // 기존 이벤트 제거
    evtList.RemoveHandler(keyMouseClick, del);

    // 새로운 이벤트 추가
    evtList.AddHandler(keyMouseClick, new MouseEventHandler(Form1_MouseClickV2));

    // 기존 이벤트 다시 추가(순서변경)
    evtList.AddHandler(keyMouseClick, del);
}


리플렉션을 통한 비공개 메서드, 필드에 대한 접근이 이 메서드의 주요 내용입니다. 이 메서드의 모든 과정은 예외 처리나, 다른 클래스를 넘겼을 때의 범용적인 처리는 전혀 되어있지 않은데요, 이런 부분은 직접 처리하실 수 있을 거라고 생각합니다. 다음 기회에 시간이 되면 범용적인 클래스로 만들어서 올려보겠습니다.

긴 글 읽어 주셔서 감사합니다.

2009/03/19 09:50 2009/03/19 09:50
http://lemonwidz.com/tc/trackback/18
지송  | 2009/04/02 16:19
아하.. 여기가 레몬님 블로그구낭..

이벤트 검색하다 즐겨찾기로 저장해둔 곳이... 여기였네용.

하하.... 좋은 하루 되세요
레몬  | 2009/04/02 20:00
하하^^;;
지송님께서 찾아주셔서 리플까지 남겨주시니 감사합니다^^;
뽀씰  | 2010/05/28 10:28
좋은 정보 얻어갑니다 ^^ 감사해요~
joy  | 2012/05/02 11:19
어려운 내용임에도 불구하고 쉽고 재미있게 이해하게됬군요.
재미있게 읽었습니다. ^^
C# Coding Standards and Best Programming Practices ( C# 코딩 표준과 좋은 프로그래밍 습관 )

이라는 제목의 닷넷 스파이더팀( http://www.dotnetspider.com )의 글을 번역한 글입니다.

작년 회사에서 팀원들 대상으로 세미나할때 사용했던 문서인데, 하드에 짱박혀있는것 보단, 많은 분들이 보셨으면 해서 올립니다.

이글의 내용중 '권고안'은 말그대로 Let's 의 의미이며, '역주'라고 작성된 이외의 모든 내용은 닷넷 스파이더 팀의 의견임을 미리 밝혀둡니다. (사실 제 의견과 좀 틀린 부분도 있어서요 =ㅂ=)

목차는 아래와 같습니다.

01. 서문
02. 저작권에 관해
03. 이력관리
04. 소개
05. 이문서의 목적(purpose)
06. 팀 내에서 표준을 만드는 방법
07. 명명규칙과 표준
08. 들여쓰기, 공백
09. 좋은 프로그래밍 습관
10. 아카텍처(Architecture)
11. ASP.NET
12. 주석
13. 예외처리

조금이나마 도움이 되었으면 합니다. (_ _)
2009/03/10 08:18 2009/03/10 08:18
http://lemonwidz.com/tc/trackback/16
꼬기얌얌얌  | 2009/03/10 09:26
좋은 자료 감사합니다..^^
(훈스닷넷에서 답변했던 내용인데 의외로 헷갈려하는분도 있을것 같아서 포스팅으로 옮깁니다.)


ref 키워드는 메서드의 인자로 사용할 수 있는데, 이를 사용하면 값형식이든, 참조형식이든 인자로 넘긴값을 메서드에서 사용 후, 변경된 값을 다시 그대로 받을 수 있습니다.
ref 키워드 없이 호출을 하게되면, 메서드에 복사본이 넘어가게되어 메서드 내에서 변경되더라도 메서드가 끝난 이후에는 원래값(넘길때 주었던)이 그대로 남아 있게 됩니다.

여기서 참조형식을 넘겨줄때 의문이 생기는데요, 참조형식은 원래 부터가 참조하는 포인터를 넘겨주기때문에 어짜피 메서드에서 값을 바꾸고 메서드 종료가 되더라도 변경된 값이 유지되는데, ref 키워드를 쓴다한들 참조형식에서는 무슨의미가 있냐? 라는 의문이 생깁니다.

위의 설명에서도 잠깐 나왔지만, 참조형식이 메서드의 매개변수로 넘어과정을 보자면

public class SampleClass { /* 클래스 내용 */ }
public static void Main()
{
    SampleClass sampleClass = new SampleClass();
    WorkWithSampleClass(sampleClass);
}
public static void WorkWithSampleClass(SampleClass obj)
{
}


이와 같은 코드가 있다고 할때, SampleClass sampleClass = new SampleClass(); 처럼 객체 생성이 되면, 아래 그림처럼 실제 객체의 내용은 힙에 생성되고, 그 힙을 가리키는 포인터가 스택에 생성됩니다.
사용자 삽입 이미지
그리고 WorkWithSampleClass(sampleClass); 를 통해 메서드가 호출될때는 아래 그림과 같이 스택에 있는 참조가 복사되어 메서드의 매개변수로 넘어갑니다.
사용자 삽입 이미지
당연히 복사되었으므로 포인터가 가리키는 힙의 객체는 같습니다. 따라서 메서드가 끝난다하더라도, 스택의 복사본만 파괴될뿐, 힙내의 객체는 여전하므로, 힙의 객체 원본을 수정한것은 메서드 호출이 끝나더라도 남아있게 되는것이죠.

그러나,
public class SampleClass { /* 클래스 내용 */ }
public static void Main()
{
   SampleClass sampleClass = new SampleClass();
   WorkWithSampleClass(sampleClass);
}
public static void WorkWithSampleClass(SampleClass obj)
{
    obj = new SampleClass();
}

위의 코드처럼 호출된 메서드 내에서 파라메터로 넘어온 obj 에 직접 다른 객체를 대입하면 어떻게 될까요?
아래 그림과 같은 변화가 메모리상에서 일어납니다.

사용자 삽입 이미지
아까, 메서드 호출시에 복사본이 넘어온다고 했고, 여기에 새로운 객체를 생성해서 대입했으니, 당연한 결과겠죠?

자, 이렇게 됬을때, 메서드가 종료되면? 네, 스택에 있는 복사본은 해당 스코프가 끝났기 때문에 소멸되고, 힙에 있던 객체역시 참조하는 곳이 없게 되므로, 가비지 수집의 대상이 됩니다. 한마디로 둘다 없어지는 거죠.

그렇기 때문에, 메서드가 끝나면, 원래있던 객체를 다시 참조하게 되는것입니다.



자, 이제 ref 키워드를 통해 넘기면..?
사용자 삽입 이미지
맨처음 나왔던, 그림과 동일합니다. 메서드의 매개변수로 스택에 있는 참조를 그대로 넘겨줍니다. 메서드 내에서도 이와 같은 메모리 구조를 유지하고 있기 때문에, 아까 나왔던 매개변수로 전달된 참조에 직접 다른 객체를 대입하게 되면 아래 처럼 됩니다.
사용자 삽입 이미지
이제 아까(ref 키워드를 사용하지 않았을때)와 다른 형세가 되었네요. 기존의 객체는 참조하는곳이 없어져서 가비지 수집의 대상이 되고, 새로 생성한 객체가 스택에 있는 참조의 참조대상이 됩니다.
당연히 메서드 종료 후에도 참조는 새로는 생성한 객체를 가지키게 되겠죠.

간단한 내용이지만, 설명이 굉장히 길어졌는데요. 요약하자면..

ref 키워드로 참조형식을 넘겨주면 참조형식에 직접 대입하여 다른 객체로 바꿀 수 있다.

(아래는 훈스닷넷에 답글을 달면서 만든 샘플 소스입니다.)
2009/03/05 11:10 2009/03/05 11:10
http://lemonwidz.com/tc/trackback/14
HOONS  | 2009/03/05 13:10
좋은글 잘 보았습니다(^^)
레몬  | 2009/03/05 19:29
어익후.. 제 블로그 첫번째 리플의 주인공이시네요^^;;
감사합니다~
anydeveloper  | 2011/02/23 22:57
다소 혼동스러운 개념이었는데, 정말 명확하게 잘 정리해 주셨네요. 좋은 내용 공유해 주신 것에 대해 진심으로 감사드립니다.^^
?? 를 기준으로 좌측값이 null 이라면 우측값을, null 이 아니라면 좌측값을 반환한다.

SQL문의 ISNULL과 같은 기능을 수행한다.

string value = null;
string result = value ?? "value is null";
Console.WriteLine(result);


아래의 코드와 동일하다.

string value = null;
string result = value == null ? "value is null" : value;
Console.WriteLine(result);


결과는 둘다

value is null

.
.
.

c#을 2년동안 나름 열심히 한다고 했는데.. 이걸 이제서야 알게 되다니.. 부끄럽다..ㅠㅡㅠ
isnull 같은게 있으면 좋겠다고 늘 생각했었는데.. 후..
2009/03/02 09:23 2009/03/02 09:23
,
http://lemonwidz.com/tc/trackback/13
from.Nyaonge's Home  2011/01/20 09:33
?? 연산자가 있는지 지금 처음 알았네~ ?? 를 기준으로 좌측값이 null 이라면 우측값을, null 이 아니라면 좌측값을 반환한다. SQL문의 ISNULL과 같은 기능을 수행한다. view plaincopy to clipboardprint? string v
지송  | 2009/04/17 20:06
오~ ... 저도 첨보는건데요. 항상 삼항연산자를 통해 반환 했는데

멋진게 있네요... 역시 레몬님 멋진글 잘봤습니다 ^^

주말 즐겁게 보내세요 ^^;
CLR Via C# 2nd Edition 을 읽는 도중 흥미로운 부분이 있어 기록해 놓는다.

요지는 관리 코드인 경우 IL -> JIT -> Native Code 단계로서 최종적으로 C++과 같은 Native 코드를 생산해 내지만, 중간 단계인 JIT 에서 속도의 저하가 일어난다 라고 사람들은 알고 있다. 하지만, JIT 의 경우 Native 코드로 컴파일할때 현재 실행환경(CPU, 32, 64비트 환경 여부 등)을 정확하게 파악하고 있으므로, 항상 해당 환경에 최적화된 Native Code 를 생산해준다..... 라는것! 일리 있어 보이는데?

믿기 힘들겠지만 필자를 포함한 많은 사람들은 비관리 어플리케이션보다 관리되는 어플리게이션이 더 빠르다고 생각한다. 이에 대한 근거는 많이 있다. 예를 들면, JIT 컴파일러는 IL코드를 네이티브 코드로 컴파일할 때 어플리케이션의 실행 관경에 대해서 비관리 컴파일러보다 훨씬 더 자세하고 정확하게 알고 있다. 다음은 관리 코드가 비관리 코드보다 성능이 우수할 수 있는 몇 가지 근거이다.

  • 컴파일 시에 JIT 컴파일러는 실행 환경이 펜티엄4 CPU 라는 것을 알 수 있고 또 펜티엄4 CPU라는 것이 확인되면 이에 최적화된 지시어를 이용해서 네이티브 코드를 생성함으로써 성능을 향상시킬 수 있다.

  • JIT 컴파일러는 특정 상황의 테스트 값 혹은 논리 연산의 결과를 실행 전에 이미 정확히 알 수 있다. 다음의 코드가 이런 예이다.

    if(numberOfCPUs > 1) {
    //...
    }

    이 경우 JIT 컴파일러를 호스팅하고 있는 시스템의 CPU가 하나라면 KIT 컴파일러는 해당 if 블록이 할상 실행되지 않을 것을 알 것이며, 따라서 전체 if 문을 네이티브 코드로 변황하지 않을 것이다. 이 경우 어플리케이션의 실행 코드는 호스팅하는 운영체계에 최적화된 코드이며 좀 더 작고 좀 더 빠른 네이티브 코드를 생성하게 된다.

  • CLR은 어플리케이션의 실행 패턴을 프로파일(profile)할 수 있으며 이에 따라서 어플리케이션 실행중에 IL 코드를 네이티브 코드로 다시 컴파일할 수 있다. 재컴파일된 네이티브 코드는 프로파일된 실행 패턴을 바탕으로 코드의 분기를 최적화하기 위해서 재정열된다.
- CLR Via C# 2nd Edition (Jeffrey Richter 저 / 송기수 역) 에서 발췌
2008/11/30 22:21 2008/11/30 22:21
http://lemonwidz.com/tc/trackback/5
전체 (23)
사진이야기 (4)
프로그래밍 (18)
  1. Nyaonge's Home  2011
    [C#] ?? 연산자(물음표 두개)
  1. 2012/03 (1)
  2. 2011/12 (2)
  3. 2009/07 (1)
  4. 2009/04 (1)
  5. 2009/03 (9)