Erik Meijer, Amanda Silver와 Paul Vick
Microsoft Corporation
2005년 9월
요약: 새로운 Visual Basic 언어 기능 및 데이터 사용이 많은 프로그래밍을 지원하는 새로운 언어 확장 기능에 대한 개요를 제공합니다(23페이지/인쇄 페이지 기준).
목차
소개
Visual Basic 9.0 시작하기
암시적으로 형식이 지정된 로컬 변수
개체 및 컬렉션 이니셜라이저
익명 형식
고급 XML 지원
쿼리 함축 구문
확장 메서드
중첩된 함수
Null 허용 형식
대리자 완화
동적 인터페이스: 강력한 "덕 타이핑(Duck Typing)"
동적 식별자
결론
소개
"Visual Basic 코드명 Orcas"(Visual Basic 9.0)에는 관계형 데이터베이스, XML 문서 및 개체 그래프를 작성, 업데이트 및 쿼리하는 데이터를 많이 사용하는 프로그래밍을 통합된 방식으로 지원하기 위해 "Visual Basic 코드명 Whidbey"(Visual Basic 8.0)을 기반으로 하는 다양한 언어 확장 기능이 도입되었습니다. 또한 Visual Basic 9.0에는 경우에 따라 동적 및 정적 형식 지정을 적절하게 사용할 수 있도록 하는 Visual Basic의 고유한 기능을 더욱 향상시키는 새로운 언어 기능도 도입되었습니다. 새로운 기능은 다음과 같습니다.
- 암시적으로 형식이 지정된 로컬 변수
- 쿼리 함축 구문
- 개체 이니셜라이저
- 익명 형식
- Linq 프레임워크를 사용한 전체 통합
- 고급 XML 지원
- 대리자 완화
- Null 허용 형식
- 동적 인터페이스
- 동적 식별자
이 문서에서는 이러한 새 기능에 대한 개괄적인 정보를 제공합니다. Visual Basic 언어 정의 및 컴파일러 미리 보기 업데이트를 비롯한 자세한 내용은 Visual Basic Developer Center(http://www.microsoft.com/Korea/MSDN/vbasic/)에서 확인할 수 있습니다.
Visual Basic 9.0 시작하기
이러한 언어 기능을 실제로 확인하기 위해 CIA World Factbook 데이터베이스 (영문)의 실제 예제를 사용하여 작업을 시작해 봅니다. 이 데이터베이스에는 전 세계 여러 나라에 대한 다양한 지리, 경제, 사회 및 정치 정보가 들어 있습니다. 이 기사에서 사용할 예제에서는 각 국가명 및 해당 국가의 수도, 총 면적 및 인구가 들어 있는 스키마를 사용합니다. 다음 클래스를 사용하여 이 스키마를 Visual Basic 9.0에서 표시합니다.
Class Country
Public Property Name As String
Public Property Area As Float
Public Property Population As Integer
End Class
다음은 여기서 예제로 사용할 국가 데이터베이스의 작은 하위 집합입니다.
Dim Countries = _
{ new Country{ _
.Name = "Palau", .Area = 458, .Population = 16952 }, _
new Country{ _
.Name = "Monaco", .Area = 1.9, .Population = 31719 }, _
new Country{ _
.Name = "Belize", .Area = 22960, .Population = 219296 }, _
new Country{ _
.Name = "Madagascar", .Area = 587040, .Population = 13670507 }, _
}
이 목록이 있으면 다음 쿼리 함축 구문(Query Comprehension)을 사용하여 인구가 1백만 이하인 모든 국가를 쿼리할 수 있습니다.
Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
For Each Country As Country In SmallCountries
Console.WriteLine(Country.Name)
Next
목록의 국가 중에는 마다가스카르의 인구만이 1백만이 넘기 때문에 위 프로그램을 컴파일 및 실행하면 다음 국가명 목록이 출력됩니다.
Palau
Monaco
Belize
이러한 코드를 아주 간단하게 작성할 수 있도록 하는 Visual Basic 9.0의 기능을 이해하기 위해 프로그램을 좀 더 자세히 살펴봅시다. 먼저, 다음과 같은 Countries 변수 선언에서는
Dim Countries = _
{ new County { .Name = "Palau", .Area = 458, .Population = 16952 }, _
... _
}
새로운 개체 이니셜라이저 구문인 new Country {..., .Area = 458, ...}를 사용하여 기존 With 문과 비슷한 간단한 식 기반 구문을 통해 복합 개체 인스턴스를 작성합니다.
이 선언에서는 또한 암시적으로 형식이 지정된 로컬 변수도 볼 수 있는데, 여기서 컴파일러는 선언 오른쪽에 있는 이니셜라이저 식에서 Countries 로컬 변수 형식을 유추합니다. 위 선언은 Country() 형식의 명시적으로 형식이 지정된 로컬 변수 선언과 정확하게 일치합니다.
Dim Countries As Country() = {...}
다시 한 번 말하지만 이는 여전히 형식이 엄격히 지정된 선언입니다. 컴파일러가 로컬 선언 오른쪽의 형식을 자동으로 유추했기 때문에 프로그래머가 해당 형식을 프로그램에 수동으로 입력할 필요가 없습니다.
SmallCountries 로컬 변수 선언은 SQL 스타일 쿼리 함축 구문(Query Comprehension)을 통해 초기화되어 인구가 1백만 이하인 국가를 필터링해 선택합니다. 쿼리 조건이 SQL과 유사한 것은 이미 SQL에 익숙한 프로그래머가 Visual Basic 쿼리 구문을 보다 신속하게 사용할 수 있도록 하기 위한 것입니다.
Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
여기에도 암시적 형식 지정이 적용되어 있습니다. 즉, 컴파일러는 SmallCountries의 형식을 IEnumerable(Of Country)로 유추합니다. 컴파일러는 쿼리 함축 구문 자체를 표준 쿼리 연산자로 변환합니다. 이 경우 변환 작업은 다음과 같이 간단합니다.
Function F(Country As Country) As Boolean
Return Country.Population < 1000000
End Function
Dim SmallCountries As IEnumerable(Of Country) = _
Countries.Where(AddressOf F)
확장된 구문은 컴파일러에서 생성된 로컬 함수를 표준 쿼리 연산자 라이브러리에서 IEnumerable(Of T) 인터페이스의 확장으로 정의되는 Where 확장 함수에 AddressOf F 대리자로 전달합니다.
이제부터는 앞서 언급한 Visual Basic 9의 몇 가지 새로운 기능을 보다 자세하게 살펴봅니다.
암시적으로 형식이 지정된 로컬 변수
암시적으로 형식이 지정된 로컬 변수 선언에서 로컬 변수의 형식은 로컬 선언문 오른쪽의 이니셜라이저 식에서 유추됩니다. 예를 들어 컴파일러는 다음 모든 변수 선언의 형식을 유추합니다.
Dim Population = 31719
Dim Name = "Belize"
Dim Area = 1.9
Dim Country = New Country{ .Name = "Palau", ...}
그러므로 위 선언은 다음의 명시적으로 형식이 지정된 선언과 정확하게 일치합니다.
Dim Population As Integer = 31719
Dim Name As String = "Belize"
Dim Area As Float = 1.9
Dim Country As Country = New Country{ .Name = "Palau", ...}
로컬 변수 선언 형식은 기본적으로 유추되므로 Option Strict 설정에 관계없이 해당 변수에 대한 액세스는 항상 초기 바인딩됩니다. Visual Basic 9.0에서 프로그래머는 다음과 같이 변수를 Object 형식으로 명시적으로 선언함으로써 후기 바인딩을 명시적으로 지정해야 합니다.
Dim Country As Object = new Country{ .Name = "Palau", ... }
명시적으로 후기 바인딩을 지정하도록 함으로써 실수로 후기 바인딩을 사용하는 일이 없도록 할 수 있습니다. 그러나 더욱 중요한 것은 이를 통해 아래에서 살펴볼 XML 등의 새로운 데이터 형식으로 후기 바인딩을 확장할 수 있다는 것입니다. 기존 동작을 전환할 수 있는 프로젝트 수준 스위치(옵션)가 추가될 예정입니다.
For...Next 또는 For Each...Next 문의 루프 제어 변수 또한 암시적으로 형식이 지정된 변수일 수 있습니다. For Dim I = 0 To Count 또는 For Each Dim C In SmallCountries에서와 같이 루프 제어 변수를 지정할 때 식별자는 암시적으로 형식이 지정된 새 로컬 변수를 정의하는데, 이 변수의 형식은 이니셜라이저 또는 컬렉션 식에서 유추되며 범위는 전체 루프입니다. 암시적으로 형식이 지정된 루프 제어 변수와 마찬가지로 For 오른쪽에 사용되는 Dim은 Visual Basic 9.0의 새로운 기능입니다.
이 형식 유추를 적용하면 다음과 같이 인구가 1백만 이하인 모든 국가를 출력하는 루프를 다시 작성할 수 있습니다.
For Each Dim Country In SmallCountries
Console.WriteLine(Country.Name)
Next
Country 형식은 SmallCountries의 요소 형식인 Country로 유추됩니다.
개체 및 컬렉션 이니셜라이저
Visual Basic에서 With 문을 사용하면 대상 식을 여러 번 지정하지 않아도 여러 집계 값 멤버에 간편하게 액세스할 수 있습니다. With 문 블록 내에서 마침표로 시작하는 멤버 액세스 식은 해당 마침표 앞에 With 문의 대상 식이 붙어 있는 것과 같이 취급됩니다. 예를 들어 다음 문은 새 Country 인스턴스를 초기화한 다음 해당 필드를 필요한 값으로 초기화합니다.
Dim Palau = New Country()
With Palau
.Name = "Palau"
.Area = 458
.Population = 16952
End With
Visual Basic 9.0의 새로운 개체 이니셜라이저는 복합 개체 인스턴스를 간편하게 만들기 위한 With의 식 기반 형식입니다. 개체 이니셜라이저를 사용하면 위의 두 개 문을 다음과 같이 암시적으로 형식이 지정된 단일 로컬 선언으로 만들 수 있습니다.
Dim Palau = New Country { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952
}
식을 사용한 이러한 스타일의 개체 초기화는 쿼리에 중요합니다. 보통 쿼리는 등호 기호 오른쪽에 있는 Select 절에 의해 초기화되는 개체 선언과 비슷합니다. Select 절은 식을 반환하므로 단일 식을 통해 전체 개체를 초기화할 수 있어야 합니다.
앞서 살펴본 것처럼 개체 이니셜라이저를 사용하면 복합 개체 컬렉션도 간편하게 만들 수 있습니다. Add 메서드를 지원하는 모든 컬렉션은 컬렉션 이니셜라이저 식을 사용하여 초기화할 수 있습니다. 예를 들어 다음과 같은 도시 선언이 부분 클래스로 주어지는 경우
Partial Class City
Public Property Name As String
Public Property Country As String
Public Property Longitude As Float
Public Property Latitude As Float
End Class
다음과 같이 예제 국가의 수도에 대해 List(Of City)를 만들 수 있습니다.
Dim Capitals = New List(Of City){ _
{ .Name = "Antanarivo", _
.Country = "Madagascar", _
.Longitude = 47.4, _
.Lattitude = -18.6 }, _
{ .Name = "Belmopan", _
.Country = "Belize", _
.Longitude = -88.5, _
.Latitude = 17.1 }, _
{ .Name = "Monaco", _
.Country = "Monaco", _
.Longtitude = 7.2, _
.Latitude = 43.7 }, _
{ .Country = "Palau",
.Name = "Koror", _
.Longitude = 135, _
.Latitude = 8 } _
}
이 예제에서는 중첩된 이니셜라이저의 생성자가 컨텍스트에서 유추되는 중첩된 개체 이니셜라이저도 사용되었습니다. 이 경우 중첩된 각 이니셜라이저는 완전한 New City{...} 형식과 정확하게 일치합니다.
익명 형식
쿼리의 결과로 형식의 특정 멤버만을 제거하거나 제외하려는 경우가 종종 있습니다. 예를 들어 원본 데이터의 Latitude 및 Longitude 열을 사용하여 항목을 식별하고 결과에서는 해당 열을 제외하는 방법으로 모든 열대 국가의 수도에 해당하는 Name 및 Country를 파악하고자 할 수 있습니다. Visual Basic 9.0에서는 위도가 북회귀선과 남회귀선 사이에 있는 각 도시에 대해 C 형식을 지정하지 않고 새 개체 인스턴스를 만드는 방식으로 이 작업을 수행합니다.
Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5
Dim Tropical = Select New{ .Name = City.Name, .Country = City.Country } _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn
Tropical 로컬 변수의 유추된 형식은 익명 형식의 인스턴스 컬렉션, 즉 IEnumerable(Of { Name As String, Country As String })입니다. Visual Basic 컴파일러에서는 다음과 같이 멤버 이름 및 형식이 개체 이니셜라이저에서 유추되는 _Name_As_String_Country_As_String_과 같은 새 시스템 생성 클래스를 만듭니다.
Class _Name_As_String_Country_As_String_
Public Property Name As String
Public Property Country As String
Public Default Property Item(Index As Integer) As Object
...
End Class
이 프로그램 내에서 컴파일러는 동일한 익명 형식을 병합합니다. 이름과 형식이 같은 속성 시퀀스를 같은 순서로 지정하는 두 개의 익명 개체 이니셜라이저는 같은 익명 형식 인스턴스를 생성합니다. 외부적으로는 Visual Basic에서 생성된 익명 형식이 지워지고 Object로 대체되기 때문에 컴파일러는 익명 형식을 인수 및 함수의 결과로 동일하게 전달할 수 있습니다. Visual Basic 코드 내에서 사용하기 위해 컴파일러는 _Name_As_String_Country_As_String_ 형식이 실제로 { Name As String, Country As String } 익명 형식을 나타냄을 기억하는 특수 사용자 지정 특성으로 생성된 클래스를 장식합니다.
익명 형식은 보통 기존 형식의 프로젝트 멤버에 사용되므로, Visual Basic 9.0에서는 축약형 프로젝션 노테이션인 New { City.Name, City.Country }를 사용하여 긴 형식인 New { .Name = City.Name, .Country = City.Country }를 축약할 수 있습니다. 이 노테이션을 쿼리 함축 구문(Query Comprehension) 결과 식에 사용하는 경우 다음과 같이 프로젝션 이니셜라이저를 더욱 짧게 축약할 수 있습니다.
Dim Tropical = Select City.Name, City.Country _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn
이러한 두 축약 형식의 의미는 모두 위의 긴 형식과 동일합니다.
고급 XML 지원
XLinq는 LINQ(Language-Integrated Query) 프레임워크 등 최신 .NET Framework 기능을 활용하기 위해 특수하게 고안된 새로운 메모리 내장 XML 프로그래밍 API입니다. 쿼리 함축 구문(Query Comprehension)을 통해 기본적인 표준 .NET Framework 쿼리 연산자에 친숙하고 편리한 구문이 추가되는 것과 같이, Visual Basic 9.0에서는 XML 리터럴 및 XML에 대한 후기 바인딩을 통한 고급 XLinq 지원을 제공합니다.
XML 리터럴을 설명하기 위해, 기본적으로 플랫인 관계형 데이터 원본 Countries 및 Capitals를 쿼리하여 각 국가의 수도가 자식 요소로 중첩되어 있으며 인구 밀도를 특성으로 계산하는 계층적 XML 모델을 만들어 봅니다.
특정 국가의 수도를 찾기 위해 각 국가의 name 멤버와 각 도시의 country 멤버를 조인합니다. 국가와 해당 수도가 주어지면 포함된 식의 빈 공간에 계산된 값을 채워 XML 코드를 쉽게 만들 수 있습니다. Name=(Country.Name)과 같이 괄호가 있는 이름 특성에 대해 "빈 공간"을 작성하고, <Name><%= City.Name %></Name>과 같이 ASP.NET에서 차용한 특수 꺾쇠 괄호 구문이 있는 자식 요소에 대해서도 "빈 공간"을 작성합니다. 다음은 XML 리터럴과 쿼리 함축 구문을 조합하는 쿼리입니다.
Dim CountriesWithCapital As XElement = _
<Countries>
<%= Select <Country Name=(Country.Name)
Density=(Country.Population/Country.Area)>
<Capital>
<Name><%= City.Name %></Name>
<Longitude><%= City.Longitude %></Longtitude>
<Latitude><%= City.Latitude %></Latitude>
</Capital>
</Country> _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country %>
</Countries>
여기서 XElement 형식은 선언에서 생략할 수 있으며, 그러면 다른 로컬 선언과 마찬가지로 유추됩니다. 이 예제에서는 아래의 설명을 위해 명시적 형식을 생략하지 않고 남겨 둡니다.
이 선언에서는 Select 쿼리의 결과가 <Countries> 요소 안에 들어가야 합니다. 그러므로 Select 쿼리는 첫 번째 "빈 공간"의 내용이며, <Countries> 내에서 익숙한 ASP.NET 스타일의 <%= 및 %> 태그로 둘러싸여 있습니다. Select 쿼리 결과도 식이고 XML 리터럴도 식이므로, 또 다른 XML 리터럴이 Select 자체 내에 중첩됩니다. 이 중첩된 리터럴 자체에는 Country.Name 및 계산된 인구 밀도 비율 Country.Population/Country.Area에 대해 중첩된 특성 "빈 공간"과 수도의 이름 및 좌표에 대한 중첩된 요소 "빈 공간"이 포함되어 있습니다.
위 쿼리를 컴파일 및 실행하면 다음 XML 문서가 반환됩니다(공간을 절약하기 위해 형식을 약간 변형함).
<Countries>
<Country Name="Palau" Density="0.037117903930131008">
<Capital>
<Name>Koror</Name><Longitude>135</Longitude><Latitude>8</Latitude>
</Capital>
</Country>
<Country Name="Monaco" Density="16694.21052631579">
<Capital>
<Name>Monaco</Name><Longitude>7.2</Longitude><Latitude>3.7</Latitude>
</Capital>
</Country>
<Country Name="Belize" Density="9.5512195121951216">
<Capital>
<Name>Belmopan</Name><Longitude>-88.5</Longitude><Latitude>17.1</Latitude>
</Capital>
</Country>
<Country Name="Madagascar" Density="23.287181452711909">
<Capital>
<Name>Antanarivo</Name>
<Longitude>47.4</Longitude><Latitude>-18.6</Latitude>
</Capital>
</Country>
</Countries>
Visual Basic 9.0는 XML 리터럴을 일반 System.Xml.XLinq 개체로 컴파일하여 Visual Basic과 XLinq를 사용하는 다른 언어 간에 완벽하게 상호 운용될 수 있도록 합니다. 이 기사의 예제 쿼리에서 컴파일러가 생성한 코드(표시되는 경우)는 다음과 같습니다.
Dim CountriesWithCapital As XElement = _
New XElement("Countries", _
Select New XElement("Country", _
New XAttribute("Name", Country.Name), _
New XAttribute("Density", Country.Population/Country.Area), _
New XElement("Capital", _
New XElement("Name", City.Name), _
New XElement("Longitude", City.Longitude), _
New XElement("Latitude", City.Latitude)))
From Country In Countries, City In Capitals _
Where Country.Name = City.Country)
Visual Basic 9.0을 사용하면 XML을 만들 수 있을 뿐 아니라 XML에 대한 후기 바인딩을 통해 XML 구조 액세스 작업을 간편하게 수행할 수 있습니다. 즉, Visual Basic 코드의 식별자가 런타임에 해당 XML 특성 및 요소에 바인딩됩니다. 예를 들어 다음과 같이 모든 예제 국가의 인구 밀도를 출력할 수 있습니다.
CountriesWithCapital.Country 자식 축을 사용하여 CountriesWithCapital XML 구조에서 모든 "Country" 요소를 가져옵니다.
Country.@Density 특성 축을 사용하여 Country 요소의 "Density" 특성을 가져옵니다.
- 원본 코드에서 세 개의 점으로 작성된
Country...Latitude 하위 항목 축을 사용하여 계층 구조에서의 위치에 관계없이 Country 요소의 모든 "Latitude" 자식을 가져옵니다.
IEnumerable(Of T)에 확장 인덱서를 사용하여 결과 시퀀스의 첫 번째 요소를 선택합니다.
이러한 내용이 모두 적용된 코드는 다음과 같습니다.
For Each Dim Country In CountriesWithCapital.Country
Console.WriteLine("Density = "+ Country.@Density)
Console.WriteLine("Latitude = "+ Country...Latitude(0))
Next
선언, 대입 또는 초기화의 대상 식이 보다 구체적인 형식이 아닌 Object 형식인 경우 컴파일러는 일반 개체에 대해 후기 바인딩을 사용합니다. 마찬가지로 대상 식이 XElement, XDocument 또는 XAttribute의 형식이나 컬렉션인 경우에도 컴파일러는 XML에 대한 후기 바인딩을 사용합니다.
XML에 대한 후기 바인딩의 결과 컴파일러는 다음과 같은 변환 작업을 수행합니다.
CountriesWithCapital.Country 하위 축 식이 CountriesWithCapital.Elements("Country") 원시 XLinq 호출로 변환됩니다. 이 호출에서는 Country 요소에서 이름이 "Country"인 모든 자식 요소 컬렉션이 반환됩니다.
Country.@Density 특성 축 식이 Country.Attribute("Density")로 변환됩니다. 이 호출에서는 Country에서 이름이 "Density"인 단일 자식 특성이 반환됩니다.
Country...Latitude(0) 하위 항목 축 식이 ElementAt(Country.Descendants(Latitude),0) 조합으로 변환됩니다. 그러면 Country 하위의 모든 요소 컬렉션이 반환됩니다.
쿼리 함축 구문
쿼리 함축 구문(Query Comprehension)은 SQL과 매우 비슷하지만 Visual Basic에 잘 맞도록 조정된 동시에 새로운 .NET LINQ 프레임워크와 원활하게 통합되는 쿼리용 언어 통합 구문을 제공합니다.
SQL 구현 (영문)에 익숙하다면 기본 .NET Framework 시퀀스 연산자에서 쿼리 프로세서 내의 쿼리 계획을 나타내는 프로젝션, 선택, 교차곱, 그룹화 및 정렬 등 대부분의 구성형 관계 대수 연산자에 대해 알고 있을 것입니다.
쿼리 함축 구문의 구문은 해당 구문을 시퀀스 연산자로 변환하여 정의되므로 기본 연산자는 범위 내에 있는 시퀀스 연산자로 바인딩됩니다. 즉, 특정 구현을 가져오면 사용자가 쿼리 함축 구문을 효과적으로 다시 바인딩할 수 있습니다. 특히 쿼리 함축 구문은 DLinq 인프라를 사용하는 시퀀스 연산자 구현이나 여러 로컬 또는 원격 데이터 원본에 대한 쿼리 실행을 배포하는 로컬 쿼리 최적화 프로그램으로 다시 바인딩할 수 있습니다. 이러한 기본 시퀀스 연산자의 다시 바인딩 작업은 기본적으로 일반적인 COM 공급자 모델과 비슷하며, 이 작업이 수행됨으로써 동일한 인터페이스의 서로 다른 구현 과정에서 결과로 생성되는 응용 프로그램 코드를 수정하지 않고도 다양한 작동 및 배포 옵션 중에서 선택할 수 있습니다.
기본 Select...From...Where... 구문은 Where 절의 술어를 충족하는 모든 값을 필터링해 선택합니다. 이 기사의 첫 번째 예제에서 인구가 1백만 이하인 모든 국가를 찾는 방법을 확인할 수 있었습니다.
Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
시퀀스 연산자 안에 있는 It 식별자는 현재 "행"으로 바인딩됩니다. Me와 같은 It의 멤버는 자동으로 범위 내에 속하게 됩니다. It의 개념은 XQuery의 컨텍스트 항목 "."에 해당하며, SQL의 "*"처럼 사용할 수 있습니다. 예를 들어 다음 쿼리를 사용하여 모든 국가와 수도의 컬렉션을 반환할 수 있습니다.
Dim CountriesWithCapital = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
이 로컬 선언에 대해 유추되는 형식은 IEnumerable(Of { Country As Country, City As City })입니다.
Order By 절을 사용하면 정렬 키 수에 따라 쿼리 결과를 정렬할 수 있습니다. 예를 들어 다음 쿼리를 실행하면 경도가 오름차순으로 정렬되고 인구가 내림차순으로 정렬된 모든 국가명 목록이 반환됩니다.
Dim Sorted = Select Country.Name _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Order By City.Longtitude Asc, Country.Population Desc
Min, Max, Count, Avg, Sum 등의 집계 연산자는 컬렉션에서 작동되어 쿼리 결과 항목을 단일 값으로 "집계"합니다. 다음 쿼리를 사용하여 인구가 1백만 이하인 국가 수를 계산할 수 있습니다.
Dim N As Integer = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000
SQL과 마찬가지로 집계에 대해서는 특수 구문을 사용합니다. 이는 여러 집계 작업을 "튜플링"하는 데 매우 편리합니다. 예를 들어 인구가 1백만 이하인 국가 수를 계산하고 이들 국가의 평균 인구 밀도를 하나의 문으로 계산하려면 다음과 같은 코드를 작성합니다.
Dim R As { Total As Integer, Density As Double } = _
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries _
Where Country.Population < 1000000
집계가 적용되지 않은 일반 결과 집합의 결과에 대해 컴파일러에서 생성되는 집계 함수를 적용하기 위해, 이러한 집계 형식은 줄임 표기법을 사용합니다.
집계 함수는 원본 컬렉션 분할과 함께 사용되는 경우가 가장 많습니다. 예를 들어 열대 국가인지 여부를 기준으로 모든 국가를 그룹화한 다음 각 그룹의 항목 수를 집계할 수 있습니다. 이 작업을 위해 Group By 절을 사용합니다. IsTropical 도우미 함수는 Country가 열대 기후인지 여부에 대한 테스트를 캡슐화합니다.
Partial Class Country
Function IsTropical() As Boolean
Return TropicOfCancer =< Me.Latitude _
AndAlso Me.Latitude >= TropicOfCapricorn
End Function
End Class
이 도우미 함수가 있는 경우에도 위와 똑같은 집계를 사용하지만, 첫 번째 분할에서는 Country.IsTropical 속성이 동일한 Country 및 Capital 쌍의 입력 컬렉션이 그룹화됩니다. 이 경우에는 열대 국가인 팔라우, 벨리즈 및 마다가스카르를 포함하는 그룹과 열대 국가가 아닌 모나코를 포함하는 두 개의 그룹이 있습니다.
| 키 |
국가 |
수도 |
Country.IsTropical() = True |
Palau |
Koror |
Country.IsTropical() = True |
Belize |
Belmopan |
Country.IsTropical() = True |
Madagascar |
Antanarivo |
Country.IsTropical() = False |
Monaco |
Monaco |
그런 다음 그룹에 있는 항목의 총 개수 및 평균 밀도를 계산하여 해당 그룹의 값을 집계합니다. 집계 결과 형식은 Total As Integer 및 Density As Double 쌍 컬렉션입니다.
Dim CountriesByClimate _
As IEnumerable(Of Total As Integer, Density As Double }) =
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group By Country.IsTropical()
실제로 Group By 절의 결과는 위 표와 비슷하게 IEnumerable(Of Grouping(Of { Boolean, { Country As Country, City As City })) 형식의 그룹화 값 컬렉션이므로, 위 쿼리를 사용하면 작업이 매우 간단해집니다. 이러한 각 Grouping 항목에는 키 추출 식 Country.IsTropical()에서 파생된 Key 멤버 및 키 추출 식과 동일한 값을 가진 고유한 국가 및 수도 컬렉션을 포함하는 Group이 들어 있습니다. Visual Basic 컴파일러는 이와 같은 그룹화가 수행되는 경우 각 분할을 집계함으로써 필요한 결과를 계산하는 사용자 정의 집계 함수를 합성합니다.
앞서 살펴본 예제에서 각 Group에는 Country와 Capital이 모두 들어 있었지만, 최종 쿼리 결과를 계산하는 데는 Country만 있으면 됩니다. Group By 절을 통해 그룹을 미리 선택할 수 있습니다. 예를 들어, 다음 구문을 사용하여 모든 국가 이름을 반구별로 분할할 수 있습니다.
Dim ByHemisphere As IEnumerable(Of Grouping(Of Boolean, String)) = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group Country.Name By City.Latitude >= 0
그러면 { New Grouping { .Key = False, .Group = { "Madagascar", "Belize" }}, New Grouping { .Key = True, .Group = { "Palau" }} 컬렉션이 반환됩니다.
Visual Basic 9.0의 쿼리 함축 구문(Query Comprehension)은 완벽하게 구성 가능 (영문)합니다. 즉, 정적 형식 지정 규칙 외에는 아무런 제한 없이 쿼리 함축 구문을 추상적으로 중첩할 수 있습니다. 이와 같은 구성성으로 인해 서로 분리되어 있는 각 하위 식을 이해하면 대규모 쿼리도 쉽게 이해할 수 있으며 언어의 구문 및 형식 지정 규칙도 확실하게 정의할 수 있습니다. 디자인 원칙으로써의 구성성은 SQL 디자인의 기본이 되는 원칙과는 다릅니다. SQL 언어는 완전하게 구성할 수 없으며, 커뮤니티에서 데이터베이스 관련 작업이 누적됨에 따라 그 수가 증가하는 여러 가지 특수한 경우에 사용하기 위한 특수 디자인이라고 할 수 있습니다. 그러나 이러한 완전하게 구성할 수 없는 특성으로 인해, SQL의 경우에는 보통 개별 식을 이해한다 해도 복잡한 SQL 쿼리를 이해할 수 없습니다.
SQL의 구성성이 떨어지는 이유 중 하나는 기본 관계형 데이터 모델 자체를 구성할 수 없다는 것입니다. 예를 들어, 테이블에는 하위 테이블이 포함될 수 없습니다(모든 테이블은 플랫 테이블이어야 합니다). 그 결과 SQL 프로그래머는 복잡한 식을 보다 작은 단위로 분해하는 대신 SQL 데이터 모델에 맞도록 플랫 테이블이 결과로 생성되는 단일체 식을 작성해야 합니다. 이에 대해 Jim Gray는 "컴퓨터 과학 분야에서 재귀적이지 않은 것은 좋지 않다"라고 언급하기도 했습니다. Visual Basic은 CRL 형식 시스템을 기반으로 하기 때문에 특정 형식의 구성 요소가 될 수 있는 형식에 아무런 제한이 없습니다. 또한 정적 형식 지정 규칙 외에는 특정 식의 구성 요소가 될 수 있는 식 종류에도 아무런 제한이 없습니다. 그러므로 행, 개체 및 XML뿐 아니라 활성 디렉터리, 파일, 레지스트리 항목 등도 모두 쿼리 원본 및 쿼리 결과의 구성 요소가 될 수 있습니다.
확장 메서드
.NET Framework 표준 쿼리 인프라의 기본 기능은 대부분 확장 메서드에서 출발합니다. 실제로 컴파일러는 모든 쿼리 함축 구문(Query Comprehension)을 범위 내에 있는 네임스페이스에 의해 정의되는 표준 쿼리 연산자 확장 메서드로 직접 변환합니다. 확장 메서드는 인스턴스 메서드 구문을 통해 호출될 수 있도록 하는 사용자 지정 특성으로 표시되는 공유 메서드입니다. 확장 메서드는 메서드를 추가하여 기존 형식 및 만들어진 형식을 확장합니다.
확장 메서드는 라이브러리 디자이너가 주로 사용하므로 Visual Basic에서는 확장 메서드를 선언하기 위한 직접 언어 구문은 제공하지 않습니다. 대신 제작자는 필요한 사용자 지정 특성을 모듈과 멤버에 직접 첨부하여 해당 모듈과 멤버를 확장 메서드로 표시할 수 있습니다. 다음 예제에서는 추상 컬렉션에서 Count 확장 메서드를 정의합니다.
<System.Runtime.CompilerServices.Extension> _
Module MyExtensions
<System.Runtime.CompilerServices.Extension> _
Function Count(Of T)([Me] As IEnumerable(Of T)) As Integer
For Each Dim It In [Me]
Count += 1
Next
End Function
End Module
꺾쇠 괄호 구문은 Me를 일반 변수 이름으로 사용할 수 있도록 하는 키워드 이스케이프 문자입니다. 확장 메서드는 인스턴스 메서드를 시뮬레이트하는 공유 메서드이므로 실제 인스턴스 메서드에서처럼 Me 식별자를 입력 이름으로 사용할 수 있어 편리하지만, 이 식별자는 키워드이므로 괄호를 사용해 이스케이프 표시해야 하기 때문에 실제로 공유 메서드에서는 사용할 수가 없습니다.
확장 메서드도 일반 공유 메서드이기 때문에 Visual Basic에서 다른 공유 함수를 호출하는 것처럼 Count 함수를 호출할 수 있습니다. 그러나 확장 메서드의 경우에는 작동할 인스턴스 컬렉션을 명시적으로 지정해야 합니다.
Dim TotalSmallCountries = _
MyExtensions.Count(Select Country _
From Country In Countries _
Where Country.Population < 1000000)
일반 Imports 문을 통해 확장 메서드를 범위 내로 가져올 수 있습니다. 이러한 확장 메서드는 첫 번째 매개 변수 위치에 추가 메서드로 사용됩니다.
Imports MyExtensions
Dim TotalSmallCountries = _
(Select Country _
From Country In Countries _
Where Country.Population < 1000000).Count()
확장 메서드의 우선 순위는 일반 인스턴스 메서드보다 낮습니다. 일반 호출 식 처리에서 적용 가능한 인스턴스 메서드를 찾지 못하는 경우 컴파일러는 해당 호출을 확장 메서드 호출로 해석합니다.
그러나 이 쿼리는 앞서 살펴본 것처럼 집계 구문을 사용하여 작성하는 것이 가장 일반적입니다.
Dim TotalSmallCountries = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000
중첩된 함수
Where, Select, SelectMany 등 대부분의 표준 쿼리 연산자는 Func(Of S,T) 형식 대리자를 인수로 취하는 확장 메서드로 정의됩니다. 컴파일러가 이해를 기본 쿼리 연산자로 변환하도록 하거나 Visual Basic 프로그래머가 쿼리 연산자를 직접 호출할 수 있도록 하려면 쉽게 대리자를 만들 수 있어야 합니다. 특히 주변 컨텍스트를 캡처하는 대리자인 클로저(closure)를 만들 수 있어야 합니다. Visual Basic의 클로저(closure) 작성 메커니즘은 로컬 함수 및 서브루틴 선언으로 이루어집니다.
중첩된 함수 사용을 표시하기 위해 System.Query 네임스페이스에 정의된 원시 기본 쿼리 연산자를 호출합니다. 확장 메서드 중 하나는 테스트가 참일 때 시퀀스에서 요소를 내보낸 다음 나머지 시퀀스 부분은 건너뛰는 TakeWhile 함수입니다.
<Extension> _
Shared Function TakeWhile(Of T) _
(source As IEnumerable(Of T), Predicate As Func(Of T, Boolean)) _
As IEnumerable(Of T)
OrderByDescending 연산자는 증명된 정렬 키에 따라 해당 인수 컬렉션을 내림차순으로 정렬합니다.
<Extension> _
Shared Function OrderByDescending (T, K As IComparable(Of K)) _
(Source As IEnumerable(Of T), KeySelector As Func(Of T, K)) _
As OrderedSequence(Of T)
인구가 1백만 이하인 모든 국가를 찾는 또 다른 방법은 먼저 국가를 인구별로 정렬한 다음 TakeWhile을 사용하여 인구가 1백만 이하인 모든 국가를 골라내는 것입니다.
Function Population(Country As Country) As Integer
Return Country.Population
End Function
Function LessThanAMillion(Country As Country) As Boolean
Return Country.Population < 1000000
End Function
Dim SmallCountries = _
Countries.OrderBy(AddresOf Population) _
.TakeWhile(AddresOf LessThanAMillion)
쿼리 함축 구문(Query Comprehension)에는 필요하지 않지만 Visual Basic에서는 익명 함수 및 서브루틴(lambda 식이라고 함)에 대한 직접 구문을 지원합니다. 이는 컴파일러에 의해 로컬 함수 선언으로 변환됩니다.
Null 허용 형식
관계형 데이터베이스는 종종 일반 프로그래밍 언어와 일치하지 않아 프로그래머들에게 익숙하지 않은 Null 가능 값에 대한 구문을 제공합니다. 데이터를 많이 사용하는 응용 프로그램의 경우에는 이러한 구문을 확실하고 정확하게 처리해야 합니다. 이러한 필요성을 감안해 "Whidbey"에서는 CLR에 Nullable(Of T As Struct) 일반 형식을 사용한 런타임 Null 허용 가능성 지원이 추가되었습니다. 이러한 형식을 사용하면 Integer, Boolean, Date와 같은 값 형식의 Null 허용 버전을 선언할 수 있습니다. Visual Basic의 Null 허용 형식 구문은 T?이며, 그 이유는 이후에 설명합니다.
예를 들어 일부 국가는 독립국이 아니기 때문에 해당하는 경우 독립 날짜를 나타내는 새로운 멤버를 Country 클래스에 추가할 수 있습니다.
Partial Class Country
Public Property Independence As Date?
End Class
배열 형식과 마찬가지로 다음 선언처럼 속성 이름에 Null 허용 한정자를 붙일 수 있습니다.
Partial Class Country
Public Property Independence? As Date
End Class
팔라우의 독립 날짜는 #10/1/1994#이지만 영국령 버전 아일랜드는 영국 영토이기 때문에 해당 독립 날짜는 Nothing입니다.
Dim Palau = _
New Country { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952, _
.Independence = #10/1/1994# }
Dim VirginIslands = _
New Country { _
.Name = "Virgin Islands", _
.Area = 150, _
.Population= 13195, _
.Independence = Nothing }
Visual Basic 9.0에서는 Null 허용 값에 대해 세 가지 값의 논리 및 Null 전파 산술을 지원합니다. 즉, 산술, 비교, 논리 또는 비트 단위, 시프트, 문자열 또는 입력 연산의 피연산자 중 하나가 Nothing인 경우에는 결과도 Nothing입니다. 두 피연산자가 모두 올바른 값인 경우 연산은 기본 피연산자 값에 대해 수행되며 결과는 Null 허용 형식으로 변환됩니다.
Palau.Independence 및 VirginIslands.Independence에 모두 Date? 형식이 있기 때문에 컴파일러는 아래 뺄셈에 대해 Null 전파 산술을 사용합니다. 그러므로 PLength 및 VILength 로컬 선언에 대해 유추되는 형식은 모두 TimeSpan?이 됩니다.
Dim PLength = #8/24/2005# - Palau.Independence REM 3980.00:00:00
두 피연산자 모두 Nothing이 아니기 때문에 PLength 값은 3980.00:00:00입니다. 반대로 VirginIslands.Independence 값은 Nothing이므로, 결과는 동일하게 TimeSpan? 형식이지만 null 전파로 인해 VILength 값은 Nothing이 됩니다.
Dim VILength = #8/24/2005# - VirginIslands.Independence REM Nothing
SQL에서와 같이 비교 연산자가 Null 전파를 수행하며 논리 연산자가 3개 값 논리를 사용합니다. If 및 While 구문에서 Nothing은 False로 해석됩니다. 그러므로 다음 코드 조각에서는 Else 분기를 취하게 됩니다.
If VILength < TimeSpan.FromDays(10000)
...
Else
...
End If
3개 값 논리에서 동등 여부 검사인 X = Nothing과 Nothing = X는 항상 Nothing으로 평가됩니다. X가 Nothing인지를 확인하려면 2개 값 논리 비교인 X Is Nothing 또는 Nothing Is X를 사용해야 합니다.
런타임은 Object에서(로) 박싱(boxing) 및 언박싱(unboxing)을 수행할 때 Null 허용 값을 특수하게 취급합니다. Nothing을 나타내는 Null 허용 값(HasValue 속성이 False임)을 박싱하면 해당 값은 Null 참조로 박싱됩니다. 올바른 값(HasValue 속성이 True임)을 박싱하면 기본 값이 먼저 언래핑된 다음 박싱됩니다. 그렇기 때문에 힙에 있는 개체에는 Nullable(Of T) 동적 형식이 없으며 외견상 모든 형식은 T입니다. 즉, Object의 값을 T 또는 Nullable(Of T)로 언박싱할 수 있습니다. 그러나 이로 인해 후기 바인딩은 2개 값 논리를 사용할지 3개 값 논리를 사용할지를 동적으로 결정하지 못합니다. 예를 들어 두 수에 대해 초기 바인딩 덧셈을 수행할 때 두 수 중 하나가 Nothing이면 Null 전파가 사용되며 결과는 Nothing이 됩니다.
Dim A As Integer? = Nothing
Dim B As Integer? = 4711
Dim C As Integer? = A+B REM C = Nothing
그러나 같은 두 값에 대해 후기 바인딩 덧셈을 사용하는 경우 결과는 4711이 됩니다. 후기 바인딩에서는 A 및 B 모두의 동적 형식이 Integer?가 아닌 Integer임을 기반으로 하여 2개 값 논리를 사용하기 때문입니다. 그러므로 Nothing은 0으로 해석됩니다.
Dim X As Object = A
Dim Y As Object = B
Dim Z As Object = X+Y REM Z = 4711
올바른 구문을 만들려면 컴파일러가 다음과 같이 Null 전파 오버로드를 사용하도록 해야 합니다.
Operator +(x As Object?, y As Object?) As Object?
그렇게 하려면 다음과 같이 ? 연산자를 사용하여 두 피연산자 중 하나를 Null 허용 형식으로 변환해야 합니다.
Dim X As Object = A
Dim Y As Object = B
Dim Z As Object? = X?+Y REM Z = Nothing
이는 모든 T 형식에 대해 T?를 만들 수 있어야 함을 의미합니다. 기본 CLR Nullable(Of T As Struct) 형식에서는 인수 값이 Null 비허용 구조로만 제한되어 있습니다. Visual Basic 컴파일러는 T가 Null 비허용 값 형식이 아닌 경우 T?를 지우고 T로 대체하며, T가 Null 비허용 값 형식인 경우에는 Nullable(Of T)로 대체합니다. 컴파일러는 Visual Basic 프로그램 내에서 두 가지 경우 모두 정적 형식은 T?임을 기억할 수 있도록 충분한 내부 메타데이터를 보유합니다.
대리자 완화
Visual Basic 8.0에서 AddressOf 또는 Handles를 사용하여 대리자를 만드는 경우, 대리자 식별자에 대한 바인딩에 사용하는 메서드 중 하나의 서명이 대리자 형식의 서명과 정확하게 일치해야 합니다. 아래 예제에서 OnClick 서브루틴의 서명은 Delegate Sub EventHandler(sender As Object, e As EventArgs) 이벤트 처리기 대리자 서명과 정확하게 일치해야 합니다. 이는 Button 형식에서 백그라운드로 선언됩니다.
Dim WithEvents B As New Button()
Sub OnClick(sender As Object, e As EventArgs) Handles B.Click
MessageBox.Show("Hello World:" + B.Text)
End Sub
그러나 대리자가 아닌 함수 및 서브루틴을 호출할 때는 실제 인수가 호출할 메서드 중 하나와 정확하게 일치할 필요가 없습니다. 다음 코드에서 볼 수 있듯이, 각각 Object 및 EventArgs 형식 매개 변수의 하위 형식인 Button 및 MouseEventArgs 형식의 실제 인수를 사용하여 OnClick 서브루틴을 실제로 호출할 수 있습니다.
Dim M As New MouseEventArgs(MouseButtons.Left, 2, 47, 11,0)
OnClick(B, M)
반대로 두 개의 Object 매개 변수를 취하는 RelaxedOnClick 서브루틴을 정의한 다음 Object 및 EventArgs 형식의 실제 인수를 사용하여 해당 서브루틴을 호출할 수 있다고 가정해 봅니다.
Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show("Hello World:" + B.Text))
End Sub
Dim E As EventArgs = M
Dim S As Object = B
RelaxedOnClick(B,E)
Visual Basic 9.0에서는 대리자에 대한 바인딩이 메서드 호출과 일치하도록 완화되었습니다. 즉, 형식 매개 변수 및 대리자 반환 형식과 정확하게 일치하는 실제 인수를 사용하여 함수나 서브루틴을 호출할 수 있는 경우에는 해당 함수나 서브루틴을 대리자에 바인딩할 수 있습니다. 다시 말해서 대리자 바인딩 및 정의 과정에서 따르는 오버로드 해결 논리는 메서드 호출의 논리와 같습니다.
이는 Visual Basic 9.0를 사용하면 두 개의 Object 매개 변수를 취하는 RelaxedOnClick 서브루틴을 Button의 Click 이벤트로 바인딩할 수 있음을 의미합니다.
Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show(("Hello World:" + B.Text)
End Sub
이벤트 처리기에 대한 두 인수(sender 및 EventArgs)는 크게 문제가 되지 않습니다. 처리기는 이벤트가 등록되는 컨트롤의 상태에 직접 액세스하며 해당 두 인수를 무시하기 때문입니다. 이와 같은 일반적인 사례를 지원하기 위해 결과가 모호해지지 않는 경우에는 대리자가 인수를 취하지 않도록 완화할 수 있습니다. 다시 말해 코드를 다음과 같이 간단하게 작성할 수 있습니다.
Sub RelaxedOnClick Handles B.Click
MessageBox.Show("Hello World:" + B.Text)
End Sub
대리자 완화는 AddressOf 또는 대리자 작성 식을 사용하여 대리자를 만들 때 메서드 그룹이 후기 바인딩 호출인 경우에도 적용됩니다.
Dim F As EventHandler = AddressOf RelaxedOnClick
Dim G As New EventHandler(AddressOf B.Click)
동적 인터페이스: 강력한 "덕 타이핑(Duck Typing)"
C#, Java 또는 Option Strict On을 설정한 Visual Basic 등 순수하게 정적으로 형식이 지정된 언어에서는 컴파일 시에 존재하는 대상 식에 대해서만 멤버를 호출할 수 있습니다. 예를 들어 아래의 두 번째 대입에는 Country 클래스에 Inflation 멤버가 없기 때문에 컴파일 시 오류가 발생합니다.
Dim Palau As Country = Countries(0)
Dim Inflation = Country.Inflation
그러나 대부분의 경우에는 대상 형식을 정적으로 파악하지 못해도 멤버에 액세스해야 합니다. Visual Basic에서는 Option Strict Off를 사용하면 Object 형식의 대상에 대한 후기 바인딩 멤버에 액세스할 수 있습니다. 후기 바인딩은 강력하고 매우 융통성 있는 기능이기는 하지만, 단점도 있습니다. 특히 IntelliSense 및 형식 유추를 사용할 수 없으며, 초기 바인딩으로 되돌아가려면 캐스트 또는 명시적 형식 지정이 필요합니다.
종종 후기 바인딩 호출을 수행할 때도 해당 값이 특정 "인터페이스"를 따른다고 가정합니다. 개체는 해당 인터페이스를 충족하면 됩니다. 동적 언어 커뮤니티에서는 이를 "덕 타이핑(Duck Typing)"이라고 합니다. 즉, 오리처럼 걷고 오리처럼 말하면 그것은 오리라는 것입니다. 덕 타이핑(Duck Typing)의 개념을 설명하기 위해 아래 예제에서는 Country 또는 사람을 나타내는 새 익명 형식을 무작위로 반환합니다. 이 두 형식에는 모두 String 형식의 Name 속성이 있습니다.
Function NamedThing() As Object
Dim Static RandomNumbers = New Random()
Dim I = RandomNumbers.Next(9)
If I < 5
NamedThing = Countries(I)
Else
NamedThing = New{ .Name = "John Doe", .Age = 42-I }
End If
End Function
Dim Name = CStr(NamedThing().Name)
후기 바인딩 호출 CStr(NamedThing())을 수행할 때는 NamedThing()에 의해 반환된 값에 String 형식의 Name 멤버가 있음을 정적으로 가정합니다. 새로운 동적 인터페이스 기능을 사용하면 이 가정을 명시적으로 표현할 수 있습니다. 정적 형식이 동적 인터페이스인 대상은 항상 후기 바인딩을 사용하여 액세스되지만, 멤버 액세스 형식은 정적으로 지정됩니다. 즉, IntelliSense와 형식 유추를 완전하게 사용할 수 있으며 캐스팅 또는 명시적 형식 지정 작업도 필요하지 않습니다.
<Dynamic> _
Interface IHasName
Property Name As String
End Interface
Dim Thing As IHasName = NamedThing()
Dim Name = Thing.Name REM Inferred As String.
동적 식별자
동적 인터페이스는 프로그래머가 후기 바인딩 호출 결과로 표시될 멤버 이름 및 서명을 정적으로 파악하고 있다고 간주한다는 사실을 활용합니다. 그러나 실제로 특정 동적 시나리오에서는 정적으로 호출할 멤버의 이름조차도 모르는 경우가 있습니다. 동적 식별자를 사용하면 호출 식의 식별자가 동적으로 계산되는 매우 늦은 후기 바인딩 호출을 수행할 수 있습니다.
다음 예제에서는 세 개의 클래스를 영어, 네덜란드어 및 프랑스어의 세 가지 언어로 선언합니다. 각 언어에는 "Name" 필드가 각각 포함되어 있습니다.
Class English
Name As String
End Class
Class Nederlands
Naam As String
End Class
Class Francais
Nom As String
End Class
Person에 대한 이 "Name" 필드에 액세스하기 위해 값을 반사하여 형식 이름을 가져오고 테이블에서 멤버 이름을 조회합니다. 그런 다음 동적 식별자를 사용하여 Person에 대해 올바른 멤버를 호출할 수 있습니다.
Dim Dictionary = New Dictionary(Of String, String) { _
.Add("English", "Name"), _
.Add("Nederlands", "Naam"), _
.Add("Francais", "Nom") }
Dim Person As Object = New Francais { .Nom = "Eiffel" }
Dim Name As String = Person.(Dictionary(Person.GetType().Name))
결론
Visual Basic 9.0에는 다양한 새 기능이 도입되었습니다. 이 문서에서는 일련의 연결된 예제를 통해 이러한 기능을 설명했지만, 다음과 같은 기본적인 개념도 파악해야 합니다.
- 관계형, 개체 및 XML 데이터. Visual Basic 9.0에서는 메모리에 보존 또는 저장된 모든 관계형 데이터베이스, XML 문서 또는 추상 개체 그래프 등의 데이터에 대한 액세스가 원본에 관계없이 통합되었습니다. 스타일, 기술, 도구 및 프로그래밍 패턴 측면에서 모두 통합 효과를 경험할 수 있습니다. 융통성이 매우 높은 Visual Basic의 구문을 사용하면 XML 리터럴 및 SQL 형식 쿼리 함축 구문(Query Comprehension) 등의 확장 기능을 언어에 추가할 수 있습니다. 이를 통해 새로운 .NET LINQ API의 "작업 영역"이 현저하게 줄어들고, IntelliSense 및 스마트 태그를 통한 데이터 액세스 기능 활용도가 높아지며, 외부 구문을 문자열 데이터에서 호스트 언어로 변환함으로써 디버깅 작업이 크게 개선됩니다. 향후에는 XSD 스키마를 활용함으로써 XML 데이터 일관성을 더욱 향상시킬 계획입니다.
- 정적 형식 지정의 이점을 통해 향상된 동적 특성. 정적 형식 지정의 이점은 널리 알려져 있습니다. 즉, 런타임이 아닌 컴파일 시에 버그를 식별하고, 초기 바인딩 액세스를 통해 성능을 높이며, 명시적인 소스 코드 작성으로 작업을 명확하게 수행하는 등 여러 가지 이점이 있습니다. 그러나 동적 형식 지정을 사용할 때 코드가 보다 짧아지고 융통성이 높아지는 경우도 있습니다. 특정 언어에서 동적 형식 지정을 지원하지 않으면 프로그래머는 필요한 경우 반사, 사전, 발송 테이블 및 기타 기술을 통해 동적 구조를 구현해야 합니다. 그러나 이렇게 하면 버그 발생 가능성이 높아지고 유지 관리 비용도 늘어납니다. 경우에 따라 적절하게 정적 및 동적 형식 지정을 모두 지원함으로써 Visual Basic은 프로그래머가 두 형식 지정의 이점을 모두 활용할 수 있도록 합니다.
- 프로그래머의 인지 필요성 감소. 형식 유추, 개체 이니셜라이저 및 대리자 완화 등의 기능을 통해 성능에는 전혀 영향을 주지 않고 중복되는 코드 및 프로그래머가 익히고 기억하거나 조회해야 하는 규칙 예외 수를 크게 줄일 수 있습니다. 동적 인터페이스와 같은 기능은 후기 바인딩의 경우에도 IntelliSense를 지원하여 고급 기능 활용도를 크게 높여 줍니다.
Visual Basic 9.0에 새로운 기능이 매우 많다고 생각될 수도 있지만, 이러한 모든 기능이 앞서 언급한 기본적인 개념을 기반으로 결합되어 Visual Basic을 현존하는 가장 뛰어난 프로그래밍 시스템으로 만들어 준다는 사실을 기억해 주십시오. 창의적으로 작업을 수행해 보시기를 바랍니다. 이 기사의 내용은 Visual Basic 9.0에서 제공될 더욱 뛰어난 기능 중 일부일 뿐입니다.