개요
문자열을 숫자로 바꾸거나 그 반대를 행하는 경우 형 변환이 필요합니다.
일반적으로 형변환은 클래스형에 대한 변환을 의미하지는 않습니다. 클래스의 경우 보통 캐스팅이라고 하여 객체지향 프로그래밍의 근간인 대체가능성에 기반한 변환을 의미하게 됩니다.
IConvertible
CLR공용타입들(char, bool, byte, int 등)은 별도의 변환 방법을 제공해야 합니다. 이를 위한 내장 인터페이스가 System.IConvertible 입니다.
이 인터페이스에서는 ToChar, ToByte 등 CLR공용타입 전체에 대한 변환 메소드 구현을 요구하고 있습니다.
사실 이것만 구현하면 c#의 형변환에 참여할 수 있는 타입의 클래스를 제작할 수 있습니다. 이 경우의 인스턴스는 캐스팅이 아니라 형 변환이라고 봐야겠죠.
(실제 위의 레퍼런스 링크에서는 Complex라는 샘플 클래스를 통해 IConvertible의 구상을 보여주고 있습니다)
CLR공용타입이 전부 이 인터페이스를 구상하고 있으므로 이론적으로 모든 CLR공용타입은 다른 타입으로 변환시킬 수 있습니다.
Convert
이러한 IConvertible에 기반한 형 간의 변환에 대한 자세한 지식을 은닉하고 유틸리티로 제공해주는 게 System.Convert입니다.
이를 이용하면 다음과 같이 편리하게 형을 변환할 수 있습니다.
var str = "1234"; //문자열을 var dbl = Convert.ToDouble(str); //더블로!
ToXXX 시리즈에 CLR공용타입 전체가 정의되어있고 각 메소드는 다시 인자로 모든 CLR공용타입을 받아들이는 노가다성 조합메소드를 제공합니다.
즉 ToInt32(bool), ToInt32(char), ToInt32(byte),.. 이런 식으로 각 메소드마다 받아들이는 인자도 CLR공용타입 전부가 준비되어 방대한 메소드를 갖고 있습니다.
ChangeType
하지만 근본적으로 IConvertible에 의존하므로 typeof를 통해 넘어온 값을 is IConvertible 로 검사해보면 손쉽게 IConvertible의 메소드로 변환하여 반환할 수 있습니다. 이런 처리를 하는 메소드가 ChangeType입니다.
var str = "1234"; //문자열을 var dbl = Convert.ChangeType(str, typeof(double)); //더블로!
하지만 위의 결과에서 dbl 의 타입을 실제로 체크해보면 double이 아니라 object가 됩니다.
Convert.ChangeType 메소드 입장에서 인자로 들어온 값은
- value is IConvertible로 확인하면
- 결국 CLR공용타입 중 하나이거나 최종적으로 ToType을 호출할 수 있으므로
- 하나하나 비교하면서 일치하는 타입으로
변환하면 됩니다. 하지만 메소드의 반환타입은 하나 뿐이므로 object로 형변환하여 반환하게 됩니다. 가볍게 Convert의 ChangeType을 구현하면 다음과 같을 것입니다(.net core의 실제 코드도 대동소이합니다)
class Convert{ static object ChangeType(object value){ //IConvertible 이 아니면 정리 if(!(value is IConvertible)) throw; //들어온 값의 타입 var type = value.GetType(); //미리 형변환해 둔 IConvertible var v = (IConvertible)value; //국제화기준 var c = CultureInfo.CurrentCulture; //CLR공용타입과 하나씩 비교 if(type == typeof(int)) return (object)v.ToInt32(c.NumberFormat); else if(type == typeof(long)) return (object)v.ToInt64(c.NumberFormat); //....CLR공용타입 전체검사 및 매칭되는 메소드 호출결과 } }
ChangeType 변환 함수는 하나의 함수가 모든 인자에 대응할 수 있으므로 각 형에 맞춰 ToXXX 를 부르는 것보다 편리합니다.
내부에서 object로 넘어온 인자를 평가해주기 때문입니다.
하지만 반대로 반환되는 형이 언제나 object라서 호스트측에서 다시 변환해야 합니다. 이를 코드로 비교해보죠.
//ToXXX를 사용 var str = "1234"; int i32 = Convert.ToInt32(str); long i64 = Convert.ToInt64(str); double dbl = Convert.ToDouble(str);
결국 반환 형이 딱 떨어져 나오기 때문에 반환 값에 대한 형 변환이 없는 대신 원하는 메소드를 매번 지정해야 합니다.
//ChangeType를 사용 var str = "1234"; int i32 = (int)Convert.ChangeType(str, typeof(Int32)); long i64 = (long)Convert.ChangeType(str, typeof(Int64)); double dbl = (double)Convert.ChangeType(str, typeof(Double));
ChangeType이라는 메소드명은 하나로 유지되지만 인자로 타입을 보내야 할 뿐만 아니라 반환 값도 다시 형변환 해야 하는 상황입니다. 동일 메소드를 사용하기 위해 치루는 댓가가 너무 큰 거죠.
제네릭을 사용한 해법
둘의 불편함을 덜고 변환하고 싶은 인자로 간단히 전달하면서 원하는 형을 받기 위해 제네릭의 도움을 받아보죠. 간단한 와꾸를 짜보면..
class Util{ public static T To<T>(object value){ } }
정도로 생각해볼 수 있습니다. 이걸 사용하는 호스트 코드는
int i32 = Util.To<int>("1234"); long i64 = Util.To<long>("1234"); double dbl = Util.To<double>("1234");
정도로 정리할 수 있습니다. 훨씬 간결하면서도 반환 형이 확정적으로 들어오게 되었습니다.
제법 깔끔해졌으니 내부를 채워보죠.
제네릭을 통해 넘어오는 T의 경우 자바에서는 슈퍼타입토큰의 요령을 사용하지 않으면 타입을 얻을 수 없습니다. 하지만 c#은 바로 T에 대한 참조를 얻을 수 있으므로 코드는 매우 간단히 해결됩니다.
class Util{ public static T To<T>(object value){ return (T)Convert.ChangeType(v, typeof(T)); } }
이제 n타입에 대응하여 m타입을 반환할 수 있는 제네릭 기반의 변환 함수를 사용할 수 있게 되었습니다.
13.3F 가 될거야.
class Util{ public static T Plus<T>(object a, object b){ //a와 b를 T로 변환한 뒤 더한 결과를 T형으로 반환한다. } }
class Util{ public static T Plus<T>(object a, object b){ //T의 형을 얻는다 var type = typeof(T); //각 T에 따라 if를 생성한다. if(T == typeof(Int32)) return Convert.ToInt32(a) + Convert.ToInt32(b); if(T == typeof(Int64)) return Convert.ToInt64(a) + Convert.ToInt64(b); //...CLR공용타입 전부 짠다.. } }
class Util{ public static T Plus<T>(object a, object b){ return (T)((dynamic)Util.To<T>(a) + (dynamic)Util.To<T>(b)); } }
결론
개발 시 가장 기초 상황인 형 변환에 대해 다뤄봤습니다.
IConvertible를 근간에 두는 CLR공용타입 간 변환에 대한 기본적인 정책을 이해하고 Convert를 응용하여 보다 간편하게 형 변환 결과를 얻을 수 있었습니다.
recent comment