확장 메서드(C# 프로그래밍 가이드)

업데이트: 2010년 10월

확장 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다. 확장 메서드는 특수한 종류의 정적 메서드이지만 확장 형식의 인스턴스 메서드인 것처럼 호출됩니다. C# 및 Visual Basic에서 작성된 클라이언트 코드의 경우 확장 메서드를 호출하는 것과 형식에 실제로 정의된 메서드를 호출하는 데는 명백한 차이가 없습니다.

가장 일반적인 확장 메서드는 System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T> 형식에 쿼리 기능을 추가하는 LINQ 표준 쿼리 연산자입니다. 표준 쿼리 연산자를 사용하려면 먼저 using System.Linq 지시문을 사용하여 범위로 가져와야 합니다. 그러면 IEnumerable<T>을 구현하는 모든 형식에 GroupBy, OrderBy, Average 등의 인스턴스 메서드가 있는 것처럼 나타납니다. List<T> 또는 Array와 같은 IEnumerable<T> 형식의 인스턴스 뒤에 "dot"를 입력하면 IntelliSense 문 완성에서 이러한 추가 메서드를 볼 수 있습니다.

다음 예제에서는 정수 배열에서 표준 쿼리 연산자 OrderBy를 호출하는 방법을 보여 줍니다. 괄호 안의 식은 람다 식입니다. 많은 표준 쿼리 연산자가 람다 식을 매개 변수로 사용하지만 확장 메서드에 대한 요구 사항은 아닙니다. 자세한 내용은 람다 식(C# 프로그래밍 가이드)을 참조하십시오.

class ExtensionMethods2    
{

    static void Main()
    {            
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }           
    }        
}
//Output: 10 15 21 26 39 45

확장 메서드는 정적 메서드로 정의되지만 인스턴스 메서드 구문을 사용하여 호출됩니다. 확장 메서드의 첫 번째 매개 변수는 메서드가 작동하는 형식을 지정하며 매개 변수 앞에 this 한정자가 있습니다. 확장 메서드는 using 지시문을 사용하여 명시적으로 네임스페이스를 소스 코드로 가져오는 경우에만 범위에 있습니다.

다음 예제에서는 System.String 클래스에 대해 정의된 확장 메서드를 보여 줍니다. 이 확장 메서드는 제네릭이 아닌 비중첩 정적 클래스 내부에서 정의됩니다.

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

using 지시문을 사용하여 WordCount 확장 메서드를 범위로 가져올 수 있습니다.

using ExtensionMethods;

또한 다음 구문을 사용하여 응용 프로그램에서 확장 메서드를 호출할 수 있습니다.

string s = "Hello Extension Methods";
int i = s.WordCount();

코드에서 인스턴스 메서드 구문을 사용하여 확장 메서드를 호출합니다. 그러나 컴파일러에서 생성된 IL(중간 언어)이 코드를 정적 메서드 호출로 변환합니다. 따라서 실제로 캡슐화의 원칙을 위반하지 않습니다. 사실상 확장 메서드는 확장하는 형식의 private 변수에 액세스할 수 없습니다.

자세한 내용은 방법: 사용자 지정 확장 메서드 구현 및 호출(C# 프로그래밍 가이드)을 참조하십시오.

일반적으로 확장 메서드를 직접 구현하는 것보다 호출하는 경우가 훨씬 많습니다. 확장 메서드는 인스턴스 메서드 구문을 사용하여 호출되므로 특별한 지식이 없어도 클라이언트 코드에서 확장 메서드를 사용할 수 있습니다. 특정 형식의 확장 메서드를 사용하려면 해당 메서드가 정의된 네임스페이스에 대해 using 지시문을 추가합니다. 예를 들어 표준 쿼리 연산자를 사용하려면 다음 using 지시문을 코드에 추가합니다.

using System.Linq;

System.Core.dll에 대한 참조를 추가해야 할 수도 있습니다. 이제 표준 쿼리 연산자가 대부분의 IEnumerable<T> 형식에 사용할 수 있는 추가 메서드로 IntelliSense에 표시됩니다.

참고

String에 대한 표준 쿼리 연산자는 IntelliSense에는 표시되지 않지만 사용할 수 있습니다.

컴파일 타임에 확장 메서드 바인딩

확장 메서드를 사용하여 클래스 또는 인터페이스를 확장할 수 있지만 재정의할 수는 없습니다. 이름과 시그니처가 인터페이스 또는 클래스 메서드와 동일한 확장 메서드는 호출되지 않습니다. 컴파일 타임에 확장 메서드는 항상 형식 자체에서 정의된 인스턴스 메서드보다 우선 순위가 낮습니다. 즉, 형식에 Process(int i)라는 메서드가 있고 동일한 시그니처를 가진 확장 메서드가 있는 경우 컴파일러는 항상 인스턴스 메서드에 바인딩합니다. 컴파일러는 메서드 호출을 발견할 경우 먼저 형식의 인스턴스 메서드에서 일치 항목을 찾습니다. 일치 항목이 없으면 형식에 대해 정의된 확장 메서드를 검색하고 찾은 첫 번째 확장 메서드에 바인딩합니다. 다음 예제에서는 컴파일러가 바인딩할 확장 메서드 또는 인스턴스 메서드를 확인하는 방법을 보여 줍니다.

예제

다음 예제에서는 C# 컴파일러가 메서드 호출을 형식의 인스턴스 메서드 또는 확장 메서드에 바인딩할 것인지 결정할 때 따르는 규칙을 보여 줍니다. 정적 클래스 Extensions는 IMyInterface를 구현하는 모든 형식에 대해 정의된 확장 메서드를 포함합니다. A, B 및 C 클래스는 모두 인터페이스를 구현합니다.

MethodB 확장 메서드는 이름과 시그니처가 클래스에서 이미 구현된 메서드와 정확하게 일치하므로 호출되지 않습니다.

일치하는 시그니처를 가진 인스턴스 메서드를 찾을 수 없으면 컴파일러는 일치하는 확장 메서드(있는 경우)에 바인딩합니다.

// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}


// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any 
    // class that implements IMyInterface.
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each 
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}


// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.

            // A contains no MethodA, so each call to MethodA resolves to 
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(object, int)
            a.MethodA("hello");     // Extension.MethodA(object, string)

            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()

            // B has methods that match the signatures of the following
            // nethod calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method for the following call, but 
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(object, string)

            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

일반 지침

일반적으로 반드시 필요한 경우에만 드물게 확장 메서드를 구현하는 것이 좋습니다. 가능하면 기존 형식을 확장해야 하는 클라이언트 코드는 기존 형식에서 파생된 새 형식을 만들어 이 작업을 수행해야 합니다. 자세한 내용은 상속(C# 프로그래밍 가이드)을 참조하십시오.

기존 메서드를 사용하여 소스 코드를 변경할 수 없는 형식을 확장하는 경우 형식의 구현이 변경되어 확장 메서드가 손상될 수도 있습니다.

지정된 형식에 대해 확장 메서드를 구현하는 경우 다음 두 가지 사항을 명심하십시오.

  • 시그니처가 형식에 정의된 메서드와 동일한 확장 메서드는 호출되지 않습니다.

  • 확장 메서드는 네임스페이스 수준에서 범위로 가져옵니다. 예를 들어 Extensions라는 단일 네임스페이스에 확장 메서드를 포함하는 여러 개의 정적 클래스가 있는 경우 using Extensions; 지시문을 통해 모두 범위로 가져옵니다.

참고 항목

참조

람다 식(C# 프로그래밍 가이드)

개념

C# 프로그래밍 가이드

표준 쿼리 연산자 개요

기타 리소스

매개 변수 및 그 영향과 같은 변환 규칙

언어 간의 확장 메서드 상호 운용성

확장 메서드 및 커리된 대리자

확장 메서드 바인딩 및 오류 보고

변경 기록

날짜

변경 내용

이유

2010년 10월

마지막 예제를 명확히 설명했습니다.

고객 의견