【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
当类型参数在编译时未知,而是在运行时动态获取时,调用通用方法的最佳方法是什么?
考虑以下示例代码-在Example()
方法内部,使用存储在myType
变量中的Type
调用GenericMethod<T>()
的最简洁方法是什么?
public class Sample
{
public void Example(string typeName)
{
Type myType = FindType(typeName);
// What goes here to call GenericMethod<T>()?
GenericMethod<myType>(); // This doesn't work
// What changes to call StaticMethod<T>()?
Sample.StaticMethod<myType>(); // This also doesn't work
}
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
#1楼
通过使用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
类型,我们推迟提供类型参数,直到运行时。 当使用dynamic
参数调用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中的缓存机制,下一次使用相同类型参数的调用应更快。 当然,您可以编写代码来缓存调用的委托,但是通过使用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
参数传递给方法时,此调用将被绑定 。 方法重载解析在运行时发生,并尝试选择最佳重载。 因此,如果您使用BarItem
类型的对象调用ProcessItem
方法,则实际上将调用非泛型方法,因为它更适合此类型。 但是,传递Alpha
类型的参数时会出现运行时错误,因为没有方法可以处理此对象(通用方法具有where T : IItem
和Alpha
类未实现此接口的约束)。 但这就是重点。 编译器没有此调用有效的信息。 作为程序员,您知道这一点,并且应确保该代码运行无误。
返回类型陷阱
当您使用动态类型的参数调用非空方法时,其返回类型也可能是dynamic
。 因此,如果您将前面的示例更改为以下代码:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
那么结果对象的类型将是dynamic
。 这是因为编译器并不总是知道将调用哪个方法。 如果知道函数调用的返回类型,则应将其隐式转换为所需的类型,以便其余代码为静态类型:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
如果类型不匹配,则会出现运行时错误。
实际上,如果您尝试在上一个示例中获取结果值,那么您将在第二个循环迭代中遇到运行时错误。 这是因为您试图保存void函数的返回值。
#2楼
您需要使用反射来使方法开始,然后通过为MakeGenericMethod提供类型参数来“构造”它:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
对于静态方法,将null
作为第一个参数传递给Invoke
。 这与泛型方法无关,只是正常的反映。
如前所述,从C#4开始,使用dynamic
很多事情都比较简单-当然,如果可以使用类型推断。 在类型推断不可用的情况下(例如问题中的确切示例),它无济于事。
#3楼
从类型信息中调用泛型方法涉及三个步骤。
TLDR:使用类型对象调用已知的泛型方法可以通过以下方法完成:
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
其中GenericMethod<object>
是要调用的方法名称,以及满足常规约束的任何类型。
(Action)匹配要调用的方法的签名,即( Func<string,string,int>
或Action<bool>
)
第1步获取通用方法定义的MethodInfo
方法1:将GetMethod()或GetMethods()与适当的类型或绑定标志一起使用。
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
方法2:创建一个委托,获取MethodInfo对象,然后调用GetGenericMethodDefinition
从包含方法的类内部:
MethodInfo method = ((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
从包含方法的类之外:
MethodInfo method = ((Action)(new Sample())
.GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)Sample.StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
在C#中,方法的名称,即“ ToString”或“ GenericMethod”实际上是指可能包含一个或多个方法的一组方法。 在您提供方法参数的类型之前,您不知道所指的是哪种方法。
((Action)GenericMethod<object>)
引用特定方法的委托。 ((Func<string, int>)GenericMethod<object>)
引用了GenericMethod的另一个重载
方法3:创建一个包含方法调用表达式的lambda表达式,获取MethodInfo对象,然后获取GetGenericMethodDefinition
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
这分解为
创建一个lambda表达式,其中主体是对所需方法的调用。
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
提取主体并将其转换为MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
从方法获取通用方法定义
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
步骤2调用MakeGenericMethod创建具有适当类型的泛型方法。
MethodInfo generic = method.MakeGenericMethod(myType);
步骤3是使用适当的参数调用该方法。
generic.Invoke(this, null);
#4楼
这是我根据Grax的答案得出的 2美分,但是对于一般方法来说,需要两个参数。
假设您的方法在Helpers类中的定义如下:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
就我而言,U类型始终是可观察的集合,存储类型T的对象。
因为我已经预定义了类型,所以我首先创建“虚拟”对象,它们代表可观察的集合(U)和存储在其中的对象(T),并将在下面用于在调用Make时获取其类型。
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
然后调用GetMethod来找到您的Generic函数:
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
到目前为止,上述调用与上面说明的调用几乎相同,但是在您需要将多个参数传递给它时,差别很小。
您需要将Type []数组传递给MakeGenericMethod函数,该函数包含上面创建的“虚拟”对象的类型:
MethodInfo generic = method.MakeGenericMethod(
new Type[] {
myCollection.GetType(),
myObject.GetType()
});
完成后,您需要如上所述调用Invoke方法。
generic.Invoke(null, new object[] { csvData });
这样就完成了。 很有魅力!
更新:
正如@Bevan强调的那样,调用MakeGenericMethod函数时,由于它接受了参数,因此不需要创建数组,也不需要创建对象来获取类型,因为我可以将类型直接传递给此函数。 就我而言,由于我在另一个类中预定义了类型,因此我只是将代码更改为:
object myCollection = null;
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
myClassInfo.CollectionType,
myClassInfo.ObjectType
);
myCollection = generic.Invoke(null, new object[] { csvData });
myClassInfo包含2个Type
属性,我在运行时根据传递给构造函数的枚举值进行设置,并将为我提供相关的类型,然后在MakeGenericMethod中使用这些类型。
再次感谢您强调此@Bevan。
#5楼
没有人提供“ 经典的反射 ”解决方案,因此这里是完整的代码示例:
using System;
using System.Collections;
using System.Collections.Generic;
namespace DictionaryRuntime
{
public class DynamicDictionaryFactory
{
/// <summary>
/// Factory to create dynamically a generic Dictionary.
/// </summary>
public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
{
//Creating the Dictionary.
Type typeDict = typeof(Dictionary<,>);
//Creating KeyValue Type for Dictionary.
Type[] typeArgs = { keyType, valueType };
//Passing the Type and create Dictionary Type.
Type genericType = typeDict.MakeGenericType(typeArgs);
//Creating Instance for Dictionary<K,T>.
IDictionary d = Activator.CreateInstance(genericType) as IDictionary;
return d;
}
}
}
上面的DynamicDictionaryFactory
类有一个方法
CreateDynamicGenericInstance(Type keyType, Type valueType)
并创建并返回一个IDictionary实例,其实例的键和值的类型恰好在调用keyType
和valueType
上指定。
这是一个完整的示例,说明如何调用此方法以实例化和使用Dictionary<String, int>
:
using System;
using System.Collections.Generic;
namespace DynamicDictionary
{
class Test
{
static void Main(string[] args)
{
var factory = new DictionaryRuntime.DynamicDictionaryFactory();
var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));
var typedDict = dict as Dictionary<String, int>;
if (typedDict != null)
{
Console.WriteLine("Dictionary<String, int>");
typedDict.Add("One", 1);
typedDict.Add("Two", 2);
typedDict.Add("Three", 3);
foreach(var kvp in typedDict)
{
Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
}
}
else
Console.WriteLine("null");
}
}
}
执行上述控制台应用程序后,我们将获得正确的预期结果:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
来源:oschina
链接:https://my.oschina.net/stackoom/blog/3142809