我想用C#写这个:
SomeUnknownType x;
SuperDuperInvoke(x, "MethodName", param1, param2, param3);
SuperDuperInvoke2(x, "MethodName", "param1String", "param2String", "param3String");
获取一些我一无所知的对象、方法名和参数列表,然后调用该方法。SuperDuperInvoke2
假定参数可以从字符串转换。
我认为这样的事情是可能的使用动态框架。。。我就是不知道怎么。。。
我知道我可以用反射来做到这一点,但它很丑陋,也很烦人。。。
我会稍微解释一下自己。
我想把它用于一些业务服务器的集成测试。服务器有许多不同的组件可以处理请求,所有组件都加载到IoC容器中。我需要公开一些组件,主要是为了测试,所以我只想收到组件的名称,我应该用什么参数调用什么方法,然后只调用它。
我知道你写过你不喜欢Reflection,但这真的那么丑陋吗?
var result = x.GetType().GetMethod( "MethodName" ).Invoke( x, new object[] { methodParams });
如果该方法可能被重载,则不能只使用名称,还需要知道要使用多少参数来调用它。像这样的
var method = x.GetType()
.GetMethods()
.First(m => m.Name == "MethodName" && m.GetParameters().Length == 2);
var result = method.Invoke( x, new object[] { methodParams });
如果您还需要按类型匹配methodParams,这将不起作用。
你说你不喜欢使用反射,但是,由于你提到你只知道方法名是字符串,所以只有一种方法:反射。这并不像你想象的那么难。有一种方法:
编辑:代码已更新。它现在可以处理重载的成员和任意数量的参数。只要您知道参数的类型,或者参数已正确初始化为各自的类型,这将起作用。
public static object InvokeMethod(object o, string MethodName, object[] parameters)
{
// get the types of the params
List<Type> paramTypes = new List<Type>();
foreach (object p in parameters)
{
paramTypes.Add(p.GetType());
}
try
{
// get the method, equal to the parameter types
// considering overloading
MethodInfo methodInfo = o.GetType().GetMethod(MethodName, paramTypes.ToArray());
// and invoke the method with your parameters
return methodInfo.Invoke(o, parameters);
}
catch (MissingMethodException e)
{
// discard or do something
}
}
注意:在您的问题文本中,您提到所有参数都可以转换为字符串,并且您将值作为字符串传递。但是,如果你有一个像Calc(int)
和Calc(long)
这样的方法,并且你只有一个字符串值要转换为这两个方法中的任何一个,你将需要更多关于你的方法的信息,因为如果所有参数值都是字符串化的,就无法预先知道你应该调用其中的哪一个方法。
使用dynamic
关键字时,您确实需要在编译时知道方法名称,但实际编译成的是DLR API调用,方法名称为字符串常量。当然可以自己调用这些,但更棘手的是,dlr的性能取决于在这些api调用的同时创建的静态缓存站点。
开源框架ImpromptuInterface(在Nuget中找到)用一些静态调用方法封装DLR api。它对缓存网站进行散列处理,所以速度不如动态关键字,但至少比反射快2倍。唯一的技巧是,如果您试图调用一个期望值的void返回方法,那么它在尝试绑定时会抛出异常。示例实现:
public static dynamic SuperDuperInvoke(object target, string methodName, params object[] args){
try{
return Impromptu.InvokeMember(target, methodName, args);
}catch(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException){
Impromptu.InvokeMemberAction(target, methodName, args);
return null;
}
}
由于它是开源的(apache许可证),如果您不想要依赖关系,您可以随时转到InvokeMember等的源代码来帮助实现SuperDuperInvoke
。
SuperDuperInvoke2
更困难,因为DLR将尝试基于参数类型匹配方法,它将考虑隐式转换,但只静态定义隐式转换(DynamicObject
上的TryConvert
不起作用),因此您需要一个代理,该代理具有静态定义的隐式转换到所有预期类型,这可能是危险的——注意方法重载——它们可能对CCD_ 9不明确。
public static dynamic SuperDuperInvoke2(object target, string methodName, params ConvertableProxy[] args){
return SuperDuperInvoke(target,methodName,args);
}
public class ConvertableProxy{
private IConvertible _value;
public ConvertableProxy(IConvertible value){
_value =value;
}
//Automatically convert strings to proxy
public static implicit operator ConvertableProxy(string value){
return new ConvertableProxy(value);
}
public static implicit operator bool(ConvertableProxy proxy)
{
return proxy._value.ToBoolean(null);
}
public static implicit operator int(ConvertableProxy proxy)
{
return proxy._value.ToInt32(null);
}
public static implicit operator string(ConvertableProxy proxy)
{
return proxy._value.ToString(null);
}
//.. Add Char, DateTime, etc.
}
如果没有动态,你会这样做:
public static SuperDuperInvoke(object o, string methodName, params object parameters)
{
Type t = o.GetType();
MethodInfo mi = t.GetMethod(methodName);
if (mi == null) throw new Exception("no such method: " + methodName);
mi.invoke(mi, o, parameters.Length == 0 ? null : parameters);
}
现在,这是相当天真的,因为它没有寻找一个特定的方法。最好的办法是获取该名称的所有方法,并向下筛选到具有正确数字参数的方法,然后向下筛选到那些具有可从给定参数的类型中赋值的类型的方法,再选择具有最少upcast的方法。