본문으로 바로가기

제네릭(Generic)과 제약조건

category Development/C# 2012. 12. 28. 17:20

제네릭(Generic)
타입 인수를 사용하여 일반화된 클래스나 메서드를 정의하는 기법
이다. 취급하는 타입만 다른 클래스나 메서드가 여러 개 필요할 때 제네릭으로 한번만 정의해 두면 비슷한 클래스나 메서드를 얼마든지 생성할 수 있다.

제네릭의 장단점
코드의 재사용을 높이고 타입 안전성을 극대화하는 장점이 있는 반면 코드의 크기가 커지고 가독성이 떨어지는 불리함이 있다. 

예) 아래 예는 두 개의 클래스가 몇 부분만 다르고 내부 코드는 모두 동일하다. 취급하는 타입만 다를 뿐인데 둘을 각각의 클래스로 정의하는 것은 굉장히 비효율적이다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            WrapperInt gi = new WrapperInt(1234);
            gi.OutValue();
            WrapperString gs = new WrapperString("문자열");
            gs.OutValue();
        }
    }

    class WrapperInt
    {
        int Value;
        public WrapperInt() { Value = 0; }
        public WrapperInt(int aValue) { Value = aValue; } 

        //Data 프로퍼티        
        public int Data
        {
            get { return Value; }
            set { Value = value; }
        }

        public void OutValue()
        {
            Console.WriteLine(Value);
        }
    }

    class WrapperString
    {
        string Value;
        public WrapperString() { Value = null; }
        public WrapperString(string aValue) { Value = aValue; } 

        // Data 프로퍼티         
        public string Data
        {
            get { return Value; }
            set { Value = value; }
        }
        public void OutValue()
        {
            Console.WriteLine(Value);
        }
    }
}
예) 제네릭을 사용하면 위의 예를 아래와 같이 바꿀 수 있다.
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Collections; 
using System.Text.RegularExpressions;   

namespace ConsoleApplication1 
{    
    class Program     
    {        
        static void Main()         
        {             
            Wrapper gi = new Wrapper(1234);             
            gi.Outvalue();               
            Wrapper gs = new Wrapper("문자열");             
            gs.Outvalue();         
        }     
    }       
    
    class Wrapper     
    {         
        T Value;             
      
        // default 키워드는 인수가 참조형식인지 값형식인지에 따라         
        // 반환하는 형태(null 또는 0)을 결정해 준다.         
        public Wrapper() { Value = default(T); }         
        public Wrapper(T aValue) { Value = aValue; } 
          
        public T Data         
        {             
            get { return Value; }            
            set { Value = value; }         
        }           
        public void Outvalue()         
        {             
            Console.WriteLine(Value);         
        }     
    }           
} 

제약 조건
제네릭의 타입 인수 T는 별다른 지정이 없으면 임의의 모든 타입을 적용할 수 있다. 이렇게 되면 T는 일단 object이므로 object의 메서드만 사용할 수 있는 제약이 생기는 셈이다. 타입 인수에 약간의 제약 조건을 지정하면 가능한 T의 종류가 제한되며 따라서 제한된 범위에서 메서드를 호출할 수 있는 효과가 있다.

 제약 조건

 설명

 where T: struct   형식 인수가 값 형식이어야 합니다. Nullable 를 제외한 임의의 값 형식을 지정할 수 있습니다. 자세한 내용은 Nullable 형식 사용(C# 프로그래밍 가이드)를 참조하십시오.
 where T : class   형식 인수가 참조 형식이어야 합니다. 이는 모든 클래스, 인터페이스, 대리자 또는 배열 형식에도 적용됩니다. 
 where T : new()   형식 인수가 매개 변수 없는 공용 생성자여야 합니다. 다른 제약 조건과 함께 사용하는 경우 new() 제약 조건은 마지막에 지정해야 합니다.
 where T : <기본 클래스 이름>   형식 인수가 지정된 기본 클래스이거나 지정된 기본 클래스에서 파생되어야 합니다. 
 where T : <인터페이스 이름>   형식 인수가 지정된 인터페이스이거나 지정된 인터페이스를 구현해야 합니다. 여러 인터페이스 제약 조건을 지정할 수 있습니다. 제한하는 인터페이스는 제네릭이 될 수도 있습니다.
 where T : U   T에 대해 지정한 형식 인수가 U에 대해 지정한 인수이거나 이 인수에서 파생되어야 합니다. 

예) T가 Human의 후손이라는 제약 조건이 있고 따라서 man은 Intro 메서드를 가진다는 것을 알 수 있다.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Collections; 
using System.Text.RegularExpressions;   

namespace ConsoleApplication1 
{     
    class Program     
    {         
        // 제약조건을 사용할 경우         
        public static void OutValue(T man) where T : Human         
        {             
            man.Intro();         
        }           
        
        static void Main()         
        {             
            Human A = new Human();             
            Student B = new Student();             
            String C = "나 문자열";               
            OutValue(A);             
            OutValue(B);             
            //OutValue(C);         
        }     
    }       
    
    class Human     
    {         
        public virtual void Intro() { Console.WriteLine("나 사람"); }     
    }       
    
    class Student : Human     
    {         
        public override void Intro() { Console.WriteLine("나 학생"); }     
    }        
} 
위의 예에서 제약 조건 where T : Human을 빼고 컴파일해보면 Outvalue 메서드 자체가 에러처리된다. 왜냐하면 제약 조건이 없으면 T는 object의 후손인 임의의 타입을 모두 받을 수 있는데 모든 타입에 Intro라는 메서드가 정의되어 있지 않기 때문이다. 제약 조건 없이 이 메서드를 제네릭으로 작성하려면 아래와 같이 해야 한다.
public static void OutValue(T man) 
{     
    Human t = man as Human;     

    if(t != null)     
    {         
    t.Intro();     
    } 
} 

'Development > C#' 카테고리의 다른 글

LINQ Sample  (0) 2013.01.31
using 과 Dispose  (0) 2013.01.04
IPAddress 가져오기  (0) 2012.12.21
List 매서드 재정의  (0) 2012.12.13
최상위 컨트롤  (0) 2012.12.13