본문 바로가기
[ Program ]/c#스터디

54. 람다식

by 관이119 2022. 2. 17.

앞장에서 무명메서드를 통해 델리게이트를 간단히 사용하는 방법을 알아 봤다.

람다식이란것을 사용하면 더 축약이 가능하다.

그런데 이렇게 줄이는것에 대한 단점도 분명히 있다.

디버깅시 소스가 실행되는 경로를 따라가기가 힘들어진다.

일단 쓰레드 자체가 디버깅도 힘든상태인데 소스가 계속 축약되다보니 나중에 다른사람이 소스를 받거나 현장에서 즉대응해야할경우 아무래도 기본소스보다 디버깅/수정 속도가 느려지게 된다.

 

일단 람다식 형식부터 알아보자.

대리자타입   변수명  = (파라미터) => 함수코드

기본형태는 이런데 실제 소스코드로 확인해보자.

 

그림1

그림1은 이제까지 진행하던 내용까지 합쳐서 테스트해놓은 내용이다.

여기서 하나씩 살펴보자.

 

 

#if del1    //기본델리게이트 호출
            tempdel td = new tempdel(tempfunc);
            td.Invoke("aaa");

이부분은 이미 계속 이야기하던 내용이라 쉽게 알거라고 생각한다.

설명하면 반환값없고 string 파라미터 하나받는 tempdel 델리게이트를 tempfunc 함수를 호출하게 정의해주고 실행했다.

 

 

#elif del2  //무명메서드 사용
            tempdel td = delegate (string str)
            {
                MessageBox.Show(str);
            };

            tempdel2 td2 = delegate (string str)
            {
                return str + "aaaa";
            };
            MessageBox.Show(td2.Invoke("bbb"));

이부분역시 바로 앞장에서 하던내용이라 어려울게 없다.

 

tempdel td = delegate (string str)
{
       MessageBox.Show(str);
};

이내용은 무명메서드로 델리게이트 만들어서 실행한 내용이고 

tempdel2 td2 = delegate (string str)
{
       return str + "aaaa";
};
MessageBox.Show(td2.Invoke("bbb"));

이부분은 리턴값이 있는 파라미터 받는 내용을 예제로 만들어놓은 부분이다.

 

 

#elif del3  //람다식 사용
            tempdel td = (str) => { MessageBox.Show(str); };
            td.Invoke("aaa");
            td("aaa");

            tempdel2 td2 = (str) => { return str + "aaaa"; };
            MessageBox.Show(td2("bbb"));

다음으로 람다식 사용하는 부분을 하나씩 살펴보자.

tempdel td = (str) => { MessageBox.Show(str); };

이부분을 보면 위에 설명한 기본형식대로 써놨다.

tempdel 은 위쪽에 지정한 델리게이트 형식이고 td라는 변수로 정의했다.

그리고 무명메서드 대신 (str) => { MessageBox.Show(str); }; 이부분을 넣었는데 str 앞에 string을 붙여줘도 되고 안붙여줘도 된다.

컴파일러가 위에 지정한 델리게이트 형식에서 알아서 유추해서 지정하기때문에 없어도 정상작동한다.

tempdel td = (str) => { MessageBox.Show(str); };

tempdel td = (string str) => { MessageBox.Show(str); };

즉 위 코드에서는 위 두문장이 완전 동일하다는 의미이다.

(str) => { MessageBox.Show(str); }; 부분을 다시 보면 (str) 로 파라미터 하나를 정의 해줬고 람다식이라는 의미로 => 를 써줬다.

그리고 그뒤에 실제로 작동할 함수부분 { MessageBox.Show(str); }; 를 무명메서드 형태로 작성해줬다.

 

 

#elif del4  //람다식 사용
            this.Invoke((tempdel)((str) => { MessageBox.Show(str); }),"asdadsad");

            object t = this.Invoke((tempdel2)((str) => { return str + "aaa"; }),"bbbbbb");
            MessageBox.Show(t.ToString());

이제 마지막 부분을 보자.

이부분도 잘보면 똑같다 단순히 Invoke 만 먼저 해준내용이다.

this.Invoke((tempdel)((str) => { MessageBox.Show(str); }),"asdadsad");

이부분을 먼저 보면 this.Invoke 로 델리게이트를 호출했다. 그리고 (str) => { MessageBox.Show(str); } 부분의 람다식으로 델리게이트를 구현했고 tempdel 이라는 델리게이트로 형변환 해주고 파라미터는 마지막에 ,"asdadsad" 이렇게 넘겨줬다.

object t = this.Invoke((tempdel2)((str) => { return str + "aaa"; }),"bbbbbb");
MessageBox.Show(t.ToString());

위에 따로 빼낸 부분은 반환값이 있는예제인데 해당내용도 같은내용이라 스스로 잘살펴보길바란다.

 

위예제로 델리게이트를 람다식으로 사용하는 방법에 대해 알수있었을 것이다.

다음으로 자주사용하는 list 에서 람다식 사용하는 방법에 대해 알아보자.

 

 

 

그림2

 

그림2의 예제는 하나의 List 에서 필요한 원소만 추출해내는 예제이다.

List<string> str = new List<string>();
for (int i = 0; i < 100; i++)
{
      str.Add(i.ToString());
}

이부분에서 str 이라는 list 에 문자열을 입력해서 기본 배열을 만들어 줬다.

그리고 아래 부분에서 

List<string> mystr_result = new List<string>();
for(int i=0;i<str.Count;i++)
{
     if (str[i].Length > 1)
          mystr_result.Add(str[i]);
}

이렇게 문자열 길이가 1보다 큰 원소들만 따로 뽑아 새로운 list 인 mystr_result 를 만들었다.

이부분을 람다식으로 바꾼것이 그 아래줄 부분에 있는 람다식이다.

List<string> mystr_result = str.FindAll((sss) => { return sss.Length > 1; });

이부분인데 FindAll 은 기본내장함수이다.

풀어서 설명하겠지만 그냥 (sss) 는 list 에 속한 원소자체를 가리키는것이기때문에 (sss) => { return sss.Length > 1; } 이구문만 보면 list 에 속하는 원소들중 길이가 1보다 큰원소를 리턴한다 라고 알수 있다.

 

왜 저렇게 만들어졌는지 하나씩 살펴보자.

 

그림3

 

그림3에서 보면 FindAll 이라는 함수는 List<string> 을 반환하고 Predicate<string> 이라는 형식의 파라미터를 받는것을 알수 있다.

리턴값이 List<string> 인것은 FindAll 함수를 호출한 str 이라는 배열차제가 List<string> 이기때문이다.

원배열(List) 자체가 string 형식이기때문에 당연히 string 을 가지는 형식을 반환하는것이고 파라미터인 Predicate 역시 string 을 가지게 되는것이다.

그러면이제 파라미터로 넘겨줘야하는 Predicate 를 따라가보자.

 

그림4

그림4에서처럼 따라가보면 Predicate  역시 델리게이트인것을 알수있다.

bool 형식을 반환하는 제네릭타입 델리게이트이고 파라미터로는 제네릭형식의 파라미터를 받는다.

즉, Predicate<string>(string obj); 이런식으로 된다는 의미이다.

만약 원래의 배열이 int 라면  Predicate<int>(int obj); 이런식으로 될것이고 다른 클래스라면 그 클래스의 타입을 가지게 될것이다.

다시 예제로 돌아가보자.

 

그림5

 

그림5를 보면 FindAll 의 파라미터로 Predicate<string> 을 만들기위해 새객체를 만들었는데 파라미터에 bool 형식을 반환하고 string 을 파라미터로 받는 target 을 넣으라고 되있다.

Predicate 가 델리게이트이니 target 은 당연히 함수명이 될것이고, 결론적으로 bool 형식을 반환하고 string 을 파라미터로 받는 함수의 명을 Predicate 를 만들때 파라미터로 넣어라 이런이야기가 된다.

 

그래서 아래에 tempfunc 함수를 하나 만들어서 파라미터로 전달해주고 그렇게 만들어진 Predicate 객체를 FindAll 의 파라미터로 전달했다. 그러면 결론적으로 mystr 에 문자열 길이가 1보다 큰원소들이 담길것이고 해당내용은 따로 출력해서 확인해보면 되겠다.

 

그림6

 

내용이 헷갈릴까봐 소스를 좀 정리했다. 그림6을 보자.

#if pre1
            List<string> mystr = str.FindAll(pred);

이부분은 방금 위에서 설명한 내용이니 넘어가겠다.

 

#elif pre2
            List<string> mystr = str.FindAll(delegate (string sss) { return sss.Length > 1 ? true : false; });

이내용은 우리가 계속 배우던 내용이다. 

Predicate 가 델리게이트이다. 

그러면 무명메서드로 대체할수 있다.

그래서 delegate (string sss) { return sss.Length > 1 ? true : false; } 이런식으로 무명메서드를 만들어서 Predicate  대신넣어줬다.

그래서 pre2 같은경우는 한줄로 줄일수 있었다.

무명메서드가 정확히 이해가 안되면 앞장을 다시 학습하자.

 

#elif pre3
            List<string> mystr = str.FindAll((s) => { return s.Length > 1; });

다시 람다식으로 넘어왔다.

(s) => { return s.Length > 1; } 이부분은 (string s) => { return s.Length > 1; } 로 변경해도 완전히 똑같은내용이다.

이부분역시 앞에서 설명했지만 람다식은 스스로 파라미터 타입을 유추하기 때문에 타입은 생략했다.

 

이제 전제적으로 왜 FindAll 할때 람다식이 저런형태로 되는지 이해가 됐을거라고 생각한다.

 

이렇게 기본 내장함수를 사용하기위해 델리게이트를 넣어줘야 하는 경우들이 꽤나 있기 때문에 델리게이트, 무명메서드, 람다식을 알고 있어야 사용할 수 있다.

특히 쓰레드에서는 계속 사용되기때문에 쓰레드를 사용하려면 몇번이고 반복해서 외워두자.

 

 

 

***숙제 : 버튼을 누르면 두개의 쓰레드에서 각각 람다식 사용하여 300이하의 짝수와 홀수의 합을 각각 출력하는 프로그램을 만들어보자.

'[ Program ] > c#스터디' 카테고리의 다른 글

56. 쓰레드2(Task)  (0) 2022.02.27
55. Action/Func  (0) 2022.02.22
53. 무명메서드  (0) 2022.02.08
52. 델리게이트  (0) 2022.02.05
51. 쓰레드1(Thread)  (0) 2022.02.04

댓글