Section 48.1: 확장 (Extension) 메소드의 개요

확장 (Extension) 메소드는 C# 3.0 에서 처음 소개되었다. 확장 메소드를 이용하면 기존 타입을 상속하거나, 재컴파일하거나, 혹은 어떠한 방식으로든 수정하지 않고도 새로운 기능을 추가함으로써 확장시킬 수 있다. 특히 이는 기능을 개선하고자 하는 타입의 소스 코드를 수정할 수 없는 경우 특히 유용하게 사용할 수 있다. 확장 메소드는 시스템 타입, 서드파티에 의해 정의된 타입들과 사용자 스스로가 정의한 타입 모두에 대해 새로이 생성될 수 있다. 이러한 확장 메소드는 마치 원본 타입에서 원래 제공되는 멤버 메소드들과 마찬가지로 호출될 수 있다. 이를 활용하면 Method Chaining 을 통한 Fluent Interface 구현이 가능하다.

역주: Method Chaining 과 Fluent Interface 에 대한 간략한 설명은 이 링크 에서 찾아보실 수 있습니다.

확장 메소드는 확장하고자 하는 원본 타입과는 별개의 정적 (static) 클래스에 정적 메소드를 추가함으로써 생성할 수 있다. 이러한 확장 메소드가 포함된 정적 클래스는 오로지 정적 메소드들을 선언하기 위한 용도로만 생성되는 경우가 많다.

확장 메소드는 특별한 첫번째 파라미터를 가지는데, 이는 바로 확장하고자 하는 대상이 되는 원본 타입을 가리킨다. 이러한 첫번째 파라미터는 this 키워드를 이용해 특별히 나타내어진다 (C# 에서의 일반적인 this 사용과 구분된다 — 일반적인 this 는 현재 객체 인스턴스의 멤버를 가리키기 위해 사용된다).

다음 예제에서, 확장하고자 하는 대상 타입은 string 클래스이다. string 은 축약된 문자열을 반환하는 Shorten() 이라는 메소드가 추가되는 방식으로 확장되었다. 정적 클래스인 StringExtensions 은 해당 확장 메소드를 내부에 선언하기 위한 용도로 생성되었다. 확장 메소드인 Shorten()this 라고 특수하게 표시된 첫번째 파라미터를 통해 string 클래스를 확장하고 있음을 나타내고 있다. 따라서, 첫번째 파라미터의 전체 형식은 this string text 가 되며, 여기서 string 은 확장 대상이 되는 원본 타입을 지칭하며 text 는 해당 파라미터의 이름을 나타낸다.

static class StringExtensions { public static string Shorten(this string text, int length) { return text.Substring(0, length); } } class Program { static void Main() { // String.ToUpper() 메소드를 호출한다 var myString = "Hello World!".ToUpper(); // StringExtensions.Shorten() 확장 메소드를 호출한다 var newString = myString.Shorten(5); // 위 함수 호출은 사실 문법적 간편화 장치 (syntactic sugar) 에 불과하며 // 아래 예제와 기능적으로 동일하다는 점을 알아둘 필요가 있다 var newString2 = StringExtensions.Shorten(myString, 5); } }

.NET Fiddle 에서 라이브 데모 확인하기

확장 메소드의 첫번째 파라미터로 넘겨진 (this 키워드와 함께 기술된) 객체는 해당 확장 메소드가 호출될 대상 인스턴스를 나타낸다.

예를 들어, 아래의 코드 실행시:

"some string".Shorten(5);

넘겨진 파라미터들의 값들은 다음과 같을 것이다:

text: "some string" length: 5

유의할 점은, 확장 메소드는 사용하는 namespace 와 동일한 namespace 에 정의가 되거나, 혹은 확장 메소드를 사용하는 코드에 해당 namespace 가 명시적으로 불러들여 지거나, 아니면 확장 클래스가 namespace 를 갖지 않을때만 사용 가능하다는 점이다. .NET framework 가이드라인에서는 확장 클래스를 고유의 namespace 에 위치하도록 권장하고 있으나, 실제로 이로 인해 접근성 문제가 야기될 수도 있다.

위 특성으로 인해, 충돌 (conflict) 이 발생할 수 있는 확장 메소드를 명시적으로 불러들이지 않는 이상, 확장 메소드와 사용하는 라이브러리간의 충돌을 피할 수 있게 된다. LINQ 확장의 예를 살펴보자:

using System.Linq; // System.Linq namespace 의 확장 메소드를 사용할 수 있게된다 class Program { static void Main() { var ints = new int[] { 1, 2, 3, 4 }; // System.Linq namespace 로부터 Where() 확장 메소드를 호출한다 var even = ints.Where(x => x % 2 == 0); } }

.NET Fiddle 에서 라이브 데모 확인하기

C# 6.0 부터는, 확장 메소드를 포함하는 클래스에 대해 using static 지시어를 사용할 수 있다. 예제는 다음과 같다: using static System.Linq.Enumerable;. 이는 같은 namespace 의 다른 타입들을 현재 scope 에 불러들이지 않으면서도 해당 특정 클래스에 선언된 확장 메소드를 사용할 수 있게 해준다.

만약 동일한 서명 (signature) 을 갖는 클래스 메소드가 존재하는 경우에는, 컴파일러는 해당 메소드를 확장 메소드보다 우선하여 호출해준다. 아래 예제를 확인한다:

class Test { public void Hello() { Console.WriteLine("From Test"); } } static class TestExtensions { public static void Hello(this Test test) { Console.WriteLine("From extension method"); } } class Program { static void Main() { Test t = new Test(); t.Hello(); // "From Test" 가 출력된다 } }

.NET Fiddle 에서 라이브 데모 확인하기

유의하여야 할 점은, 만약 동일한 서명(signature) 를 갖는 확장 메소드가 두개 존재하며, 그중 하나가 동일 네임스페이스에 속한 경우에는 해당 확장 메소드를 우선하여 사용하게 될 것이라는 점이다. 반면에, 두 메소드가 모두 using 키워드를 통하여 접근되는 경우에는, 아래와 같은 내용으로 컴파일 에러가 발생할 것이다:

The call is ambiguous between the following methods or properties

또한 알아둘 점은, originalTypeInstance.ExtensionMethod() 와 같이 문법적으로 간편화된 방식의 확장 메소드 호출은 선택적인 (optional) 편의기능이라는 점이다. 첫번째 특수 파라미터에 메소드가 호출될 객체를 전달하는 전통적인 방식의 호출 방법을 통해서도 해당 메소드를 호출할 수 있다.

따라서, 아래 두가지 예제 모두 정상 동작할 것이다:

// 확장 메소드가 string 클래스에 속한 것처럼 호출하기 -- string 클래스를 심리스 (seamless) 하게 확장할 수 있다 String s = "Hello World"; s.Shorten(5); // 전통적인 방법으로 두개의 파라미터를 static 메소드에 전달하는 방식의 호출 StringExtensions.Shorten(s, 5);
본 문서는 C# Notes for Professionals (라이센스:CC-BY-SA) 를 한글로 번역한 문서입니다. 번역상 오류가 있을 수 있으므로 정확한 내용은 원본 문서를 참고하세요.

[출처] https://books.goalkicker.com/CSharpBook/

반응형

+ Recent posts