본문 바로가기
비주얼스튜디오

확장메소드

by 케야르 2010. 12. 21.

이번 강좌는 C# 3.0에서 추가된 확장 메서드에 대한 이야기입니다. "지금까지 너는 ASP.NET 강좌만 주욱 써 온데다가, 솔직히 기술 수준도 낮아 보이는 네가 갑자기 언어 관련 글을 쓴다니 우습지도 않구나"라고 속으로 말하는 거 다 들립니다. 물론, 저도 오랜만에(?) 언어 관련 기능에 대한 글을 쓰려니 조금은 어색하긴 합니다만, 이 기능은 여러분의 업무에 상당히 도움이 될만한 내용이기에 훑어보는 차원에서 다뤄볼까 합니다. 일단, 시작합니다.

확장 메서드, 즉, Extension Method란 일반적인 인스턴스 메서드 구문(클래스의 인스턴스를 생성한 후, 메서드를 호출하는 구문)을 사용하여 호출할 수 있는 정적 메서드를 말합니다. 즉, 다음의 str 이라는 변수가 string 형식이라고 가정할 경우,

str.ConvertToAngel();

과 같이 호출하여 사용할 수 있는 메서드(위의 경우, ConvertToAngel () 메서드)를 확장 메서드라고 한다는 것입니다.

물론, ConvertToAngel()라는 메서드는 String 클래스에서 제공되는 메서드가 아닙니다. '엔젤'이 들어가 있는 것부터가 심상치 않은 저 명칭의 메서드가 String 클래스에 존재할 리가 없잖습니까? 그럼에도, 저렇게 호출이 가능하도록 개발자가 메서드를 작성하여 기존 클래스에 접목시킬 수 있게 하는 기능을 확장 메서드라 하며, 이러한 확장 기능은 C# 3.0부터 지원되고 있습니다. 이는 비교적 따끈따근한 기능이라는 말도 됩니다.

그렇다면, 어떻게 이러한 확장이 가능한 것일까요? 그리고, 그렇게 확장을 하려면 무엇을 어떻게 해야 하는 것일까요? 그리고, 저렇게 사용하는 것이 도대체 뭐가 어떻다는(좋다는 건지, 나쁘다는 건지) 건가요? 궁금증은 꼬리에 꼬리를 물 것입니다(아아. 물론, 수업시간에 젤 뒷자리에서 과제 등의 이유로 인해 이 글을 보고 있는 몇몇 분은 전혀 이러한 궁금증을 갖지 않는 것을 압니다. 흥!).

그렇다면, 헬퍼(Helper) 메서드에 대한 이야기를 본격적으로 시작하도록 하겠습니다.

응? 헬퍼 메서드? 왠 헬퍼 메서드? 좀 전까지만 해도, 확장 메서드를 이야기한다면서? 분명, 이 강좌는 확장 메서드를 설명하기 위한 강좌인 것으로 아는데!! 라고 생각하셨죠?

저도 물론 기억하고 있습니다. 하지만, 확장 메서드는 헬퍼 메서드와 깊은 연관 관계를 가지고 있기에, 헬퍼 메서드라는 녀석에 대해서 먼저 이야기를 나누는 것이 제 맘대로 그냥 좋아 보입니다.

그렇다면, 헬퍼 메서드는 무엇일까요? 다들 아시는 명칭이겠습니다만, 개인적으로 정의를 내려보자면, 이는 어떤 복잡한 로직을 별도의 메소드로 분리, 작성하여 개발자들이 그 내부는 알 필요 없이 쉽게 사용할 수 있도록 한 메서드라고 말할 수 있을 것 같습니다. 대부분, 기업 프로젝트 중 공통 모듈을 만들거나, 공통 라이브러리를 개발할 경우에 빠지지 않고 요구되곤 하는 것이 헬퍼 클래스, 헬퍼 메서드들인데요(이를 퉁쳐서 공통 메서드라고도 하죠). 이는 주로 프로젝트 중 표준화 팀이나, 공통 개발팀에 속해있는 중급 개발자들이 업무 분석을 거친 뒤, 프로젝트 개발에 공통적으로 필요한 모듈 중 일부를 헬퍼 클래스의 형식으로 개발하곤 합니다.

일반적으로 이러한 모듈로 작성되는 것들은 개발자가 각각 작성하기에는 다소 복잡한 로직이거나, 반복적으로 사용되는 로직들의 집합이곤 하죠. 이러한 로직들을 헬퍼 메서드나 공통 메서드 형태로 분리하여 라이브러리로 제공하게 되면, 개발자들은 단지 그 메서드를 편하게 호출해서 결과값만을 사용할 수 있기에 개발의 생산성이 향상되곤 합니다. 여러분들도 프로젝트를 수행해 본 경험이 있다면 개인적으로든, 프로젝트 상에서의 임무로서든 이러한 류의 헬퍼 메서드를 작성해 본 적이 있을 겁니다.

이해를 돕기 위해서, 가벼운 예를 하나 들어보도록 하겠습니다.

현재 작업하는 프로젝트에서 날짜와 관련해서 다양한 헬퍼 메서드가 필요하다는 요청이 접수되었습니다. 다양한 요청 중 하나는 지정된 날짜에서 몇 일을 뺀 날짜, 즉, 특정날짜의 이전 일을 구해오는 메서드를 헬퍼로 제공해달라는 것입니다. 메서드의 시그너처는 다음과 같았으면 하고요(이런 형태가 가장 일반적인 형태이죠).

public static DateTime MinusDays(DateTime dt, int days)

사실, 이미 DateTime 클래스에는 AddDays라는 이름의 메서드가 존재하고 있기에, 실제로 이러한 역할의 헬퍼 메서드는 필요치 않을 것입니다만(AddDays 메서드에 마이너스 일자를 인자로 주면 되기에), 예제로 해보는 것이니 이해하시기 바랍니다.

대부분의 헬퍼 메서드들은 정적(static)으로 구성되곤 합니다. 매우 자주 호출되는 메서드들일 것이기에, 번거롭게 인스턴스를 생성하고 메서드를 호출할 필요없이, 빠르고 쉽게 호출할 수 있도록 대부분 정적으로 만들곤 하죠. 여러분이라면 좀 전에 설명 드린 날짜관련 메서드를 어떻게 만드시겠습니까? 아마도 명칭은 조금씩 다를 수 있겠지만, 대부분 다음과 같이 만들 것 같네요.

    public static class DateTimeHelper

    {

        public static DateTime MinusDays(DateTime dt, int days)

        {

            DateTime d = dt.AddDays(-days);

            return d;

        }

    }

그렇겠죠?

다음은 이렇게 작성된 메서드를 사용하는 예를 보여주고 있습니다.

    DateTime current = DateTime.Now;

    DateTime dt1 = DateTimeHelper.MinusDays(current, 3);

    Console.WriteLine(dt1.ToString());

매우 일반적인 패턴이죠? 이미 상당히들 익숙해져 있는 코딩일 듯 합니다. 이런 것을 헬퍼 메서드라고 하죠. 클래스의 이름에 Helper라는 글자는 넣지 않아도 상관은 없습니다. 명칭이 중요한 것이 아니라, 기능이 중요한 것이니까요.

그런데, 이러한 류의 헬퍼 메서드들을 사용하다 보면 2가지 정도가 조금 불편하다는 생각을 갖게 됩니다.

    1. 매번 헬퍼 클래스의 이름을 개발자가 기억해서 코딩해야 한다.
    2. 첫 번째 인자로 항상 대상이 되는 개체를 넘겨줘야 한다.

그래서, 헬퍼 메서드를 사용하다 보면 생기는 이러한 불편함을 없애보자는 생각을 해 낸 사람들이 있었으며, 그 사람들은 위의 헬퍼 메서드를 반드시(?) 다음과 같이 사용할 수 있었으면 좋겠다는 생각을 했습니다.

    DateTime dt2 = current.MinusDaysEx(3);

    Console.WriteLine(dt2.ToString());

즉, 메서드를 대상 개체의 확장 메서드로서 위와 같이 사용할 수 있다면, 헬퍼 클래스의 이름 따위를 기억하지 않아도 될 뿐만 아니라, 대상 개체 형식도 매번 첫 번째 인자로 넘길 필요가 없다는 생각을 한 것이죠. 결과적으로, 메서드의 호출이 너무나도 직관적일 수 있게 되는 것이고요. 아마도, 그들은 저러한 기능을 지원하게 만든 뒤, 즐거움에 맥주를 한 잔 하지 않았을까 합니다. 물론, 팀장님이 쏘셨겠죠?

그리하여, 실제로 C# 3.0에서는 저렇게 메서드를 사용할 수 있도록 돕는 기능을 탑재했으며, 그것을 우리는 확장 메서드 기능이라고 부릅니다. 멋지지 않나요?

그렇다면, 확장 메서드를 만들려면 어떻게 하면 될까요?

매우 간단합니다. 기존 헬퍼 메서드의 첫 번째 인자(대상 개체 인자)의 앞에 this라는 키워드만 붙이면 끝입니다. 그러면, 그 this를 붙인 형식의 확장 메서드로서 헬퍼 메서드가 인식되니까요. 다음은 기존 DateTimeHelper 클래스에 확장 메서드를 추가한 예입니다(명칭의 충돌을 막기 위해서, 예제의 경우는 확장 메서드명 뒤에 Ex를 붙였습니다).

    public static class DateTimeHelper

    {

        public static DateTime MinusDays(DateTime dt, int days)

        {

            DateTime d = dt.AddDays(-days);

            return d;

        }

 

        public static DateTime MinusDaysEx(this DateTime dt, int days)

        {

            DateTime d = dt.AddDays(-days);

            return d;

        }

    }

두 개의 메서드는 메서드 명과 this라는 키워드를 제외하면 완전하게 동일합니다만, 두 번째 추가된 메서드인 확장 메서드는 앞서 말씀드린 바와 같이 기존에 존재하는 형식(DateTime)의 메서드인 것처럼 확장되어 동작하게 됩니다.

    DateTime current = DateTime.Now;

    DateTime dt1 = DateTimeHelper.MinusDays(current, 3);    // 헬퍼 메서드

    DateTime dt2 = current.MinusDaysEx(3);                  // 확장 메서드

그렇기에, 사실상 확장 메서드의 경우는 헬퍼 메서드와는 달리 자신이 속해있는 클래스의 명칭은 그다지 중요하지 않습니다. 헬퍼 메서드의 경우야 그 메서드를 호출하기 위해서는 클래스명부터 다 코딩을 해줘야 하지만, 확장 메서드는 헬퍼 메서드와는 달리 메서드 호출을 위해 클래스 명이 필요하지는 않으니까요. 확장 메서드에서의 클래스 이름은 단지 논리적으로 확장 메서드들을 구분하고 관리하기 위한 용도에 불과한 것이죠.

다만, 확장 메서드를 담는 클래스는 반드시 정적(static) 클래스여야만 합니다. 일반적인 헬퍼 메서드는 반드시 클래스가 정적으로 선언되지 않아도 무방합니다만, 확장 메서드의 경우는 이것이 강제 사항입니다. 반드시 클래스는 static 이어야 한다는 점을 기억하시기 바랍니다.

여하튼, 보고나니, 확장 메서드란 녀석 참으로 편해 보이는군요.

그런데, 기존 클래스가 가지지 않은 이름의 메서드를 확장한다면 모르겠는데, 이놈이 기존 클래스의 메서드와 동일한 이름으로 확장한다면?? 대표적인 메서드로는 ToString()이 있는데요. 만일, 개발자가 아무렇게나 마구 확장해서(사실은 이런 일이 생기는 것 자체가 말이 안됩니다만), 기존 형식의 메서드를 뒤집어 쓴다거나 해서 문제가 생길 일은 없을까요?

네. 문제는 없습니다. 왜냐하면, 어떤 개체 형식이 가지고 있는 메서드와 동일한 이름(정확히 말하자면, 동일한 시그너처)으로 확장 메서드를 만드는 경우, 그 확장 메서드는 무시되니까요. 다음 강좌에서 다시 정리할 것이긴 합니다만, 클래스의 인스턴스 메서드가 존재하는 경우라면 인스턴스 메서드가 가장 우선이 됩니다. 확장 메서드는 기존 인스턴스 메서드를 대체할 수 없다는 것이죠.

그리고, 메서드의 확장은 표준화 팀이나 공통 팀에서 주관하고 관리해서 개발해야 할 것입니다. 아무나 막 만들어서 쓸 수 있게 한다는 것 자체가 프로젝트를 산으로 보내겠다는 것이니까요.

자. 다음은 제가 이번 예제를 위해 작성한 전체 소스입니다(빠른 테스트를 위해서 콘솔 프로그램으로 만들었습니다).

using System;

 

namespace ExtensionSample

{

    public static class DateTimeHelper

    {

        public static DateTime MinusDays(DateTime dt, int days)

        {

            DateTime d = dt.AddDays(-days);

            return d;

        }

 

        public static DateTime MinusDaysEx(this DateTime dt, int days)

        {

            DateTime d = dt.AddDays(-days);

            return d;

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            DateTime current = DateTime.Now;

            DateTime dt1 = DateTimeHelper.MinusDays(current, 3);

            Console.WriteLine(dt1.ToString());

 

            DateTime dt2 = current.MinusDaysEx(3);

            Console.WriteLine(dt2.ToString());

            Console.ReadKey();

        }

    }

}

확장 메서드를 작성한 다음에 밑으로 그를 사용하는 코드를 작성하게 되면, VS 2008을 이용할 경우 다음과 같이 인텔리센스 기능을 지원하는 것을 볼 수 있습니다. 즉, 개체 변수 명에 .(점)을 찍으면 메서도 목록이 주루룩 나오는데요. 이때 확장 메서드도 나타난다는 것이죠. 게다가, 확장 메서드를 의미하는 아이콘은 일반 메서드 아이콘과 약간의 차이를 두어서(아이콘 우측 하단에 화살표가 추가되어 있죠?) 아이콘만으로도 식별이 가능하게 하고 있습니다(더불어, 옆으로 보이는 설명에도 (extension)이라는 명칭이 붙어있는 것도 볼 수 있습니다).

이제 여러분은 확장 메서드가 기존에 주로 사용했던 정적 헬퍼 메서드를 좀 더 편하게 사용하기 위한 목적으로 등장했다는 것을 알게 되었으며, 확장 메서드를 작성하는 방법도 알게 되었습니다.

사실, 확장 메서드의 등장은 LINQ라는 신기술에 이러한 확장 기능이 필요해서 등장하게 된 것이 더 큰 이유인데요. 속을 들여다보면, 결국은 헬퍼 메서드 방식을 사용해서는 LINQ 구문을 만족시킬 수 없기 때문이라고 볼 수 있습니다.

다음 강좌에서는 이렇게 작성된 확장 메서드를 IL 역어셈블러를 통해서 그 내부를 들여다보는 시간을 갖은 뒤, 막상 벗겨보고 나니 확장 메서드라는 것이 헬퍼 메서드와 동일한 것이라는 사실까지 확인해 보도록 하겠습니다. 그리고, 확장 메서드를 널리 사용하라는 권장의 말씀으로 강좌를 맺도록 하겠습니다(여기까지 다 말해줬으니 강좌를 안 써도 다들 읽은 것이나 진배없이 생각할 것 같다는 느낌이… 드네요).