코드상에서는 단순히 MyClass obj = new MyClass()처럼 클래스의 객체를 생성할 수 있다.
런타임에서 어떤 클래스의 객체를 생성하고 싶을 땐 어떻게 해야 할까?
방법 1. Activator.CreateInstance()
인스턴스화 하고자 하는 클래스의 생성자 정보를 정확히 알고 있을 때 쓸 수 있는 방법이다. 제일 간단하다.
class MyClass
{
public MyClass() {}
public MyClass(int i) {}
}
static void Main(string[] args)
{
var obj = Activator.CreateInstance(typeof(MyClass), 1);
}
Activator.CreateInstance(Type, params object[]) 메서드는 지정된 타입의 생성자에서 인수 목록이 가장 일치하는 생성자를 호출하여 객체를 생성해준다.
typeof 대신 아래와 같이 GetType()으로 타입을 가져다가 넣어도 된다.
static void Main(string[] args)
{
var obj1 = new MyClass();
var obj2 = Activator.CreateInstance(obj1.GetType());
}
위 코드는 MyClass의 기본 생성자를 호출하여 객체를 생성해줄 것이다.
방법 2. Type.GetConstructors()로 직접 찾아서 호출
생성자 정보를 나타내는 ConstructorInfo 개체들을 가져다가 직접 매개변수 리스트를 비교하여 원하는 생성자를 호출할 수 있다. 물론 이 방법은 Activator.CreateInstance()에 비해 신경 써야 할 부분이 많지만 private 생성자 등 코드로 직접 호출할 수 없는 생성자도 호출할 수 있는 등의 장점이 있다.
static object createInstance( Type t, params object[] args )
{
var cons = t.GetConstructors( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance );
// 원래 default형식 매개변수나 params, ref, out 등 이곳에서 고려할 사항이 많으나 여기서는 타입 비교만 한다.
var ctorIndex = -1; // 호출 가능한 생성자의 인덱스
for ( var i = 0; i < cons.Length; i++ )
{
var paramInfos = cons[i].GetParameters();
// cons[i]의 매개변수 개수와 args에 지정된 값의 개수가 다른 경우 이 생성자는 호출할 수 있는 생성자가 아님.
if ( paramInfos.Length != args.Length ) continue;
bool invalid = false;
for ( var j = 0; j < paramInfos.Length && !invalid; j++ )
{
// args[j]가 null인 경우 타입을 비교할 수 없으므로 매개변수의 타입이 값 형식인지 아닌지로 판단한다.
if ( args[j] == null )
{
// 매개변수 타입이 값형식이라면 null을 대입할 수 없기에 호출 불가함.
if ( paramInfos[j].ParameterType.IsValueType )
{
invalid = true;
}
// 그렇지 않다면 타입과 상관 없이 null을 대입할 수 있기 때문에 OK.
}
// 두 타입이 서로 동일하다면 OK.
else if ( args[j].GetType() == paramInfos[j].ParameterType )
{
}
else
{
// TypeDescriptor.GetConverter()로 args[j]의 형식에 대한 타입 변환기를 가져온다.
var converter = TypeDescriptor.GetConverter( args[j].GetType() );
// 이 변환기로 args[j]의 값이 paramInfos[j]의 매개변수 형식으로 변환될 수 있는지 확인한다.
if ( !converter.CanConvertTo( paramInfos[j].ParameterType ) )
{
// 변환할 수 없으므로 continue
invalid = true;
}
}
}
if ( !invalid )
{
if ( ctorIndex != -1 )
{
// 이미 호출 가능한 생성자가 앞에서 1개 나왔음. = 생성자 모호함
throw new AmbiguousMatchException( "지정된 인수로 호출할 수 있는 생성자가 2개 이상 존재합니다." );
}
else
{
ctorIndex = i;
}
}
}
// 찾은 인덱스 번호의 생성자를 호출
if ( ctorIndex != -1 )
{
// 호출 & 반환
return cons[ctorIndex].Invoke( args );
}
else
{
throw new MissingMethodException( "지정된 인수와 일치하는 생성자를 찾을 수 없습니다." );
}
}
이 코드는 타입 검사에 실패하는 예가 꽤 많을 것이다. (예를 들어 IList 형식과 string 형식을 모두 Collection으로 취급하여 TypeConverter가 변환 가능하다고 판단한다.)
다만 여러 가지 Reflection 기능의 사용 예시들을 보여주기 위해 함께 포스팅한다.
'.NET > C# Reflection' 카테고리의 다른 글
[C# Reflection] GetValue()와 SetValue() 메서드 (0) | 2021.09.20 |
---|---|
[C# Reflection] GetField(), GetFields() 메서드와 FieldInfo 클래스 (0) | 2021.09.20 |
[C# Reflection] Reflection이란? (0) | 2021.09.14 |