728x90
반응형
1. 문제
using System;
class Program {
public static int Max(int a, int b) {
return a < b ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
}
}
위는 인자를 2개 받아 둘 중 더 큰 값을 리턴하는 Max함수를 만들어 사용하는 코드이다.
인자는 int타입만을 받고 있는데 10번째 라인과 같이 string타입도 비교를 하고 싶다고 해보자.
string타입은 비교연산자를 사용할 순 없지만 CompareTo메소드를 통해 비교가 가능하다.
CompareTo메소드는 앞이 크면 1, 작다면 -1, 같으면 0이 나오므로 0보다 작은걸로 비교하면 된다.
그럼 아래와 같은 코드가 된다.
using System;
class Program {
public static int Max(int a, int b) {
return a < b ? b : a;
}
public static string Max(string a, string b) {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
Console.WriteLine(Max(1.2, 3.4)); // error
}
}
이제 문제 없이 string타입도 비교가 가능하지만 또 다른 문제가 있다.
15번째 라인과 같이 double타입은 비교가 불가능하다.
메소드를 묶어서 템플릿으로 만들기에는 안의 내용이 다르다.
일단 안의 내용을 같게 만들어보자.
string은 비교연산자를 쓸 수 없지만 int타입은 비교연산자와 CompareTo를 모두 사용가능하다.
따라서 아래와 같은 코드로 바꿀 수 있다.
using System;
class Program {
public static int Max(int a, int b) {
// return a < b ? b : a;
return a.CompareTo(b) < 0 ? b : a;
}
public static string Max(string a, string b) {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
Console.WriteLine(Max(1.2, 3.4)); // error
}
}
이제 안의 내용이 같아졌으니 하나로 합칠 수 있게 되었다.
2. 해결
using System;
class Program {
public static int Max(int a, int b) {
return a.CompareTo(b) < 0 ? b : a;
}
public static string Max(string a, string b) {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
}
}
위에서 맞춘 코드로 2개로 나뉘어져 있는 Max함수를 하나로 합쳐보자.
using System;
class Program {
// 1. object
public static object Max(object a, object b) {
IComparable c1 = a as IComparable;
IComparable c2 = b as IComparable;
return c1.CompareTo(c2) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
int n = (int)Max(10, 20);
}
}
첫 번째로 object로 바꾸어 묶어 보았다.
C#의 모든 타입들은 object로 부터 파생되므로 인자로 무엇이 와도 받을 수 있다.
하지만 object에는 CompareTo메소드가 없기에 에러가 난다.
이것을 사용하기 위해서는 CompareTo를 제공하는 IComparable 인터페이스로 캐스팅을 해야한다.
물론 안전한 코드로 짜려면 null 값인지 조사해야하는데, 일단 넘겼다.
또 object로 사용하면 반환 시 캐스팅이 필요하다.
16번째 라인과 같이 Max의 반환값이 object이므로 int로 캐스팅을 해줘야 한다.
또 결정적인 문제로
10, 20과 같은 value타입이 object로 인자로 전달되는데
object는 레퍼런스 타입이므로 박싱현상이 일어난다.
따라서 object로 만들수는 있지만 성능상의, 사용상의 문제가 있다.
① Object 사용
=> CompareTo를 호출하려면 IComparable 인터페이스 타입으로 캐스팅 후 사용
=> 반환 값을 받을 때 캐스팅 필요
=> Value type 인 경우 Boxing / UnBoxing 발생
using System;
class Program {
// 2. IComparable
public static object Max(IComparable a, IComparable b) {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
int n = (int)Max(10, 20);
}
}
두 번째는 IComparable 인터페이스를 사용한 경우이다.
인자로 아예 인터페이스 타입으로 받게되면 IComparable을 구현한 모든 타입이 들어갈 수 있는데
int도 구현되어 있고, string도 구현되어 있으니 문제 없다.
첫 번째 방법과 비교해 캐스팅 부분이 사라져 조금 깔끔해졌다.
하지만 문제는 여전히 있다.
C#에서 모든 인터페이스는 reference type이다.
5번째 라인에 있는 a와 b는 stack에 있는 참조 변수이고
사용할 때 넘기는 값은 넘어갈 때 Heap에 복사본을 만들고 a와 b가 그걸 가리키게 된다.
즉, Boxing 현상이 발생한다.
또, 리턴값을 받을 때 캐스팅이 여전히 필요하다.
② IComparable 인터페이스 사용
=> Value type 인 경우 Boxing / UnBoxing 발생
=> 반환 값을 받을 때 캐스팅 필요
using System;
class Program {
// 3. Generic
public static T Max<T>(T a, T b) {
// 기본적으로 object로 할 수 있는 연산만 가능
// a.Equals(b); // ok - ojbect 멤버
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
int n = Max(10, 20);
}
}
마지막으로 Generic을 이용한 경우이다.
이렇게 되면 int타입 string타입 두 개가 생기게 되지만
int를 int로 받고 string을 string으로 받으므로 Boxing없이 받을 수 있다.
또 반환 값 또한 캐스팅이 필요 없다.
하지만 문제는 있다.
CompareTo 메소드를 사용할 수 없다.
왜냐하면 컴파일러는 현재 T를 임의의 타입으로 생각하고 있는데
그 임의의 타입에 CompareTo가 있다고 확신할 수 없기 때문에 에러가 난다.
반대로 Equals는 문제가 없는데 이건 object의 멤버이고, 모든 타입은 object로 파생되기 때문이다.
그래서 제너릭을 사용하면 기본적으로 object로 할 수 있는 연산만 가능하다.
하지만 우리가 쓰고 싶은건 CompareTo 메소드이다.
이럴때는 제너릭 제약이란 것을 쓰면 된다.
using System;
class Program {
// 3. Generic
public static T Max<T>(T a, T b) where T : IComparable {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20));
Console.WriteLine(Max("A", "B"));
int n = Max(10, 20);
}
}
뒤에 where T : IComparable 이라고만 적으면 된다.
의미는 Max는 임의의타입 T를 받을 건데 그 T는 IComparable 인터페이스를 구현해야 한다.
인터페이스가 구현되어 있지 않은 타입일 경우 에러가 난다.
이렇게 표기하는 것을 제너릭 제약이라고 부르고
이 Max는 임의의타입을 받는데 IComparable을 구현한 규칙을 지킨 타입이어야 한다.
C++20에서 concept과 같다.
③ Generic 사용
=> Boxing / Unboxing 현상이 없다.
=> Generic Constraint 를 표기해야 한다.
① Generic Constraint
using System;
class Program {
// ,로 2개를 적을 수 있다. class, struct는 앞에 와야 한다.
public static T Max<T>(T a, T b) where T : class, IComparable {
return a.CompareTo(b) < 0 ? b : a;
}
static void Main() {
Console.WriteLine(Max(10, 20)); // error
Console.WriteLine(Max("A", "B")); // ok
int n = Max(10, 20);
}
}
728x90
반응형
'프로그래밍 > C#' 카테고리의 다른 글
[C#] Delegate (2) (0) | 2020.04.04 |
---|---|
[C#] Delegate (1) (0) | 2020.03.30 |
[C#] 제너릭 (Generic) (0) | 2020.03.23 |
[C#] 인덱서 (Indexer) (0) | 2020.03.19 |
[C#] 속성 (Property) (2) (0) | 2020.03.13 |