dynamic
리플렉션 API 대신 형식 을 사용하면 런타임에만 알려진 형식 매개 변수를 사용하여 일반 메서드를 호출하는 것을 크게 단순화 할 수 있습니다 .
이 기술을 사용하려면 Type
클래스 의 인스턴스뿐만 아니라 실제 객체에서 유형을 알아야합니다 . 그렇지 않으면 해당 유형의 오브젝트를 작성하거나 표준 리플렉션 API 솔루션을 사용해야합니다 . Activator.CreateInstance 메서드 를 사용하여 개체를 만들 수 있습니다 .
일반 메소드를 호출하려면 "정상적인"사용법에서 해당 유형이 유추 된 것이므로 알 수없는 유형의 오브젝트를로 캐스팅하는 것입니다 dynamic
. 예를 들면 다음과 같습니다.
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
이 프로그램의 결과는 다음과 같습니다.
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
전달 된 인수의 실제 유형 ( GetType()
메서드 사용)과 일반 매개 변수 유형 ( typeof
연산자 사용 ) 을 작성하는 일반 인스턴스 메소드입니다 .
객체 인자를 dynamic
타입으로 캐스트함으로써 런타임까지 타입 파라미터를 제공하는 것을 연기했습니다. 때 Process
메소드가 불려 dynamic
인수 후 컴파일러는이 인수의 유형에 대해 상관하지 않는다. 컴파일러는 런타임에 리플렉션을 사용하여 전달 된 인수의 실제 유형을 확인하고 호출 할 최상의 메소드를 선택하는 코드를 생성합니다. 여기에는 하나의 일반 메소드 만 있으므로 적절한 유형 매개 변수를 사용하여 호출됩니다.
이 예제에서 출력은 다음과 같이 작성합니다.
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
동적 유형의 버전은 확실히 더 짧고 작성하기 쉽습니다. 또한이 함수를 여러 번 호출하는 성능에 대해 걱정하지 않아도됩니다. DLR 의 캐싱 메커니즘 덕분에 동일한 유형의 인수를 사용하는 다음 호출이 더 빨라 집니다. 물론 호출 된 대리자를 캐시하는 코드를 작성할 수 있지만 dynamic
형식을 사용하면이 동작을 무료로 얻을 수 있습니다.
호출하려는 일반 메소드에 매개 변수화 된 유형의 인수가없는 경우 (유형 매개 변수를 유추 할 수 없음) 다음 예제와 같이 헬퍼 메소드에서 일반 메소드의 호출을 랩핑 할 수 있습니다.
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
유형 안전성 향상
dynamic
리플렉션 API를 대신 하여 객체를 사용하는 것에 대한 가장 큰 장점은 런타임까지 알 수없는이 특정 유형의 컴파일 시간 검사 만 손실한다는 것입니다. 다른 인수와 메소드의 이름은 평소와 같이 컴파일러에 의해 정적으로 분석됩니다. 인수를 제거하거나 더 추가하거나, 유형을 변경하거나 메소드 이름을 바꾸면 컴파일 타임 오류가 발생합니다. 메소드 이름을 문자열로 Type.GetMethod
, 인수를 객체 배열로 인수로 제공하면 이런 일이 발생하지 않습니다 MethodInfo.Invoke
.
다음은 컴파일시 (코멘트 된 코드) 및 런타임시 일부 오류가 발생하는 방법을 보여주는 간단한 예입니다. 또한 DLR이 호출 할 메소드를 분석하는 방법을 보여줍니다.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
여기서 우리는 인자를 dynamic
타입 으로 캐스팅하여 몇 가지 메소드를 다시 실행 합니다. 첫 번째 인수 유형의 확인 만 런타임에 연기됩니다. 호출하는 메소드의 이름이 존재하지 않거나 다른 인수가 유효하지 않은 경우 (잘못된 인수 또는 유형이 잘못된 경우) 컴파일러 오류가 발생합니다.
dynamic
인수를 메소드에 전달하면 이 호출은 최근에 바인드 됩니다. 메소드 과부하 해결은 런타임에 발생하며 최상의 과부하를 선택하려고합니다. 따라서 유형 ProcessItem
의 객체로 메소드 를 호출하면 BarItem
이 유형과 더 잘 일치하기 때문에 실제로는 제네릭이 아닌 메소드를 호출합니다. 그러나이 Alpha
객체를 처리 할 수있는 메서드가 없기 때문에 형식 의 인수를 전달하면 런타임 오류가 발생 합니다 (일반 메서드에는 제약 조건이 where T : IItem
있고 Alpha
클래스는이 인터페이스를 구현하지 않습니다). 그러나 그것은 요점입니다. 컴파일러에이 호출이 유효하다는 정보가 없습니다. 프로그래머는 이것을 알고 있으며이 코드가 오류없이 실행되도록해야합니다.
리턴 타입
동적 유형의 매개 변수 void 이외의 메소드를 호출 할 때, 그것의 반환 형식은 아마 것입니다 수 dynamic
도 . 따라서 이전 예제를이 코드로 변경하려면 다음을 수행하십시오.
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
결과 객체의 유형은 dynamic
입니다. 컴파일러는 어떤 메소드가 호출 될지 항상 알지 못하기 때문입니다. 함수 호출의 리턴 유형을 알고 있으면 나머지 코드가 정적으로 유형이되도록 암시 적으로 이를 필수 유형으로 변환 해야합니다.
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
유형이 일치하지 않으면 런타임 오류가 발생합니다.
실제로 이전 예제에서 결과 값을 얻으려고하면 두 번째 루프 반복에서 런타임 오류가 발생합니다. void 함수의 반환 값을 저장하려고했기 때문입니다.