您可以在编译时优雅地选择方法的特定泛型重载,而无需像此处的其他答案那样将任何字符串传递给运行时搜索。
静态方法
假设您有多个同名的静态方法,例如:
public static void DoSomething<TModel>(TModel model)
public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)
// etc
如果您创建的 Action 或 Func 与您要查找的重载的泛型计数和参数计数相匹配,您可以在编译时选择它,并使用相对较少的杂技。
示例:选择第一种方法 - 返回 void,因此使用一个 Action,采用一个泛型。我们使用 object 来避免指定类型:
var method = new Action<object>(MyClass.DoSomething<object>);
示例:选择第二种方法 - 返回 void,因此 Action,2 个泛型类型,因此使用类型对象两次,2 个泛型参数各使用一次:
var method = new Action<object, object>(MyClass.DoSomething<object, object>);
您只需获得所需的方法,无需进行任何疯狂的管道操作,也无需运行时搜索或使用有风险的字符串。
方法信息
通常在反射中您需要 MethodInfo 对象,您也可以通过编译安全的方式获得该对象。这是当您传递要在方法中使用的实际泛型类型时。假设你想要上面的第二种方法:
var methodInfo = method.Method.MakeGenericMethod(type1, type2);
您的通用方法没有任何反射搜索或对 GetMethod() 的调用或脆弱的字符串。
静态扩展方法
您在 Queryable.Where 中引用的具体示例迫使您对 Func 定义有所了解,但通常遵循相同的模式。 The signature for the most commonly used Where() extension method 是:
public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>)
显然这会稍微复杂一些 - 这里是:
var method = new Func<IQueryable<object>,
Expression<Func<object, bool>>,
IQueryable<object>>(Queryable.Where<object>);
var methodInfo = method.Method.MakeGenericMethod(modelType);
实例方法
结合 Valerie 的评论 - 要获得实例方法,您需要做一些非常相似的事情。假设你的类中有这个实例方法:
public void MyMethod<T1>(T1 thing)
首先选择方法和静态方法一样:
var method = new Action<object>(MyMethod<object>);
然后调用 GetGenericMethodDefinition() 以获取通用 MethodInfo,最后使用 MakeGenericMethod() 传递您的类型:
var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);
解耦 MethodInfo 和参数类型
问题中没有要求这样做,但是一旦您执行上述操作,您可能会发现自己在一个地方选择方法,并决定在另一个地方传递什么类型。您可以将这两个步骤解耦。
如果您不确定要传入的泛型类型参数,您始终可以在没有它们的情况下获取 MethodInfo 对象。
静态:
var methodInfo = method.Method;
实例:
var methodInfo = method.Method.GetGenericMethodDefinition();
然后将其传递给其他知道要实例化类型的方法并调用该方法 - 例如:
processCollection(methodInfo, type2);
...
protected void processCollection(MethodInfo method, Type type2)
{
var type1 = typeof(MyDataClass);
object output = method.MakeGenericMethod(type1, type2).Invoke(null, new object[] { collection });
}
这特别有帮助的一件事是从类内部选择类的特定实例方法,然后将其公开给以后需要各种类型的外部调用者。
附录
下面的一些 cmets 说他们无法让这个工作。我不必经常选择像这样的通用方法可能并不奇怪,但我今天碰巧这样做了,在幕后使用的经过良好测试的代码中,所以我想我会提供现实世界的例子 - 也许它会帮助那些努力让它发挥作用的人。
C# 缺少 Clone 方法,所以我们有自己的方法。它可以采用许多参数,包括解释如何在源对象内递归复制 IEnumerable 属性的参数。
复制 IEnumerable 的方法名为 CopyList,如下所示:
public static IEnumerable<TTo> CopyList<TTo>(
IEnumerable<object> from,
Func<PropertyInfo, bool> whereProps,
Dictionary<Type, Type> typeMap
)
where TTo : new()
{
为了使事情复杂化(并锻炼这种方法的肌肉),它有几个重载,比如这个:
public static IEnumerable<TTo> CopyList<TTo>(
IEnumerable<object> from,
Dictionary<Type, Type> typeMap
)
where TTo : new()
{
所以,我们有几个(我只向您展示 2 个,但代码中还有更多)方法签名。它们具有相同数量的通用参数,但不同数量的方法参数。名称是相同的。我们怎么可能调用正确的方法?开始 C# 忍者!
var listTo = ReflectionHelper.GetIEnumerableType(
fromValue.GetType());
var fn = new Func<
IEnumerable<object>,
Func<PropertyInfo, bool>,
Dictionary<Type, Type>,
IEnumerable<object>>(
ModelTransform.CopyList<object>);
var copyListMethod = fn.GetMethodInfo()
.GetGenericMethodDefinition()
.MakeGenericMethod(listTo);
copyListMethod.Invoke(null,
new object[] { fromValue, whereProps, typeMap });
第一行使用了一个辅助方法,我们将在后面讨论,但它所做的只是在该属性中获取 IEnumerable 列表的泛型类型,并将其分配给listTo。下一行是我们真正开始执行此技巧的地方,我们在其中布置了具有足够参数的Func,以匹配我们打算获取的特定CopyList() 重载。具体来说,我们想要的 CopyList() 有 3 个参数,并返回 IEnumerable<TTo>。请记住,Func 将其返回类型作为其最后一个泛型 arg,并且我们将替换 object,只要我们打算获取的方法中有泛型。但是,正如您在此示例中所看到的,我们不需要在其他任何地方替换对象。例如,我们知道我们想要传递一个接受 PropertyInfo 并返回真/假 (bool) 的 where 子句,而我们只是在 Func 中直接说出这些类型。
作为 Func 的构造函数参数,我们传递 CopyList() - 但请记住,名称 CopyList 由于方法重载而含糊不清。真正酷的是,C# 现在正在为您做艰苦的工作,通过查看 Func 参数并确定正确的参数。事实上,如果你弄错了 args 的类型或数量,Visual Studio 实际上会用错误标记该行:
'CopyList' 没有重载匹配委托 'Func...'
告诉你究竟需要修复什么还不够聪明,但如果你看到那个错误,你就很接近了 - 你需要仔细检查 args 和返回类型并将它们完全匹配,将 Generic args 替换为目的。
在第三行,我们调用 C# 内置的 .GetMethodInfo(),然后是 .MakeGeneric(listTo)。我们只有一个 Generic 可以为此设置,因此我们将其传递为 listTo。如果我们有 2 个,我们会在这里传递 2 个参数。这些 Type 参数正在替换我们之前所做的 object 替换。
就是这样——我们可以调用copyListMethod(),没有字符串,完全编译安全。最后一行进行调用,首先传递 null,因为它是一个静态方法,然后是一个带有 3 个参数的 object[] 数组。完成。
我说过我会回到ReflectionHelper 方法。这里是:
public static Type GetIEnumerableType(Type type)
{
var ienumerable = type.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName);
var generics = ienumerable.GetGenericArguments();
return generics[0];
}