PROGRAMMING WORKSHOP

Skip Navigation Links. Skip Navigation Links.

VB.Net |
LINQ & LAMBDA Expression

VB.Net을 보면 흥미로운 것이 많다
그중에 하나가 LINQ라고 하는 것이고
LINQ언어의 표현식은 LAMBDA라고 하는 표현식이다
LINQ언어를 표현하는 표현식의 기초가 되는 LAMBDA표현식을 살펴보도록 하자
그리고 LINQ언어로 들어가도록 하자..
LINQ언어는 여러분이 작성하는 코드에 쿼리를 할수 있는 기능의
표현식을 한줄로 직접 작성할수 있는 것이다..
쿼리??? 쿼리는 대표적으로 SQL 언어라고 하는 것이 있다
이것은 단순히 DB에서 원하는 정보를 찾아서 가져온다,이것을 쿼리라고 하고..
단순한 DB의 것이 아닌, 모든 데이타가 집합적으로 몰려 있을때 간단하게 찾아 오는
방법(Query)이 없을까??
그래서 .NetFrameWork 환경에서 제공하는 엔진인 것이다
집합적으로 몰려있는 정보라는 것이 무엇인가?
가장 기본적인 것이 배열을 기본으로 시작하여 다양한 집합체들이 있다

예를 들면
배열이 하나 있다고 치자..
하나 만들어서 구현해 보면..

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
	Dim sX As String() = {"A", "Q", "B", "Z", "M", "P", "C", "O", "R", "M", "T", "U"}
	Dim P = From m In sX Order By m Select m

	Dim sT As String = ""
	For Each sP As String In P
		sT &= sP & vbNewLine
	Next
	MsgBox(sT)
End Sub

전통적인 방법으로 하자면
순환문을 돌면서 자리바꿈을 하고 어쩌구 하여야 할 것이다
아래와 같이

Sub sortText()
Dim sTemp As String
Dim iX As Long
Dim iY As Long
Dim iMin As Integer
Dim iMax As Integer
Dim arrX As Variant
arrX = Array("A", "Q", "B", "Z", "M", "P", "C", "O", "R", "M", "T", "U")
iMin = LBound(arrX)
iMax = UBound(arrX)
For iX = iMin To iMax - 1
   For iY = iX + 1 To iMax
     If arrX(iX) > arrX(iY) Then
       sTemp = arrX(iX)
       arrX(iX) = arrX(iY)
       arrX(iY) = sTemp
     End If
   Next
Next
sTemp = ""
For iX = 0 To iMax
    sTemp = sTemp & arrX(iX) & vbNewLine
Next
MsgBox sTemp
End Sub

그러나 위와 같이 한줄로 명령문을 주면 결과가 얻어진(Query) 것이다..
이런 쿼리문( LINQ) 를 구성하는 키워드들이 LAMBDA의 표현식의 것들인 것이다

이런 식으로 집합적으로 모여있는데이타(세상의 모든 데이타는 집합적으로 모여있는 것)를
정렬도 하고sorting,
휠터도 하고 filtering,
그룹핑도 하고 grouping,
그리고 다른 것과 서로 합치기도 하고 joining,
그리고 계산도 하고 calculations
한마디로 엑셀 시트의 기능들을 한줄로 표현하여 원하는 그룹을
원하는 값을 알아내주는 것이다

LINQ에 대하여서는 UNO_Weekly코너의 지난화일에서
LAMBDA표현식에 대한 설명하지 않았었다
그런 것들을 좀더 자세하게 살펴 보면서
LAMBDA표현식을 시작으로,Delegate,Extension Method,그리고 LINQ까지 가보도록 하자

모두 VB.Net을 하면서 헷갈리는 단어들이지만
알고 나면 모두 순환문을 돌리면서 떼거리정보를 처리하는 것을
단순하게 처리하고자 하는 노력들의 단어들이니 깊이 살펴볼만한 가치가 있다
물론 그동안하여온 순환문에 의존하고 지내도 아무 상관 없다
그러나 자동차를 사면 많은 옵션을 달고 싶은 것과 마찬가지로
다른 다양함을 즐기는 것이 좋은 것

시작하자..

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
	Dim lX = Function(x) x * 100
	MsgBox(lX(100))
End Sub

어라..이것이 뭔가??
이것이 하나의 명령줄로 함수를
만들어서 처리한 표현식인 것이고 Lambda 표현식이라고 한다
호출하여야할 함수의 이름도, 프로시져의 이름도 필요없이 간단한 것은 그냥
변수선언하고 값 Assign하듯이 표현하면 좋겠는데...
그래서 나온것이 LAMBDA표현식인 것이다
아래와 같이 표현하는 것이 그동안하던 함수를 호출하는 방법인데

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
	MsgBox(lX(100))
End Sub

Function lX(X) 
	Return X*100
End Sub

함수나 프로시져의 이름도 필요없고 한줄로 표현하면
End Function,End Sub 도 생략해 버린셈이다

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
	Dim sQ = Sub(x) MsgBox(x)
	sQ("Excel And VB.Net , All is fun and exciting")
End Sub

역시 버튼을 크릭하면 메시지박스가 뜰 것이다
위에서 이야기했다시피 여러줄로 표현한다면 아래와 같이 하여도 되고
한줄로 표현하면 End Sub,End Function도 생략해도 된다

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
Dim lX = Function(x)
		 Return x * Int(Rnd() * 100) + 100
	 End Function
Dim sQ = Sub(x)
		 MsgBox(x)
	 End Sub
	 
	 MsgBox(lX(1000))
     sQ(<text>
excel is great
vb.net is very useful
access is very wonderful
</text>)

End Sub

VB의 고전적 문법이 손에 익어서 위와 같은 구문이 낯섫지만
아마도 손에 익으면 무척 편리할 것이고
이 LAMBDA표현식에서 더 발전하는 것이 LINQ 라고 보시면 점점
낯섫지 않다
또 이렇게 작성하는 방법도 있다

Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        MsgBox((Function(x As Integer) (Int(Rnd() * x) + 1))(10))
        MsgBox((Function(x As DateTime) DateAdd("d", 5, x).ToShortDateString)(DateTime.Today))
End Sub

위의 한줄의 표현은 아래와 같이 변수를 선언하고 매개변수를
전달하는 대신에 약식으로 표현된 방법이라고 할수 있는 것

Dim lX = Function(x) Int(Rnd() * x) + 1
Dim lY = Function(x As DateTime) DateAdd("d", 5, x).ToShortDateString
MsgBox(lX(10))
MsgBox(lY(DateTime.Today))

매개변수를 전달하는 것이 뒤의 빨강색괄호친 부분이다..즉 매개변수를 전달하고
변수에 받을 필요없이 사용하는 초 단축표현이다
첫째것은 10을 전달하여 1과 10사이의 난수를 결과로 받는 것이고
두번째것은 오늘 날짜(DateTime.Today)를 전달하여 오늘에서 5일지난 날짜를 받아서
표현한 것이다
잘 활용하면 코딩이 훨씬 간략해질 것이라는 예감이 들것이다

List 집합체에 문자를 만들어서 100개를 넣었다
그리고 첫문자가 A인 것만 휠터를 하고 싶다
그동안의 방법은 순환을 하면서 찾았다..
그러나 아래와 같이 하면 간략하다

Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click
	Dim oList As New List(Of String)
	For iX As Integer = 0 To 99
		oList.Add(Chr(Int(Rnd() * 26) + 65) & Chr(Int(Rnd() * 26) + 65) & Chr(Int(Rnd() * 26) + 65))
	Next

	Dim oX = oList.Where(Function(x) x.Substring(0, 1) = "B")
	Dim sX As String = ""
	
	For Each s In oX
		sX &= s & vbNewLine
	Next
	MsgBox(sX)

End Sub

위의 

Dim oX = oList.Where(Function(x) x.Substring(0, 1) = "B")

표현은 목록의 확장적 기능인 Where 메소드(이것을 Extension Method라고 한다)를 사용해도 되고
이 메소드내에 사용하는 표현식이 LAMBDA 표현식인것이다

표현은 아래와 같이 다시 LINQ 표현으로도 같은 결과를 얻는다

Dim oX = From X In oList _
	Where X.Substring(0, 1) = "B"  _
	Select X

위의 내용 아래화일로 실행시켜보시고..

***[LOG-IN]***

프로그래밍을 처음 배울때는 순환문이 돌아가는 것은 보고 꺼뻑죽지만
좀 내공이 쌓이고, 실무에 적용이 들어가면
순환문좀 돌리지 않고, 더 빠르게 뭔가 할수 있는 것은 없을까??
그것의 답이라고 해도 될 것이다
물론 LINQ에서 집합체를 분석하여 원하는 것을 가져 올때는 내부적으로
순환문이 돌아가는 것이고, 개발자는 그렇게 하라고 지시문만 작성하면
되는 셈이다

정보가 집합적으로 모여 있는 것의 가장 기본이 배열이다
배열로 몇개 연습을 해보자

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
	Dim sWords = New String() {"ABC", "A", "ABCDE", "AB", "ABCDEF", "ABCDEFG", "QR", "GRP"}
	Dim iMaxLength = sWords.Max(Function(word) word.Length)
	Dim longestWord = sWords.First(Function(word) word.Length = iMaxLength)
	MsgBox(longestWord)
End Sub

위의 내용은 배열중에서 가장 긴문자를 갖고 있는 것은 어떤 것인지 찾는 것이다
이것을 VBA에서의 전통적 배열에서는 순환문을 돌리는 수 밖에 없다
그리고 배열자체에 속성이나 메소드가 없다
그러나 VB.Net에서는 배열뒤에 줄줄이 속성이나 메소드가 붙는다
이것이 Lambda 표현식을 각 개체에 확장적으로 지원하는 기능이 되는 것이다
첫번째 줄은..
배열이 갖고 있는 각각의 배열요소의 문자갯수가 가장 큰 값을 얻어내고..

Dim iMaxLength = sWords.Max(Function(word) word.Length)

두번째는 위에서 얻어온 문자길이 값을 이용하여 한번더 작업하여

Dim longestWord = sWords.First(Function(word) word.Length = iMaxLength)

가장 긴 단어를 찾아낸 것이다
First를 붙인 것은 가장 긴 단어의 갯수가 같은 것이 있을수 있으니까..
첫번째것을 갖여 온 것이다

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
	Dim sWords = New String() {"ABC", "A", "ABCDE", "AB", "ABCDEF", "ABCDEFGK", "QR", "GRP", "ZYXVWOPX"}
	Dim iMaxLength = sWords.Max(Function(word) word.Length)
	Dim longestWord = Array.FindAll(sWords, Function(x) x.Length = iMaxLength)
	Dim sAll As String = ""
	For Each sX As String In longestWord
		sAll &= sX & vbNewLine
	Next
	MsgBox(sAll)
End Sub

FillAll 이라는 것을 사용하여 조건에 맞는 것을 모두 가져오게 했다
그런데 눈썰미가 있으면 이것이 왜이런 것이지??? 하는 것이 있을 것이다
Dim longestWord = Array.FindAll(...)
이라고 할때 longestWord의 정보의 타입을 왜 선언하지 않아도 되지???
LINQ나 Lambda표현식으로 얻어지는 값은 정보형태가 불특정한 것이다
어떤 때는 단일값이 올때도 있고, 어떤때는 집합체가 올때도 있고..
불특정한 타입의 정보에 불특정한 갯수의 정보가 결과값으로 오는 것이다
이런 모든 것을 처리하는 것이..

Dim longestWord As IEnumerable = Array.FindAll(sWords, Function(x) x.Length = iMaxLength)

이런 것을 모두 자체적으로 순환하면서 다양한 일들을
처리하도록(찾고,그룹핑하고,정렬하고,휠터하고,계산하고..등)
VB.Net에서 제공되는 집합체를 다루는 표준의 인터페이스이다
이것은 생략해도 되고 위와 같이 선언해도 좋다..

이것을 사용하여 개발자는 찾아라, 정렬해라,휠터해라등등의 몇개의
약속된 키워드들을 활용하여 선언적으로 작성하면 되는 것이다
배열뿐만 아니라...목록(List(Of T))도 역시 집합체이다
이것도 아래와 같이 간단하게 찾고 싶은 값을 조건을 주어서 찾아서
정렬을 하여 새로운 목록으로 만들어서 사용할수 있는 것이다

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
	Dim oList As New List(Of String)
	For iX As Integer = 1 To 300
		oList.Add(Chr(Int(Rnd() * 26) + 65))
	Next
	oList = oList.Where(Function(x) x <= "F").ToList

	oList.Sort()

	Dim sQ As String = ""
	For Each sX As String In oList
		sQ &= sX & ","
	Next
	MsgBox(sQ)
End Sub

목록개체나 배열뒤에 붙는 메소드가 도대체
어떻게 생겨먹었길래 위와 같은 작업들을 할까..



집합체들의 메소드중에 그림과 같이 활살표가 붙어 있는 것은
Enumerable 이라는 개체의 메소드들을 빌려서 사용하는 확장형 메소드인 것이다
그러니 집합체의 내용을 순환하고,계산하고,휠터하고,그룹핑을 하고 하는 것등의
모든 요술쟁이 역할을 Enumerable이 제공하고 있는 것이다

VBA에서는 배열과 Collection개체딱 두가지 밖에 없다
그리고 Collection이나 Array는 메소드나 속성도 별로 없다
물론 엑셀에서의 워크시트나 Range개체에서의 메소드로 이런 기능을 커버하지만
이것에 비하여 VB.Net에서는 Enumerable개체로 부터 확장되는 다양한 멤버가
구성되는 셈이다
그것이 VB.Net의 파워라고 할수 있을 것이다
실은 VB.Net의 파워가 아니고 .NetFrameWork 라는 환경이 파워인것
Enumerable개체는 Shared Type으로서 개체생성없이 참조가 되는 크래스이고
이것을 사용자가 IEnumerable(Of T) 라는 인터페이스를
통하여 구현하게 되는 것이고 앞으로 이야기할 LINQ는 바로 이것을
쿼리언어로 활용하게 되는 것이다
몇개만 더 해보고 다음 페이지로 가자..LINQ라는 것도 해보고..
한없이 응용되는 것이니까..하자면 끝도 없다
아래의 것은 배열요소 문장의 길이를 계산하고 이것을 모두 합치는 것
즉 배열요소상의 총문자의 갯수..

Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        Dim sWords = New String() {"ABC", "A", "ABCDE", "AB", "ABCDEF", "ABCDEFGK", "QR", "GRP", "ZYXVWOPX"}
        Dim lX = sWords.Sum(Function(x) x.Length)

        ' 위의 것을 지우고 아래의 것을 실행해도 마찬가지다, 이것이 LINQ적 표현이다
		' 위의 것은 다양한 집합체를 확장한 Extended Method를 활용하는 것이고..
		' 앞으로 진지하게 생각해 볼 것이 아래의 LINQ인 것이다
		' 물론 간단한것은 위와 같이 다양한 집합체의 메소드를 사용하고..
		
        'Dim lX = Aggregate x In sWords _
        '             Let q = x.Length
        '             Into Sum(q)


        MsgBox(lX)
 End Sub

위와 같은 문제를 풀려면 기존의 언어로서는 순환문을 돌면서
계산을 하고 하는 과정을 거쳤지만..
위의 것은 그냥 어떻게 하라는 명령문만 몇자 적어주면 순환을 하던
계산을 하던 Enumerable이라는 개체의 기능이 작동되어 처리되는 것
이것에 접근하여 명령을 주고 받는 언어가 LINQ이고
LINQ의 표현식들은 모두 LAMBDA 표현식인 것이고..
다 같은 내용이지만 하나 더 해보면.. 문자의 갯수가 3개 이하의 것만 뽑아보고 싶다면

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click
	Dim sWords = New String() {"ABC", "A", "ABCDE", "AB", "ABCDEF", "ABCDEFGK", "QR", "GRP", "ZYXVWOPX"}
	Dim oX = sWords.Where(Function(x) x.Length <= 3)
	Dim sX As String = ""
	For Each Q As String In oX
		sX &= Q & vbNewLine
	Next
	MsgBox(sX)
End Sub

모두 개체의 확장메소드에 RAMDA표현식으로 Sub, Function을 표현한것이다..
다음 페이지에서 좀더 흥미있게 살펴보도록 하자

***[LOG-IN]***