您可以在不将任何字符串传递到运行时搜索的情况下,在编译时相对优美地选择特定的方法通用重载,就像其他答案所做的那样。
静态方法
假设您有多个名称相同的静态方法,例如:
public static void DoSomething<TModel>(TModel model)
public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)
如果您创建的Action或Func与您正在寻找的重载的泛型计数和参数计数匹配,您可以在编译时使用相对较少的技巧选择它。
示例:选择第一个方法 - 返回void,因此使用Action,需要一个泛型。我们使用object来避免立即指定类型:
var method = new Action<object>(MyClass.DoSomething<object>)
示例:选择第二种方法 - 返回void,因此使用Action,两个通用类型所以将type object分别用于两个通用参数:
var method = new Action<object, object>(MyClass.DoSomething<object, object>);
您得到了所需的方法,无需进行任何疯狂的管道操作,也不需要运行时搜索或使用风险字符串。
MethodInfo
通常在反射中,您需要MethodInfo对象,您还可以以编译安全的方式获取它。这是当您传递要在方法中使用的实际泛型类型时发生的情况。假设您想要上面第二个方法:
var methodInfo = method.Method.MakeGenericMethod(type1, type2);
这是一个通用的方法,没有任何反射搜索或对 GetMethod() 的调用或脆弱的字符串。
静态扩展方法
你提到的具体例子 Queryable.Where 迫使你在 Func 定义中变得有点花哨,但一般都是按照相同的模式进行的。最常用的 Where() 扩展方法的签名如下:
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 });
}
这特别有助于从类内部选择特定实例方法,然后将其公开给需要不同类型的外部调用者。
附录
下面的一些评论说他们无法使其工作。也许并不奇怪,我并不经常像这样选择通用方法,但今天我正在这样做,在经过充分测试的代码中,该代码一直在幕后使用,因此我想提供一个真实的例子——也许它将有助于那些难以使其正常工作的人。
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
将其返回类型作为其最后的泛型参数,并且我们在打算抓取的方法中无论何时都可以用object
替换泛型。但是,正如您在此示例中看到的那样,我们不需要在任何其他地方替换object
。例如,我们知道我们想要传递一个接受PropertyInfo
并返回true/false(bool
)的where子句,我们只需在Func
中直接指定这些类型。
作为Func的构造函数参数,我们传递CopyList()
——但请记住,由于方法重载,名称CopyList
是含糊不清的。真正酷的是,C#现在正在为您做这项艰巨的工作,通过查看Func参数并确定正确的参数。事实上,如果您在类型或参数数量方面犯了错误,Visual Studio实际上会用错误标记该行:
没有匹配委托 'Func...'的'CopyList'重载
它不够聪明,不能告诉你需要修复什么,但是如果您看到该错误,就接近了——您需要仔细检查参数和返回类型,并准确匹配它们,并将通用参数替换为object。
第三行,我们调用C#内置的.GetMethodInfo()
,然后是.MakeGeneric(listTo)
。对于这个方法,我们只有一个泛型要设置,所以我们将其作为listTo
传递进去。如果有2个,我们在此处传递2个参数。这些参数替换了我们之前进行的替换。
就这样,我们可以调用copyListMethod()
,无需字符串,完全编译安全。最后一行进行了调用,首先传递null,因为它是一个静态方法,然后是一个包含3个参数的数组。 完成。
我说过我会回来介绍ReflectionHelper
方法。下面就是它:
public static Type GetIEnumerableType(Type type)
{
var ienumerable = type.GetInterface(typeof(System.Collections.Generic.IEnumerable<>).FullName);
var generics = ienumerable.GetGenericArguments();
return generics[0];
}
var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);
- Valerie1[System.Object] Where[Object](System.Linq.IQueryable
1[System.Object], System.Linq.Expressions.Expression1[System.Func
2[System.Object,System.Boolean]])不是一个泛型方法定义。MakeGenericMethod只能在MethodBase.IsGenericMethodDefinition为true的方法上调用。 - Tsahi Asher