Bev*_*van 1002 c# generics reflection
当在编译时未知类型参数但是在运行时动态获取时,调用泛型方法的最佳方法是什么?
考虑以下示例代码 - 在Example()
方法内部,GenericMethod<T>()
使用Type
存储在myType
变量中调用的最简洁方法是什么?
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>()
{
//...
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 1085
您需要使用反射来获取方法,然后通过使用MakeGenericMethod提供类型参数来"构造"它:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Run Code Online (Sandbox Code Playgroud)
对于静态方法,将null
第一个参数传递给Invoke
.这与通用方法无关 - 它只是普通的反射.
如上所述,当C#4使用时,很多这样做更简单dynamic
- 当然,如果你可以使用类型推断.在类型推断不可用的情况下(例如问题中的确切示例),它没有帮助.
Adr*_*ero 161
只是对原始答案的补充.虽然这将工作:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Run Code Online (Sandbox Code Playgroud)
它也有点危险,因为你丢失了编译时检查GenericMethod
.如果您稍后进行重构和重命名GenericMethod
,则此代码将不会注意到,并且在运行时将失败.此外,如果对程序集进行任何后处理(例如,混淆或删除未使用的方法/类),则此代码也可能会中断.
所以,如果你知道你在编译时链接到的方法,并且这不会被调用数百万次因此开销无关紧要,我会将此代码更改为:
Action<> GenMethod = GenericMethod<int>; //change int by any base type
//accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Run Code Online (Sandbox Code Playgroud)
虽然不是很漂亮,但你有一个编译时参考GenericMethod
,如果你重构,删除或做任何事情GenericMethod
,这段代码将继续工作,或至少在编译时中断(如果你删除GenericMethod
).
执行相同操作的其他方法是创建一个新的包装类,并通过它创建它Activator
.我不知道是否有更好的方法.
Mar*_*ski 131
通过使用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)"
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下是该程序的输出:
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
Run Code Online (Sandbox Code Playgroud)
Process
是一个通用实例方法,它写入传递参数的实际类型(通过使用GetType()
方法)和泛型参数的类型(通过使用typeof
运算符).
通过将object参数转换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 });
}
Run Code Online (Sandbox Code Playgroud)
具有动态类型的版本肯定更短且更容易编写.您也不必担心多次调用此函数的性能.由于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) + ">");
}
}
Run Code Online (Sandbox Code Playgroud)
使用dynamic
object作为替换使用反射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);
}
}
Run Code Online (Sandbox Code Playgroud)
这里我们再次通过将参数强制转换为dynamic
类型来执行某些方法.只有第一个参数类型的验证才会推迟到运行时.如果您正在调用的方法的名称不存在或者其他参数无效(参数数量错误或类型错误),则会出现编译器错误.
将dynamic
参数传递给方法时,此调用最近被绑定.方法重载解析在运行时发生并尝试选择最佳过载.因此,如果您ProcessItem
使用BarItem
类型的对象调用该方法,那么您实际上将调用非泛型方法,因为它更适合此类型.但是,当您传递该Alpha
类型的参数时,您将收到运行时错误,因为没有可以处理此对象的方法(泛型方法具有约束where T : IItem
,Alpha
类不实现此接口).但这就是重点.编译器没有此调用有效的信息.作为程序员,你知道这一点,你应该确保这段代码运行没有错误.
当您使用动态类型的参数调用非void方法时,其返回类型可能也是dynamic
如此.因此,如果您将以前的示例更改为此代码:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Run Code Online (Sandbox Code Playgroud)
那么结果对象的类型就是dynamic
.这是因为编译器并不总是知道将调用哪个方法.如果你知道函数调用的返回类型,那么你应该隐式地将它转换为所需的类型,所以其余的代码是静态类型的:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
Run Code Online (Sandbox Code Playgroud)
如果类型不匹配,您将收到运行时错误.
实际上,如果您尝试在前一个示例中获取结果值,那么您将在第二个循环迭代中获得运行时错误.这是因为您尝试保存void函数的返回值.
jbt*_*ule 17
使用C#4.0,不需要反射,因为DLR可以使用运行时类型调用它.由于使用DLR库是一种动态的痛苦(而不是为您生成代码的C#编译器),开源框架Dynamitey(.net标准1.5)为您提供了对编译器生成的相同调用的简单缓存运行时访问为了你.
var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));
var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
Run Code Online (Sandbox Code Playgroud)
Gra*_*x32 12
从类型信息调用泛型方法涉及三个步骤.
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
Run Code Online (Sandbox Code Playgroud)
其中GenericMethod<object>
是要调用的方法名称以及满足通用约束的任何类型.
(Action)匹配要调用的方法的签名,即(Func<string,string,int>
或Action<bool>
)
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
Run Code Online (Sandbox Code Playgroud)
从包含方法的类内部:
MethodInfo method = ((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
Run Code Online (Sandbox Code Playgroud)
从包含方法的类的外部:
MethodInfo method = ((Action)(new Sample())
.GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)Sample.StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
Run Code Online (Sandbox Code Playgroud)
在C#中,方法的名称,即"ToString"或"GenericMethod"实际上是指一组可能包含一个或多个方法的方法.在提供方法参数的类型之前,不知道您指的是哪种方法.
((Action)GenericMethod<object>)
指的是特定方法的委托. ((Func<string, int>)GenericMethod<object>)
指的是GenericMethod的不同重载
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
Run Code Online (Sandbox Code Playgroud)
这打破了
创建一个lambda表达式,其中正文是对所需方法的调用.
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
Run Code Online (Sandbox Code Playgroud)
提取主体并强制转换为MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
Run Code Online (Sandbox Code Playgroud)
从方法中获取泛型方法定义
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
Run Code Online (Sandbox Code Playgroud)
MethodInfo generic = method.MakeGenericMethod(myType);
Run Code Online (Sandbox Code Playgroud)
generic.Invoke(this, null);
Run Code Online (Sandbox Code Playgroud)
没有人提供" 经典反射 "解决方案,所以这里有一个完整的代码示例:
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
上面的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");
}
}
}
Run Code Online (Sandbox Code Playgroud)
执行上述控制台应用程序时,我们得到正确的预期结果:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
238185 次 |
最近记录: |