Programming/C#

C# 2.0 변경점(1) - 제네릭스(Generics), ?? 연산자

lee308812 2019. 3. 31. 15:42

[ 제네릭스(Generics) ]

 

- 기존 C# 1.0에서는 기본 형식으로 컬렉션 객체를 사용하는 경우 박싱/언박싱 문제가 발생한다. 즉, 값형식들을 참조형식인 object로 바꾸는 과정에서 힙에 object instance를 할당하게 되어 성능 저하 문제가 발생한다.

 

- 이를 보완하기 위해, 제네릭스를 도입하였다.

using System;

namespace ConsoleApp1
{
    public class GenericsSample1<T>
    {
        T item;

        public GenericsSample1(T value)
        {
            item = value;
        }
    }

    // 형식 매개 변수의 이름은 아무렇게나 지정가능
    public class GenericsSample2<K>
    {
        K item;

        public GenericsSample2(K value)
        {
            item = value;
        }
    }

    // 2개 이상도 가능
    public class TwoGeneric<K, V>
    {
        K _key;
        V _value;

        public void Set(K key, V value)
        {
            _key = key; _value = value;
        }
    }
}

 

- 아래와 같이 메서드 단위로도 지정 가능하다. Max함수에서 CompareTo 메서드를 사용하는데, 모든 T로 대체될 타입이 모두 CompareTo 메서드를 지원하지 않으므로 컴파일 단계에서 오류가 발생하는 것을 막기 위해 where 키워드로 제약 조건을 도입하였다.

 

- where T: struct = T형식 매개변수는 반드시 값 형식만 가능

- where T: class = T형식 매개변수는 반드시 참조 형식만 가능

- where T: new() = T형식 매개변수의 타입에는 반드시 기본생성자가 정의되어 있어야함.

- where T: U = T형식 매개변수는 반드시 "U형식 인수"에 해당하는 타입이거나 그것으로부터 상속받은 클래스만 가능

using System;

namespace ConsoleApp
{
    public class Utility
    {
        public static void WriteLog<T>(T item)
        {
            string output = string.Format("{0} : {1}", DateTime.Now, item);
            Console.WriteLine(output);
        }

        public static T Max<T>(T item1, T item2) where T : IComparable
        {
            if(item1.CompareTo(item2) >= 0)
            {
                return item1;
            }

            return item2;
        }
        
        public static void CheckNull<T>(T item) where T : class
        {
            if (item == null) throw new ArgumentNullException();
        }
        
        public static T Allocate<T, V>() where V : T, new()
        {
            // Derived class를 new로 할당하여 BaseClass로 형변환해서 반환
            return new V();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Utility.Max(5, 6));
        }
    }
}

- 마이크로소프트에서는 기존 컬렉션 타입의 박싱/언박싱 문제를 해결하는 타입을 .NET Framework 2.0의 BCL의 System.Collections.Generics 네임스페이스에 추가하였다.

.NET 1.0 컬렉션 대응되는 제네릭스 버전의 컬렉션
ArrayList List<T>
Hashtable Dictionary<TKey, TValue>
SortedList SortedDictionary<TKey, TValue>
Stack Stack<T>
Queue Queue<T>

- 인터페이스도 박싱/언박싱 문제가 발생하는 경우 새롭게 제네릭 버전이 제공된다.

IComparable<T>, IComparer<T>, ...

 

- 변수를 초기화하지 않은 경우 값 형식은 0, 참조 형식은 null로 초기화된다. 그러나 제네릭의 형식 매개변수로 전달된 경우에는 코드에서 미리 타입을 알 수 없기 때문에 그에 대응되는 초기값도 알 수 없다. 컴파일러가 이를 결정할 수 있도록 default 키워드를 사용할 수 있다.

using System;

namespace ConsoleApp1
{
    class ArrayNoException<T>
    {
        int _size;
        T[] _items;

        public ArrayNoException(int size)
        {
            _size = size;
            _items = new T[size];
        }

        public T this[int index]
        {
            get
            {
                if(index >= _size)
                {
                    return default(T);
                }

                return _items[index];
            }

            set
            {
                if (index >= _size) return;

                _items[index] = value;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ArrayNoException<int> list = new ArrayNoException<int>(10);

            list[10] = 5; // 일반적인 배열이라면 예외 발생
            Console.WriteLine(list[10]);
        }
    }
}

 

 

 

[ ?? 연산자(null 병합 연산자) ]

 

- ?? 연산자는 null 값을 가진 참조형 변수를 손쉽게 처리할 수 있는 연산자다. 

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            string txt = null;

            if (txt == null) Console.WriteLine("(null)");
            else Console.WriteLine(txt);

            // 아래와 같이 간단하게 바꿔 쓸 수 있다.
            Console.WriteLine(txt ?? "(null)");

        }
    }
}