'변수 리프팅'에 해당되는 글 2건

  1. 2009.08.19 Lambda Expressions in Silverlight #2
  2. 2009.08.19 Lambda Expressions in Silverlight #1
Silverlight2009.08.19 18:49
Lambda Expressions in Silverlight #1 포스팅과 이어지는 내용입니다.

 구체적인 예를 한 가지 들어보자면, Image Control을 배치 한 뒤 ImageOpened 되면 Image Control을 MouseLeftButtonUp 했을 때, 마우스의 포인트를 ListBox에 추가하는 기능을 구현한다고 가정 해 보겠습니다.
 위와 같이 구현하기 위해서는 이미지가 반드시 Opened 되야 만 MouseLeftButtonUp 이 발생하여야 하도록 하기 때문에 ImageOpened 이벤트 구현부에서 MouseLeftButtonUp 이벤트를 구현 한다면, 무리 없이 모든 기능을 구현할 수 있습니다.
int count = 1;
this.LogoImage.ImageOpened += (s, e) =>
{
    this.MousePositionListBox.Items.Clear();
    this.LogoImage.MouseLeftButtonUp += (obj, args) =>
    {
        this.MousePositionListBox.Items.Add(String.Format("{0} , Point : {1}",
            (count++).ToString(), args.GetPosition(this.LogoImage)));
    };
};

 아무 문제 없이 쉽게 구현 한 것 같지만, 여기에는 한 가지 중요한 문제가 존재합니다. 바로 Image Control의 Source가 변경 되는 경우, ImageOpened 가 또 일어나게 된다는 점이죠. 그렇게 되면, MouseLeftButtonUp 이벤트는 Image Control 의 Source가 변경 될 때마다 이벤트 구현부분인 람다식의 구현부가 계속 추가 되겠죠. 그럼 MousePositionListBox에는 Image Control이 변경 된 만큼 뜨게 됩니다. 

 이런 경우를 막기 위해서, 보통 람다식을 떼고 아래와 같이 코드를 입력하여 방지 할 수 있는데요,
Int32 count = 1; 

public MainPage()
{
    InitializeComponent();
 
    bool firstImageOpened = true;
    this.LogoImage.ImageOpened += (s, e) =>
    {
        this.MousePositionListBox.Items.Clear();
        this.count = 1;

        if (firstImageOpened)
        {
            this.LogoImage.MouseLeftButtonUp += new MouseButtonEventHandler(LogoImage_MouseLeftButtonUp);
            firstImageOpened = false;
        }
        else
        {
            this.LogoImage.MouseLeftButtonUp -= new MouseButtonEventHandler(LogoImage_MouseLeftButtonUp);
            this.LogoImage.MouseLeftButtonUp += new MouseButtonEventHandler(LogoImage_MouseLeftButtonUp);
        }
    };

void LogoImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    this.MousePositionListBox.Items.Add(String.Format("{0} , Point : {1}",        (count++).ToString(), e.GetPosition(this.LogoImage)));
}


 우선 count 변수를 맴버 변수로 바꿔야 했고, ImageOpened 가 처음 일어나는 것인지 여부를 체크하여 동일한 이벤트 구현부를 추가 또는 삭제 하는 작업을 추가했습니다. 이렇게 되면, Image Control의 Source가 변경 되어 ImageOpened 될 때 MouseLeftButtonUp에 등록되는 함수는 단 하나만 되게 됩니다.

 미리 예상을 하고 람다식을 사용하지 않았다면 상관이 없지만, 이미 사용한 뒤에 나중에 이런 문제를 발견한다면 (그나마 발견하면 다행이겠죠) 앞서 설명을 드렸던 Variable Lifting이 쓰인 경우 람다식을 떼어 내기가 여간 쉬운 것이 아닙니다. 람다식 블럭 내에서 사용 된 모든 변수를 맴버로 떼어내던가 하는 귀찮고, 정교한 작업을 해야 하기 때문이죠.

 그러나, 이런 점을 Variable Lifting을 그대로 이용하여 적용할 수 있는 방법이 있습니다. 람다식의 다른 표현을 사용하는 것이죠 :)
bool firstImageOpened = true;
MouseButtonEventHandler mouseHandler = null;
this.LogoImage.ImageOpened += (s, e) =>
{
    this.MousePositionListBox.Items.Clear();
    int count = 1;
 
    if (firstImageOpened)
    {
        mouseHandler = delegate(object sender, MouseButtonEventArgs args)
        {
            this.MousePositionListBox.Items.Add(String.Format("{0} , Point : {1}",
                (count++).ToString(), args.GetPosition(this.LogoImage)));
        };
        this.LogoImage.MouseLeftButtonUp += mouseHandler;
        firstImageOpened = false;
    }
    else
    {
        this.LogoImage.MouseLeftButtonUp -= mouseHandler;
        this.LogoImage.MouseLeftButtonUp += mouseHandler; 
    }
};
Posted by Min-gu, Kim
Silverlight2009.08.19 17:23
 안녕하세요. 오늘은 포스팅 할 주제는 C# 3.0의 언어적 특성인 부분이라서 Silverlight 뿐만 아니라, WPF든, WinForm이건 상관 없이 유용한 팁이 될 것 같습니다.

Variable Lifting
 제가 생각하는 람다식의 가장 큰 이점 중 하나는 바로, Variable Lifting 입니다.
 Variable Lifting 이란, 다음 코드와 같이 전역 변수가 아닌 블럭 내 지역 변수 임에도 람다식의 블럭에서도 사용이 가능한 것을 말합니다. 
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    int index = 0;

    this.TestButton.Click += (s, args) =>
    {
        index++;
        MessageBox.Show(index.ToString());
    };
}

 위 코드에서 int형 변수인 index는 MainPage_Loaded 함수의 블럭에 존재하는 지역 변수이지만, TestButton.Click 이벤트를 등록하는 람다식의 블럭에서도 사용이 가능합니다.

 어떻게 보면 그냥 당연하게 보일 수도 있는데요. 그럼 람다식이 아닌 형태로 코드를 타이핑 했다면 어떨까요?
Int32 index = 0;

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    this.TestButton.Click += new RoutedEventHandler(TestButton_Click);
}

void TestButton_Click(object sender, RoutedEventArgs e)
{
    this.index++;
    MessageBox.Show(this.index.ToString());
}


 보시는 바와 같이, index 변수를 전역으로 올려서 써야만 TestButton_Click 이벤트를 구현하는 부분에서 사용할 수 있습니다.

 이렇게 좋은 Variable Lifting 를 활용하다 보면, 람다식을 너무 많이 쓰게 됩니다. (특히, 이벤트의 구현 같은 부분에서 말이죠) 그러나, 너무 과하게 사용하다 보면 아니, "적재적소에 사용하지 않고 남발하게 되면" 뜻하지 않게 이벤트가 엉켜 있거나 등록이 중복 되어 구현부가 수 번 실행 되는 등의 문제가 발생하게 됩니다.

 위와 같은 문제가 일어나는 구체적인 예제는, #2에서 다시 포스팅 하겠습니다. (바로~)
Posted by Min-gu, Kim

티스토리 툴바