Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 일반화 프로그래밍 : 형식 매개 변수 제약 본문

Programming/C#

[Programming/C#] 일반화 프로그래밍 : 형식 매개 변수 제약

scii 2020. 9. 19. 01:22

일반화 메소드나 일반화 클래스가 입력받는 형식 매개 변수 T는 "모든" 데이터 형식을 대신할 수 있다. 이렇게 모든 형식에 대응할 수 있는 형식 매개 변수가 필요한 때도 있지만, 종종 특정 조건을 갖춘 형식에만 대응하는 형식 매개 변수가 필요할 때도 있다.
이 때 형식 매개 변수의 조건에 제약을 줄 수 있다.

예를 들어 MyList<T> 클래스의 형식 매개 변수 T에 "MyClass 로부터 상속받는 형식이어야 할 것" 이라는 제약을 주면 다음과 같이 클래스 선언문의 헤더에 where 절을 추가해주면 된다.

class MyList<T> where T : MyClass
{
    // ...
}

일반화 메소드도 비슷하다. CopyArray<T>() 의 형식 매개 변수 T에 "값 형식이어야 할 것" 이라는 제약은 다음과 같이 줄 수 있다.

void CopyArray<T>(T[] source, T[] target) where T : struct
{
    for(int i=0; i<source.Length; i++)
    {
        target[i] = source[i];
    }
}

일반화 코드에서 형식을 제약하는 문법은 다음과 같으며, 형식 매개 변수에 대한 일반화 클래스나 일반화 메소드 모두에 동일하게 사용된다.

where 형식매개변수 : 제약조건

제약 조건에는 여러 가지가 올 수 있다. where 절과 함께 사용할 수 있는 제약 조건은 아래와 같다.

제약 조건 설명
where T : struct T는 값 형식이어야 한다.
where T : class T는 참조 형식이어야 한다.
where T : new() T는 반드시 매개 변수가 없는 생성자가 있어야 한다.
where T : 부모_클래스_이름 T는 명시한 부모 클래스의 파생 클래스여야 한다.
where T : 인터페이스_이름 T는 명시한 인터페이스를 반드시 구현해야 한다. 인터페이스 이름에는 여러 개의 인터페이스를 명시할 수도 있다.
where T : U T는 또 다른 형식 매개 변수 U로부터 상속받은 클래스여야 한다.

제약 조건 where T : new()

CreateInstance<T>() 메소드는 매개 변수가 없는 기본 생성자를 가진 어떤 클래스의 객체라도 생성해준다. 이 메소드를 호출할 때 기본 생성자가 없는 클래스를 형식 매개 변수로 넘기면 컴파일 에러가 발생한다.

public static T CreateInstance<T>() where T: new()
{
    return new T();
}

제약 조건 where T : U

CopyArray<T>() 는 소속 클래스인 BaseArray<U> 의 형식 매개 변수 U로부터 T가 상속받아야 할 것을 강제한다.

class BaseArray<U> where U : Base
{
    public U[] Array{ get; set; }
    public BaseArray(int size)
    {
        Array = new U[size];
    }
    
    // T는 U로부터 상속받은 클래스여야 한다.
    public void CopyArray<T>(T[] source) where T : U
    {
        source.CopyTo(Array, 0);
    }
}

일반화 프로그래밍 제약 조건의 예제

using System;

namespace test
{
    interface IInterface1
    {
        void Print1();
    }

    interface IInterface2
    {
        void Print2();
    }

    // T는 값 형식이어야 한다.
    class StructArray<T> where T : struct
    {
        public T[] Array { get; }
        public StructArray(int size) => Array = new T[size];
    }

    // T는 참조 형식이어야 한다.
    class RefArray<T> where T : class
    {
        public T[] Array { get; }
        public RefArray(int size) => Array = new T[size];
    }

    class Base
    {
    }

    class Derived : Base
    {
    }

    // U는 Base클래스 기반의 클래스여야 한다.
    class BaseArray<U> where U : Base
    {
        public U[] Array { get; }
        public BaseArray(int size) => Array = new U[size];

        // T는 U로부터 상속받은 클래스여야 한다.
        public void CopyArray<T>(T[] source) where T : U
        {
            source.CopyTo(Array, 0);
        }
    }

    // T는 명시한 인터페이스를 반드시 구현해야 한다. 라는 의미의 형식 매개 변수 제약 조건
    class FooClass<T> where T : IInterface1, IInterface2, new()
    {
        public FooClass() => Console.WriteLine("InterClass Called!");

        public void PrintFunc()
        {
            T bar = new T();
            bar.Print1();
            bar.Print2();
        }
    }

    // 인터페이스를 상속하는 클래스
    class InterClass : IInterface1, IInterface2
    {
        public void Print1()
        {
            Console.WriteLine("Print1()");
        }

        public void Print2()
        {
            Console.WriteLine("Print2()");
        }
    }

    internal class Program
    {
        // T는 매개 변수가 없는 생성자를 가지고 있어야 한다.
        public static T CreateInstance<T>() where T : new()
        {
            return new T();
        }

        public static void Main(string[] args)
        {
            StructArray<int> a = new StructArray<int>(3);
            a.Array[0] = 0;
            a.Array[1] = 5;
            a.Array[2] = 10;

            RefArray<StructArray<double>> b = new RefArray<StructArray<double>>(3);
            b.Array[0] = new StructArray<double>(5);
            b.Array[1] = new StructArray<double>(8);
            b.Array[2] = new StructArray<double>(9);

            BaseArray<Base> c = new BaseArray<Base>(3);
            c.Array[0] = new Base();
            c.Array[1] = new Derived();
            c.Array[2] = CreateInstance<Base>();

            BaseArray<Derived> d = new BaseArray<Derived>(3);
            // Base 형식은 이곳에 할당 할 수 없다. 왜냐면 Base에서 파생된 클래스를 형식 매개 변수로 넘겨줬기 때문.
            d.Array[0] = new Derived();
            d.Array[1] = CreateInstance<Derived>();
            d.Array[2] = CreateInstance<Derived>();

            BaseArray<Derived> e = new BaseArray<Derived>(3);
            e.CopyArray<Derived>(d.Array);
            
            // 인터페이스
            FooClass<InterClass> fooClass = new FooClass<InterClass>();
            fooClass.PrintFunc();
        }
    }
}


/* 결과

InterClass Called!
Print1()
Print2()

*/
Comments