通过使用动态类型而不是反射API,可以极大地简化使用只有在运行时才知道的类型参数来调用泛型方法。
要使用这种技术,必须从实际对象(而不仅仅是type类的实例)中了解类型。否则,您必须创建该类型的对象或使用标准反射API解决方案。您可以使用Activator创建对象。CreateInstance除外的方法。
如果你想调用一个泛型方法,在“正常”用法中,它会推断出它的类型,那么它只是将未知类型的对象强制转换为动态的。这里有一个例子:
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操作符)。
通过将对象参数转换为动态类型,我们将类型参数的提供推迟到运行时。当使用动态参数调用Process方法时,编译器并不关心此参数的类型。编译器生成的代码在运行时检查传入参数的真实类型(通过反射)并选择要调用的最佳方法。这里只有这一个泛型方法,因此使用适当的类型参数调用它。
在这个例子中,输出和你写的是一样的:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
动态类型的版本肯定更短,更容易编写。你也不应该担心多次调用这个函数的性能。由于DLR中的缓存机制,下一次具有相同类型参数的调用应该更快。当然,您可以编写代码缓存调用的委托,但通过使用动态类型,您可以免费获得这种行为。
如果你想调用的泛型方法没有参数化类型的参数(所以它的类型参数不能被推断出来),那么你可以将泛型方法的调用包装在一个助手方法中,如下例所示:
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) + ">");
}
}
增加型号安全性
What is really great about using dynamic object as a replacement for using reflection API is that you only lose compile time checking of this particular type that you don't know until runtime. Other arguments and the name of the method are staticly analysed by the compiler as usual. If you remove or add more arguments, change their types or rename method name then you'll get a compile-time error. This won't happen if you provide the method name as a string in Type.GetMethod and arguments as the objects array in 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);
}
}
在这里,我们再次通过将参数强制转换为动态类型来执行一些方法。只有第一个参数类型的验证被推迟到运行时。如果所调用的方法的名称不存在或其他参数无效(参数数量错误或类型错误),则会得到编译器错误。
When you pass the dynamic argument to a method then this call is lately bound. Method overload resolution happens at runtime and tries to choose the best overload. So if you invoke the ProcessItem method with an object of BarItem type then you'll actually call the non-generic method, because it is a better match for this type. However, you'll get a runtime error when you pass an argument of the Alpha type because there's no method that can handle this object (a generic method has the constraint where T : IItem and Alpha class doesn't implement this interface). But that's the whole point. The compiler doesn't have information that this call is valid. You as a programmer know this, and you should make sure that this code runs without errors.
返回类型gotcha
当你用动态类型的参数调用一个非void方法时,它的返回类型也可能是动态的。所以如果你把前面的例子改为下面的代码:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
那么结果对象的类型将是动态的。这是因为编译器并不总是知道将调用哪个方法。如果你知道函数调用的返回类型,那么你应该隐式地将它转换为所需的类型,以便其余的代码是静态类型的:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
如果类型不匹配,则会得到一个运行时错误。
实际上,如果您尝试在前面的示例中获取结果值,那么您将在第二次循环迭代中得到一个运行时错误。这是因为您试图保存void函数的返回值。