확장 메서드(Visual Basic)

확장 메서드를 사용하면 개발자는 새 파생 형식을 만들지 않고도 이미 정의된 데이터 형식에 사용자 지정 기능을 추가할 수 있습니다. 확장 메서드를 사용하면 기존 형식의 인스턴스 메서드인 것처럼 호출할 수 있는 메서드를 작성할 수 있습니다.

설명

확장 메서드는 Sub 프로시저 또는 Function 프로시저만 될 수 있습니다. 확장 속성, 필드 또는 이벤트를 정의할 수 없습니다. 모든 확장 메서드는 System.Runtime.CompilerServices 네임스페이스에서 확장 특성 <Extension>으로 표시되어야 하며 모듈에서 정의되어야 합니다. 확장 메서드가 모듈 외부에서 정의되면 Visual Basic 컴파일러는 "확장 메서드는 모듈에서만 정의할 수 있습니다."라는 오류 BC36551을 생성합니다.

확장 메서드 정의의 첫 번째 매개 변수는 메서드가 확장하는 데이터 형식을 지정합니다. 메서드가 확장되면 첫 번째 매개 변수가 메서드를 호출하는 데이터 형식의 인스턴스에 바인딩됩니다.

Extension 특성은 Visual Basic Module, Sub, Function에만 적용될 수 있습니다. 이를 Class 또는 Structure에 적용하는 경우 Visual Basic 컴파일러에서 오류 BC36550을 생성합니다. "'Extension' 특성은 'Module', 'Sub' 또는 'Function' 선언에만 적용할 수 있습니다."

예시

다음 예제에서는 String 데이터 형식에 대한 Print 확장을 정의합니다. 메서드는 Console.WriteLine을 사용하여 문자열을 표시할 수 있습니다. Print 메서드의 매개 변수인 aString은 메서드가 String 클래스를 확장하도록 설정합니다.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()> 
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

End Module

확장 메서드 정의는 확장 특성 <Extension()>으로 표시됩니다. 메서드가 정의되는 모듈을 표시하는 것은 선택 사항이지만 각 확장 메서드는 반드시 표시해야 합니다. 확장 특성에 액세스하려면 System.Runtime.CompilerServices를 가져와야 합니다.

확장 메서드는 모듈 내에서만 선언할 수 있습니다. 일반적으로 확장 메서드가 정의된 모듈은 호출되는 모듈과 동일하지 않습니다. 대신 필요한 경우 확장 메서드를 포함하는 모듈을 가져와서 범위로 가져옵니다. Print를 포함하는 모듈이 범위 내에 있으면 ToUpper과 같이 인수를 사용하지 않는 일반 인스턴스 메서드인 것처럼 메서드를 호출할 수 있습니다.

Module Class1

    Sub Main()

        Dim example As String = "Hello"
        ' Call to extension method Print.
        example.Print()

        ' Call to instance method ToUpper.
        example.ToUpper()
        example.ToUpper.Print()

    End Sub

End Module

다음 예제인 PrintAndPunctuate도 두 개의 매개 변수로 정의되는 String의 확장이기도 합니다. 첫 번째 매개 변수인 aString은 확장 메서드가 String을 확장하도록 설정합니다. 두 번째 매개 변수인 punc는 메서드가 호출될 때 인수로 전달되는 문장 부호 문자열로 사용됩니다. 메서드는 문자열 뒤에 문장 부호를 표시합니다.

<Extension()> 
Public Sub PrintAndPunctuate(ByVal aString As String, 
                             ByVal punc As String)
    Console.WriteLine(aString & punc)
End Sub

메서드는 punc: example.PrintAndPunctuate(".")에 대한 문자열 인수를 전송함으로써 호출됩니다.

다음 예제에서는 PrintPrintAndPunctuate를 정의하고 호출합니다. 확장 특성에 대한 액세스를 사용하도록 설정하기 위해 정의 모듈에서 System.Runtime.CompilerServices를 가져옵니다.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()>
    Public Sub Print(aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Sub PrintAndPunctuate(aString As String, punc As String)
        Console.WriteLine(aString & punc)
    End Sub
End Module

다음으로, 확장 메서드를 범위로 가져와서 호출합니다.

Imports ConsoleApplication2.StringExtensions

Module Module1

    Sub Main()
        Dim example As String = "Example string"
        example.Print()

        example = "Hello"
        example.PrintAndPunctuate(".")
        example.PrintAndPunctuate("!!!!")
    End Sub
End Module

이러한 확장 메서드 또는 유사한 확장 메서드를 실행하려면 해당 메서드가 범위 내에 있어야 합니다. 확장 메서드를 포함하는 모듈이 범위 내에 있는 경우 IntelliSense에 표시되며 일반 인스턴스 메서드인 것처럼 호출할 수 있습니다.

메서드가 호출되면 첫 번째 매개 변수에 대한 인수가 전송되지 않습니다. 이전 메서드 정의의 매개 변수 aString은 이를 호출하는 String의 인스턴스인 example에 바인딩됩니다. 컴파일러는 첫 번째 매개 변수에 전송된 인수로 example을 사용합니다.

Nothing으로 설정된 개체에 대해 확장 메서드가 호출되면 확장 메서드가 실행됩니다. 일반 인스턴스 메서드에는 적용되지 않습니다. 확장 메서드에서 Nothing을 명시적으로 확인할 수 있습니다.

확장할 수 있는 형식

다음을 포함하여 Visual Basic 매개 변수 목록에 표시될 수 있는 대부분의 형식에서 확장 메서드를 정의할 수 있습니다.

  • 클래스(참조 형식)
  • 구조체(값 형식)
  • 인터페이스
  • 대리자
  • ByRef 및 ByVal 인수
  • 제네릭 메서드 매개 변수
  • 배열

첫 번째 매개 변수는 확장 메서드가 확장하는 데이터 형식을 지정하므로 필수 사항이며 선택이 될 수 없습니다. 따라서 Optional 매개 변수 및 ParamArray 매개 변수는 매개 변수 목록의 첫 번째 매개 변수가 될 수 없습니다.

확장 메서드는 런타임에 바인딩에서 고려되지 않습니다. 다음 예제에서 문 anObject.PrintMe()는 두 번째 PrintMe 확장 메서드 정의가 삭제된 경우와 동일한 예외인 MissingMemberException 예외를 발생합니다.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module4

    Sub Main()
        Dim aString As String = "Initial value for aString"
        aString.PrintMe()

        Dim anObject As Object = "Initial value for anObject"
        ' The following statement causes a run-time error when Option
        ' Strict is off, and a compiler error when Option Strict is on.
        'anObject.PrintMe()
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal str As String)
        Console.WriteLine(str)
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal obj As Object)
        Console.WriteLine(obj)
    End Sub

End Module

모범 사례

확장 메서드는 기존 형식을 확장하는 편리하고 강력한 방법을 제공합니다. 그러나 성공적으로 사용하려면 몇 가지 고려해야 할 사항이 있습니다. 이러한 고려 사항은 주로 클래스 라이브러리의 작성자에게 적용되지만 확장 메서드를 사용하는 모든 애플리케이션에 영향을 줄 수 있습니다.

가장 일반적으로 사용자가 소유하지 않은 형식에 추가하는 확장 메서드는 사용자가 제어하는 형식에 추가된 확장 메서드보다 더 취약합니다. 사용자가 소유하지 않은 클래스에서 확장 메서드를 방해할 수 있는 여러 가지 상황이 발생할 수 있습니다.

  • 인수에서 매개 변수로의 축소 변환 없이 호출 문의 인수와 호환되는 서명을 가진 액세스 가능한 인스턴스 멤버가 있는 경우 모든 확장 메서드보다 인스턴스 메서드를 먼저 사용해야 합니다. 따라서 특정 시점에 적절한 인스턴스 메서드가 클래스에 추가되면 사용하는 기존 확장 멤버에 액세스할 수 없게 될 수 있습니다.

  • 확장 메서드의 작성자는 다른 프로그래머가 원래 확장보다 우선 순위가 될 수 있는 서로 충돌하는 확장 메서드를 작성하는 것을 방지할 수 없습니다.

  • 확장 메서드를 자체 네임스페이스에 배치하여 견고성을 향상할 수 있습니다. 그런 다음, 라이브러리의 소비자는 네임스페이스를 포함하거나 제외할 수 있으며 라이브러리의 나머지 부분과 별도로 네임스페이스 중에서 선택할 수 있습니다.

  • 특히 인터페이스 또는 클래스를 소유하지 않는 경우 클래스를 확장하는 것보다 인터페이스를 확장하는 것이 더 안전할 수 있습니다. 인터페이스의 변경은 인터페이스를 구현하는 모든 클래스에 영향을 줍니다. 따라서 작성자는 인터페이스에서 메서드를 추가하거나 변경할 가능성이 적습니다. 그러나 클래스가 동일한 서명으로 확장 메서드가 있는 두 인터페이스를 구현하는 경우에는 두 확장 메서드는 모두 표시되지 않습니다.

  • 가능한 가장 구체적인 형식을 확장합니다. 형식 계층 구조에서 다른 형식이 많이 파생된 형식을 선택하는 경우 인스턴스 메서드 또는 사용자의 메서드를 간섭하는 다른 확장 메서드를 도입할 수 있는 가능성이 있습니다.

확장 메서드, 인스턴스 메서드, 속성

범위 내 인스턴스 메서드에 호출 문의 인수와 호환되는 서명이 있는 경우 인스턴스 메서드는 모든 확장 메서드보다 우선적으로 선택됩니다. 확장 메서드가 더 일치하는 경우에도 인스턴스 메서드가 우선 적용됩니다. 다음 예제에서 ExampleClass는 하나의 Integer 매개 변수 형식이 있는 ExampleMethod라는 인스턴스 메서드를 포함합니다. 확장 메서드 ExampleMethodExampleClass를 확장하며 Long 형식의 매개 변수가 하나 있습니다.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Extension method")
End Sub

arg1Long이고 확장 메서드의 Long 매개 변수와만 호환되므로 다음 코드에서 ExampleMethod에 대한 첫 번째 호출은 확장 메서드를 호출합니다. ExampleMethod에 대한 두 번째 호출에는 Integer 인수인 arg2가 있으며 인스턴스 메서드를 호출합니다.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the extension method.
    example.exampleMethod(arg1)
    ' The following statement calls the instance method.
    example.exampleMethod(arg2)
End Sub

이제 두 가지 메서드에서 매개 변수의 데이터 형식을 반대로 전환합니다.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Long)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Integer)
    Console.WriteLine("Extension method")
End Sub

이번에는 Main의 코드가 인스턴스 메서드를 두 번 호출합니다. 이는 arg1arg2 모두 Long으로 확대 변환이 있고 두 경우 모두 인스턴스 메서드가 확장 메서드보다 우선하기 때문입니다.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the instance method.
    example.ExampleMethod(arg1)
    ' The following statement calls the instance method.
    example.ExampleMethod(arg2)
End Sub

따라서 확장 메서드는 기존 인스턴스 메서드를 대체할 수 없습니다. 그러나 확장 메서드의 이름이 인스턴스 메서드의 이름과 같지만 서명이 충돌하지 않으면 두 메서드 모두에 액세스할 수 있습니다. 예를 들어 클래스 ExampleClass에 인수를 사용하지 않는 ExampleMethod라는 메서드가 포함된 경우 다음 코드와 같이 이름은 같지만 서명이 서로 다른 확장 메서드가 허용됩니다.

Imports System.Runtime.CompilerServices

Module Module3

    Sub Main()
        Dim ex As New ExampleClass
        ' The following statement calls the extension method.
        ex.ExampleMethod("Extension method")
        ' The following statement calls the instance method.
        ex.ExampleMethod()
    End Sub

    Class ExampleClass
        ' Define an instance method named ExampleMethod.
        Public Sub ExampleMethod()
            Console.WriteLine("Instance method")
        End Sub
    End Class

    <Extension()> 
    Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal stringParameter As String)
        Console.WriteLine(stringParameter)
    End Sub

End Module

이 코드의 출력은 다음과 같습니다.

Extension method
Instance method

속성의 경우 상황은 더 간단해집니다. 확장 메서드의 이름이 확장된 클래스의 속성 이름과 같은 경우 확장 메서드가 표시되지 않고 액세스할 수 없습니다.

확장 메서드 우선 순위

서명이 동일한 두 확장 메서드가 범위 내에 있고 액세스할 수 있는 경우 우선 순위가 더 높은 메서드가 호출됩니다. 확장 메서드의 우선 순위는 메서드를 범위로 가져오는 데 사용되는 메커니즘을 기반으로 합니다. 다음 목록에서는 우선 순위 계층을 가장 높은 계층에서 가장 낮은 계층으로 보여 줍니다.

  1. 현재 모듈 내부에 정의된 확장 메서드입니다.

  2. 부모 네임스페이스보다 우선 순위가 높은 자식 네임스페이스가 있는 현재 네임스페이스 또는 부모 네임스페이스 중 하나의 데이터 형식 내에 정의된 확장 메서드입니다.

  3. 현재 파일의 모든 형식 가져오기 내에 정의된 확장 메서드입니다.

  4. 현재 파일의 모든 네임스페이스 가져오기 내에 정의된 확장 메서드입니다.

  5. 프로젝트 수준 형식 가져오기 내에 정의된 확장 메서드입니다.

  6. 프로젝트 수준 네임스페이스 가져오기 내에 정의된 확장 메서드입니다.

우선 순위가 모호성을 해결하지 못하는 경우 정규화된 이름을 사용하여 호출하는 메서드를 지정할 수 있습니다. 이전 예제의 Print 메서드가 StringExtensions라는 모듈에 정의된 경우 정규화된 이름은 example.Print() 대신 StringExtensions.Print(example)입니다.

참고 항목