Welcome to dynamic C# 외전(2) - Generic Method.

C# 2009. 11. 30. 08:30 Posted by 알 수 없는 사용자

- 또 외전이군 ㅋ

오랜만의 포스팅이네요. 넵. 또 외전입니다-_-;;; 최근에 다른 일때문에 포스팅을 못하다가 어떤 계기가 생겨서 포스팅을 하게됐습니다. 음;; 제네릭 메서드를 사용해서 코딩할 일이 있었는데요, 갑자기 전에 어떻게 했는지 기억이 안나는거 있죠-_-. 그래서, 기억을 더듬고 검색해서 코딩을 했습니다. 그래서 저도 안까먹고, 혹시 이거때매 해매는 분들을 위해서 포스팅을 할까 합니다.

* 경고 : 늘 그렇지만 제가 하는 포스팅은 허접합니다. 왜냐면, 제가 허접하기 때문이죠. 포스팅의 허접함에 눈이 아프시다면, "Oh My Eyes!"를 한번 외치시고 뒤로가기 버튼을 활용하시면 직빵입니다. ㅋ OME는 스타에서만 나오는게 아니거등요.


- 제네릭?

넵. 제네릭은 불필요한 박싱/언박싱을 없애도록 도와준 고마운 기능입니다. C# 2.0에서 추가가 됐었죠. 내부적으로는 F#에서 유용하다고 검증이 돼서, C#에 추가하기로 결정을 했었다고 합니다. F#이 쫌 까칠하긴 해도, 파워풀 하거든요^^(까칠하다보니, F#이랑 쫌 잘지내보려고 데이트도 11번 정도 했지만 일단은 손 놨습니다. 상처받은 마음을 C#과 좀 달래려구요. 근데 요즘은 C#한테도 상처를....)

혹시 모르는 분들을 위해서 허접한 실력으로 부연설명을 드리자면요. 박싱(boxing)은 포장을 하는 거구요, 언박싱(unboxing)은 포장을 푸는겁니다. 즉, C#에는 크게 타입이 크게 두가지로 분류가 되는데요. 값형식(value type)과 참조형식(reference type)입니다. 값형식은 변수의 이름이 가리키는 곳에는 실제 값이 들어있고, 참조형식은 변수의 이름이 가리키는 곳에 실제값에 대한 참조가 있습니다. 즉, C에서의 포인터의 개념을 생각하시면 됩니다. 포인터는 실제값을 가지는게 아니라, 값이 있는 곳의 주소를 가지고 있는거니까요.

근데, 값형 타입이 닷넷의 최상위 타입인 object같은 참조형 타입과 같이 처리해야 되는 경우가 있습니다. 예를 들면, C# 1.0에서는 컬렉션이 object형만 받을 수 있었습니다. 그래서, int타입만 처리하는 경우에도 컬렉션을 사용하려면, object타입으로 int타입을 포장해서 집어넣고, 빼낼때도 object타입의 포장을 풀고 int타입을 꺼내와야 했던거죠. 그런데, 이게 너무 삽질이다 보니 더 편하게 할 수 있는 방법으로 제네릭이 등장 했습니다.

제네릭은 어떤 타입도 될 수 있는 걸 말하는데요, List<int>는 int타입만 받는 컬렉션, List<string>은 string타입만 받는 컬렉션이 되면서, 불필요한 박싱/언박싱도 없어지고 코드도 훨씬 명확해 지는 장점이 있습니다. 그런데, 꼭 이럴때 뿐만 아니라도 제네릭이 필요한 경우가 생기는데요. 오늘 제가 설명드리면서 보여드릴 예제가 그런 경우입니다.


- 그래그래. 예제 내놔봐.

예제는 XML로 객체를 시리얼라이즈(Serialize)하고 다시 디시리얼라이즈(Deserialize)하는 건데요. 시리얼라이즈을 할때는 시리얼라이징 하는 객체의 타입을 명시해줘야 합니다. 그래서 한 프로그램에서 여러객체를 시리얼라이즈해야 할때는, 그 수만큼 오버로딩이 되기도 합니다. 하지만, 제네릭이 출동한다면?

제.

네.

릭.

...죄송합니다. 스덕이다 보니, 스타와 관련된 드립을 하게 되는군요. 계속해서 말씀드리죠-_-. 어떤 타입이든지 될 수 있는 제네릭을 활용한다면, 메서드를 변경시키지 않고서도 여러타입에 사용가능한 메서드를 만들 수 있습니다. 그럼, 소스를 한번 보시죠.

 class Serializer
{
    internal static bool Serialize<T>(T source, string fileName)
    {
        try
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            Stream stream = new FileStream(fileName, FileMode.Create,
                   FileAccess.Write, FileShare.None);
            serializer.Serialize(stream, source);
            stream.Close();

            return true;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    internal static T Deserialize<T>(string fileName) where T : class, new()
    {
        T target;
        FileInfo _settingFile = new FileInfo(fileName);
        if (!_settingFile.Exists)
        {
            return new T();
        }

        try
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            Stream stream = new FileStream(fileName, FileMode.Open,
                   FileAccess.Read, FileShare.None);
            target = serializer.Deserialize(stream) as T;
            stream.Close();

            return target;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}


위의 소스에서 <T>라고 쓰인부분이 바로 제네릭을 쓰겠다고 하는 겁니다. 즉, 어떤 타입이든지 될 수 있는 T라는 제네릭한 타입에 대해서 메서드를 작성하겠다는 거죠. 그리고 주목하실 부분이, Deserialize메서드 선언부의 끝부분의 "where T : class, new()"입니다. 이건, 제네릭한 타입 파라미터에 제약조건을 주는 겁니다. 즉, 위에 선언된 건 T가 제네릭한 타입이긴한데 class여야만 하고, 파라미터없고 public인 생성자가 있어야 한다는 겁니다. 그런 타입만 T를 통해서 넘어올 수 있다는 거죠. 제약조건은 그 외에도 아래와 같은 것들이 있습니다.

 제약조건  설명
 where T: struct  타입 매개변수는 반드시 Nullable을 제외한 값형(value type)이어야만 한다.
 where T : class  타입 매개변수는 반드시 참조형(reference type)이어야만 한다.
 where T : new()  타입 매개변수는 반드시 public이고 매개변수가 없는 생성자를 갖고 있어야 한다. 그리고 다른 제약 조건이랑 같이 사용될때는 new()가 맨뒤에 와야한다.
 where T : <base class name>  타입 매개변수는 반드시 명시된 클래스를 상속 해야한다.
 where T : <interface name>  타입 매개변수는 반드시 명시된 인터페이스이거나, 명시된 인터페이스를 구현해야 한다.
 where T : U  T에 해당되는 매개변수는 반드시 U에 해당되는 매개변수 타입이거나, U를 상속한 타입이어야 한다. 

네. 뭐 그렇답니다. 프레임워크 개발자나 복잡한 경우가 아니면, 저걸 다 쓸일이 있을지는 모르겠지만 알아두면 좋겠죠. 그리고 위의 코드는 아래와 같이 사용할 수 있습니다. 메서드가 static으로 된건 그냥 편의를 위해서 입니당.

public class Pair
{
    public int Id { get; set; }
    public string Name { get; set; }
}

class Program
{
    static bool SerializePairs(List<Pair> pairs)
    {
        try
        {
            Serializer.Serialize<List<Pair>>(pairs, "Pairs.xml");

            return true;
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
            return false;
        }
    }

    static List<Pair> DeserializePairs()
    {
        try
        {
            return Serializer.Deserialize<List<Pair>>("Pairs.xml");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            return new List<Pair>();
        }
    }

    static void Main(string[] args)
    {
        List<Pair> pairs = new List<Pair>
        {
            new Pair{
                Id=1,
                Name="홍길동"
            },
            new Pair{
                Id=2,
                Name="루저"
            },
            new Pair{
                Id=3,
                Name="위너"
            }
        };

        SerializePairs(pairs);
        List<Pair> pairs2 = DeserializePairs();

        foreach (Pair pair in pairs2)
        {
            Console.WriteLine(
                string.Format("{0} : {1}",
                    pair.Id,
                    pair.Name)
                );
        }
    }
}


Pair라는 객체가 있고, 그 객체에는 홍길동과 루저, 위너 이렇게 3명이 들어갑니다. 그리고 그 객체를 시리얼라이즈하고, 다시 디시리얼라이즈해서 결과를 화면에 출력하고 있죠. 메서드를 호출할때 "Serialize<List<Pair>>(pairs, "Pairs.xml")" 이렇게, <>안에 제네릭한 타입인 T가 실제로 어떤 타입이 될지를 명시해주고 있습니다. 그리고 결과를 보면 아래와 같구요.


넵. 그렇습니다. 별거 없죠-_-;;;


- 여기까지.

넵. 여기까지입니다. 뭐 특별한 내용도 없지만, 그냥 한번 정리하면 저도 편하고 필요하신 분들에게도 편하지 않을까 싶어서 말이죠-_- ㅋ. 소스는 파일로 첨부해드리겠습니다. 필요하신분들은 참고하시구요~. 언제가 될지는 모르겠지만, 다음 포스트에서 뵙져!!!


- 참고자료
1. http://msdn.microsoft.com/en-us/library/d5x73970.aspx