상세 컨텐츠

본문 제목

[C#] 일반화

.NET/C#

by 클리엘 클리엘 2021. 2. 11. 21:24

본문

728x90

1. 일반화 구현

 

C#에서 일반화는 동일한 처리를 필요로 하는 여러 데이터형식을 통일시켜 코드의 중복을 최소하하는 것을 말합니다. 에를 들어 배열을 복사하는 아래와 같은 메서드가 있다고 가정해 보겠습니다.

public int[] Copy(int[] array1, int[] array2)
{
	for (int i = 0; i < array2.Length; i++)
		array1[i] = array2[i];

	return array1;
}

이 메서드는 int형의 배열을 받아 그대로 복사해 주는데 만약 매개변수로 전달될 배열이 string이나 float등 다른 형식의 매개변수라면 그 만큼 메서드의 수를 늘리는 방법을 생각해 볼 수 있습니다.

public float[] Copy(float[] array1, float[] array2)
{
	for (int i = 0; i < array2.Length; i++)
		array1[i] = array2[i];

	return array1;
}

public string[] Copy(string[] array1, string[] array2)
{
	for (int i = 0; i < array2.Length; i++)
		array1[i] = array2[i];

	return array1;
}

하지만 일반화를 사용하면 매개변수 데이터형식을 다음과 같이 변경하여

public T[] Copy<T>(T[] array1, T[] array2)
{
	for (int i = 0; i < array2.Length; i++)
		array1[i] = array2[i];

	return array1;
}

다양한 데이터형식에 대응하는 처리를 하나로 통일할 수 있습니다. 메서드에 사용된 T는 Type을 의미하며 이 T가 매개변수로 전돨되는 데이터 타입(형식)으로 치환되어 처리되는 것입니다.

static void Main(string[] args)
{
	int[] i = new int[5];
	int[] j = new int[5] { 10, 20, 30, 40, 50};

	MyClass mc = new MyClass();
	mc.Copy(i, j);

	foreach(int item in i) {
		Console.WriteLine($"{item}");
	}
}

//또는

static void Main(string[] args)
{
	string[] i = new string[5];
	string[] j = new string[5] { "10", "20", "30", "40", "50"};

	MyClass mc = new MyClass();
	mc.Copy(i, j);

	foreach(string item in i) {
		Console.WriteLine($"{item}");
	}
}

일반화는 위에서 처럼 메서드가 아닌 클래스에서도 적용이 가능합니다. 우리가 아래글에서 다루었던

 

[.NET/C#] - [C#] 배열및 컬렉션과 인덱서활용 그리고 나열하기

class MyArray : IEnumerator, IEnumerable
{
	int[] array;
	int index = -1;

	public MyArray()
	{
		array = new int[5];
	}

	public int this[int index]
	{
		get {
			return array[index];
		}

		set {
			array[index] = value;
		}
	}

	public bool MoveNext()
	{
		if (index <= (array.Length - 1)) {
			Reset();
			return false;
		}

		++index;
		return true;
	}

	public void Reset()
	{
		index = -1;
	}

	public object Current
	{
		get {
			return array[index];
		}
	}

	public IEnumerator GetEnumerator()
	{
		for (int i = 0; i < array.Length; i++) {
			yield return array[i];
		}
	}
}

MyArray클래스를 다시 살펴보겠습니다. 이 클래스는 인덱서를 통해 개체를 배열처럼 활용하고자 하며 IEnumerator, IEnumerable 2개의 인터페이스를 상속해 배열의 요소를 나열하고 있습니다.

 

에제의 MyArray는 int형식의 배열을 다루고 있는데 위에서 메서드를 일반화한것처럼 클래스도 일반화를 통해 int뿐만 아니라 string이나 float등 다양한 형식의 데이터를 다룰 수 있도록 처리 할 수 있습니다.

class MyArray<T> : IEnumerator, IEnumerable
{
	T[] array;
	int index = -1;

	public MyArray()
	{
		array = new T[5];
	}

	public T this[int index]
	{
		get {
			return array[index];
		}

		set {
			array[index] = value;
		}
	}

	public bool MoveNext()
	{
		if (index <= (array.Length - 1)) {
			Reset();
			return false;
		}

		++index;
		return true;
	}

	public void Reset()
	{
		index = -1;
	}

	public object Current
	{
		get {
			return array[index];
		}
	}

	public IEnumerator GetEnumerator()
	{
		for (int i = 0; i < array.Length; i++) {
			yield return array[i];
		}
	}
}

클래스에서 일반화형식인 <T>를 잡고 내부 배열및 배열과 관련된 데이터형식에서도 T를 지정하였습니다. 그리고 실제 클래스의 인스턴스를 생성할때 어떠한 형식의 데이터를 다룰것인지 다음과 같은 방법으로 알려주기만 하면 됩니다.

static void Main(string[] args)
{
	MyArray<string>	ma = new MyArray<string>();
	ma[0] = "abc";
	ma[1] = "def";
	ma[2] = "ghi";
	ma[3] = "jil";
	ma[4] = "mno";

	foreach(string i in ma) {
		Console.WriteLine($"{i}");
	}
}

여기까지만 하면 동작하는데는 이상이 없으나 데이터의 형식에 따라 foreach의 성능문제가 나올 수 있습니다. 요소에 접근해 데이터를 끄집어 올때마다 형변환을 수행하는 절차를 거쳐야 하기 때문입니다. 이 문제점을 해결하려면 foreach를 통해 배열의 나열을 가능하게 하는 IEnumerator, IEnumerable 2개의 인터페이스를 일반화가 가능한 IEnumerator<T>, IEnumerable<T> 인터페이스로 바꾸고 일반화 형식의 메서드를 새롭게 구현하면 됩니다.

class MyArray<T> : IEnumerator<T>, IEnumerable<T>
{
	T[] array;
	int index = -1;

	public MyArray()
	{
		array = new T[5];
	}

	public T this[int index]
	{
		get {
			return array[index];
		}

		set {
			array[index] = value;
		}
	}

	public bool MoveNext()
	{
		if (index <= (array.Length - 1)) {
			Reset();
			return false;
		}

		++index;
		return true;
	}

	public void Reset()
	{
		index = -1;
	}

	public T Current
	{
		get {
			return array[index];
		}
	}

	object IEnumerator.Current
	{
		get {
			return array[index];
		}
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		for (int i = 0; i < array.Length; i++) {
			yield return array[i];
		}
	}

	public IEnumerator<T> GetEnumerator()
	{
		for (int i = 0; i < array.Length; i++) {
			yield return array[i];
		}
	}

	public void Dispose()
	{
		
	}
}

IEnumerator<T>, IEnumerable<T> 인터페이스를 상속하면 Current 속성 2개, GetEnumerator() 메서드를 2개씩 구현해야 합니다. 하나는 기존 그대로고 다른 하나는 일반화가 적용된 버전입니다. 또한 Dispose() 메서드를 추가로 구현해야 하는데 딱히 해당 메서드에서 필요한 구현이 없으면 메서드의 실체구현을 생략하면 됩니다.


2. where

 

일반화에서 where은 일반화 메서드나 클래스에 대해 사용가능한 형식을 지정하고자 하는 경우 사용할 수 있습니다. 에를 들어 T가 MyClass로 부터 상속받은 파생클래스여야 한다는 제약이 있다면 일반화 클래스에 다음과 같이 where T : 구문을 사용하여 상속받아야 하는 클래스를 지정해 줍니다.

class MyArray<T> : IEnumerator, IEnumerable where T : MyClass

메서드도 동일한데 일반화메서드에서 값형식의 데이터만 취급되어야 한다면 아래와 같이 지정할 수 있습니다.

public T[] Copy<T>(T[] array1, T[] array2) where T : struct

참고로 where를 통해서 제한할 수 있는 방식은 아래와 같은 것들이 있습니다.

where T : struct T는 값형식이어야 함
where T : class T는 참조형식이어야 함
where T : new() T는 매개변수없는 생성자가 있어야 함
where T : 기반클래스 T는 기반클래스에서 파생된 클래스어야 함
where T : 인터페이스 T는 인터페이스를 구현해야 함(콤마(,)를 통해 여러 인터페이스를 지정할 수 있음)
where T : U T는 형식매개변수 U에서 상속받아야 함

wher T : U는 다소 생소한데 일반화 메서드에서 T가 자신이 속한 클래스의 U로 지정된 기반클래스나 혹은 기반클래스를 상속받은 클래스로 대상을 한정하는 것입니다.

class MyBase
{

}

class MyClass<U> where U : MyBase
{
	public T[] Copy<T>(T[] array1, T[] array2) where T : U
	{
		for (int i = 0; i < array2.Length; i++)
			array1[i] = array2[i];

		return array1;
	}
}
static void Main(string[] args)
{
	MyBase[] a = new MyBase[1];
	MyBase[] b = new MyBase[1];
	b[0] = new MyBase();

	MyClass<MyBase> m = new MyClass<MyBase>();
	m.Copy<MyBase>(a, b);
}

3. 일반화 컬렉션

 

컬렉션에 담을 수 있는 데이터 형식은 object이므로 어떤 데이터형이든 컬렉션에 담을 수 있었습니다. 무엇이든 가능하다는 점은 좋은데 object형식이다보니 실제 데이터로 접근할때는 본래 데이터의 형식으로 매변 형변환을 수행해야 합니다. 이런 동작은 형변환의 처리에 따른 성능문제를 유발합니다.

 

그래서 컬렉션을 일반화하면 미리 형식을 지정하기 때문에 위에서 언급한 문제를 해소할 수 있습니다.

List<int> l = new List<int>();
l.Add(10);
l.Add(20);
l.Add(30);

l.RemoveAt(2);

l.Insert(2, 40);

먼저 List는 ArrayList의 일반화 버전입니다. 형식지정이외에 동일하게 사용됩니다.

Queue<int> q = new Queue<int>();
q.Enqueue(10);
q.Enqueue(10);
q.Enqueue(10);

q.Dequeue();

Stack<int> s = new Stack<int>();
s.Push(10);
s.Push(20);
s.Push(30);

s.Pop();

Queue와 Stack은 기존 컬렉션과 이름이 같고 일반화를 위한 <T>만 추가됩니다.

Dictionary<int, string> d = new Dictionary<int, string>();
d[0] = "aaa";
d[1] = "bbb";
d[2] = "ccc";

Console.WriteLine($"{d[0]}");

Dictionary는 Hashtable과 대응됩니다.

728x90

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

[C#] 예외처리  (0) 2021.02.25
[C#] 일반화  (0) 2021.02.11
[C#] 배열및 컬렉션과 인덱서활용 그리고 나열하기  (0) 2021.02.08
[C#] 프로퍼티  (0) 2021.02.01
[C#] 인터페이스와 추상클래스  (0) 2021.01.28
[C#] 클래스  (0) 2021.01.26

태그

관련글 더보기

댓글 영역