我一直在寻找某种类似的方法,但不幸的是没有找到它 - 但后来决定自己解决它。然而——在原型设计过程中,我发现“out”和“ref”是相互排斥的——所以你只能支持其中一个。
所以我想支持如下语法:
object DoCall<A1>( String function, A1 a1 )
这将需要生成如下函数:
object DoCall<A1>( String function, out A1 a1 )
object DoCall<A1>( String function, ref A1 a1 )
哪个编译器不喜欢。
所以我决定只支持'ref'关键字——因为它可以支持进出方向,但'out'只支持出方向。
但另外就像有人可能注意到的那样——如果你需要支持所有类型的参数排列——简单的编码是不够的——你需要编写代码生成器——我最后做了什么。
所以测试代码看起来像这样:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace TestReflection
{
public class CustomClassAsArg
{
public string MyInfo { get; set; }
}
public class CallMe
{
public void Hello1(String msg, int i)
{
Console.WriteLine(msg + ": " + i.ToString());
}
public void Hello2(ref String msg)
{
msg += "out string";
}
public void Hello2(ref int a)
{
a += 2;
}
public string Hello3(string a)
{
return a + "--";
}
public static bool MyStaticMethod( int arg, ref String outs )
{
outs = "->" + arg.ToString();
return true;
}
public bool? ThreeStateTest( int i )
{
switch ( i )
{
case 0:
return null;
case 1:
return false;
case 2:
return true;
}
return null;
}
public void UpdateCC( CustomClassAsArg c )
{
c.MyInfo = "updated";
}
}
class Program
{
static void Main(string[] args)
{
ClassCaller.UpdateSourceCodeHelperFunctions(2);
CallMe m = new CallMe();
ClassCaller c = new ClassCaller(m);
string r = "in string ";
int arg = 1;
String sx = "";
object ox = c.DoCall("!MyStaticMethod", 23, ref sx);
Console.WriteLine(sx);
c.DoCall("Hello1", "hello world", 1);
c.DoCall("Hello2", ref r);
Console.WriteLine(r);
c.DoCall("Hello2", ref arg);
Console.WriteLine(arg.ToString());
bool? rt = (bool?)c.DoCall("ThreeStateTest", 0);
rt = (bool?)c.DoCall("ThreeStateTest", 1);
rt = (bool?)c.DoCall("ThreeStateTest", 2);
CustomClassAsArg ccarg = new CustomClassAsArg();
c.DoCall("UpdateCC",ccarg);
} //Main
}
}
和 ClassCaller.cs 本身:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
/// <summary>
/// Helper class for performing invoke function call in dynamically loaded assembly.
/// </summary>
public class ClassCaller
{
Type type;
object o;
bool throwOnError;
/// <summary>
/// Can specify class using only assembly name / class type without
/// actual class instance - if you intend to call only static methods.
/// </summary>
/// <param name="assemblyName">Assembly name</param>
/// <param name="className">Class name, including namespace</param>
/// <param name="_throwOnError">true if throw on error</param>
public ClassCaller(String assemblyName, String className, bool _throwOnError)
{
throwOnError = _throwOnError;
Assembly asm = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.GetName().Name == assemblyName).FirstOrDefault();
if (asm == null)
{
if (_throwOnError)
throw new NullReferenceException("Assembly with name '" + assemblyName + "' was not found");
return;
}
type = asm.GetType(className, _throwOnError);
}
public ClassCaller(object _o)
{
type = _o.GetType();
o = _o;
}
/// <summary>
/// Gets method to invoke.
/// </summary>
/// <param name="func">Function name to get. Use '!' as a prefix if it's static function.</param>
/// <param name="types">Function argument types.</param>
/// <returns>Method to be invoked.</returns>
public MethodInfo GetFunc(String func, Type[] types)
{
bool bIsStatic = func.FirstOrDefault() == '!';
if (bIsStatic) func = func.Substring(1);
BindingFlags f = BindingFlags.Public | BindingFlags.NonPublic;
if (!bIsStatic)
f |= BindingFlags.Instance;
else
f |= BindingFlags.Static;
MethodInfo m = type.GetMethod(func, f, null, types, null);
if (m == null && throwOnError)
throw new NotSupportedException("Compatible function '" + func + "' not found");
return m;
}
//Autogenerated code starts (Do not edit)
public object DoCall(string func)
{
Type[] types = new Type[] { };
object[] args = new object[] { };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
return r;
}
public object DoCall<A1>(string func, A1 a1)
{
Type[] types = new Type[] { typeof(A1) };
object[] args = new object[] { a1 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
return r;
}
public object DoCall<A1>(string func, ref A1 a1)
{
Type[] types = new Type[] { typeof(A1).MakeByRefType() };
object[] args = new object[] { a1 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
a1 = (A1)args[0];
return r;
}
public object DoCall<A1, A2>(string func, A1 a1, A2 a2)
{
Type[] types = new Type[] { typeof(A1), typeof(A2) };
object[] args = new object[] { a1, a2 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
return r;
}
public object DoCall<A1, A2>(string func, ref A1 a1, A2 a2)
{
Type[] types = new Type[] { typeof(A1).MakeByRefType(), typeof(A2) };
object[] args = new object[] { a1, a2 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
a1 = (A1)args[0];
return r;
}
public object DoCall<A1, A2>(string func, A1 a1, ref A2 a2)
{
Type[] types = new Type[] { typeof(A1), typeof(A2).MakeByRefType() };
object[] args = new object[] { a1, a2 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
a2 = (A2)args[1];
return r;
}
public object DoCall<A1, A2>(string func, ref A1 a1, ref A2 a2)
{
Type[] types = new Type[] { typeof(A1).MakeByRefType(), typeof(A2).MakeByRefType() };
object[] args = new object[] { a1, a2 };
MethodInfo f = GetFunc(func, types);
if (f == null)
return null;
object r = f.Invoke(o, args);
a1 = (A1)args[0];
a2 = (A2)args[1];
return r;
}
//Autogenerated code ends
public static void UpdateSourceCodeHelperFunctions( int nParametersToSupport)
{
String srcFilename = new StackTrace(true).GetFrame(0).GetFileName();
String src = File.ReadAllText(srcFilename, Encoding.UTF8);
String autogenRegex = "(Autogenerated\\scode\\sstarts.*?[\r\n]{2})(.*)([\r\n]{2}\\s+//Autogenerated\\scode\\sends)";
if (!Regex.Match(src, autogenRegex, RegexOptions.Singleline).Success)
{
Console.WriteLine("Error: Invalid source code");
return;
}
string[] argType = new String[] { "", "ref" };
String s = "";
string lf = "\r\n";
string headSpace = " ";
for (int callArgs = 0; callArgs <= nParametersToSupport; callArgs++)
{
int[] argTypes = new int[callArgs];
int iterations = (int)Math.Pow(2, callArgs);
for (int i = 0; i < iterations; i++)
{
//public object DoCall<A1, A2>(String func, A1 a1, A2 a2)
s += headSpace;
s += "public object DoCall" + ((callArgs != 0) ? "<" : "");
s += String.Join(", ", Enumerable.Range(1, callArgs).Select(n => "A" + n));
s += (callArgs != 0) ? ">" : "";
s += "(string func";
String types = "";
String paramsList = "";
bool[] isRefType = new bool[callArgs];
for (int iArg = 0; iArg < callArgs; iArg++)
{
isRefType[iArg] = (((1 << iArg) & i) != 0);
String isRef = isRefType[iArg] ? "ref " : "";
String argTypeName = "A" + (iArg + 1);
String argName = "a" + (iArg + 1);
s += ", ";
s += isRef;
s += argTypeName + " " + argName;
if (iArg != 0)
{
types += ", ";
paramsList += ", ";
}
types += "typeof(" + argTypeName + ")";
if (isRefType[iArg])
types += ".MakeByRefType()";
paramsList += argName;
} //for
s += ")";
s += lf;
s += headSpace + "{" + lf;
//Type[] types = new Type[] { typeof(A1).MakeByRefType() };
s += headSpace + " ";
if( types.Length != 0 ) types += " ";
s += "Type[] types = new Type[] { " + types + "};";
s += lf;
//object[] args = new object[] { a1 };
s += headSpace + " ";
if( paramsList.Length != 0 ) paramsList += " ";
s += "object[] args = new object[] { " + paramsList + "};";
s += lf;
//MethodInfo f = GetFunc(func, types);
//if (f == null)
// return null;
//object r = f.Invoke(o, args);
s += headSpace + " MethodInfo f = GetFunc(func, types);" + lf;
s += headSpace + " if (f == null)" + lf;
s += headSpace + " return null;" + lf;
s += headSpace + " object r = f.Invoke(o, args);" + lf;
for (int iArg = 0; iArg < callArgs; iArg++)
{
if (!isRefType[iArg])
continue;
// a1 = (A1)args[0];
String argTypeName = "A" + (iArg + 1);
String argName = "a" + (iArg + 1);
s += headSpace + " ";
s += argName + " = (" + argTypeName + ")args[" + iArg + "];";
s += lf;
}
s += headSpace + " return r;" + lf;
s += headSpace + "}" + lf;
s += lf;
}
} //for
String oldautogenCode = Regex.Match(src, autogenRegex, RegexOptions.Singleline).Groups[2].Value;
//
// Visual studio text editor configuration affects spacing. We trim here everything so we can compare output.
//
oldautogenCode = oldautogenCode.Replace(" ", "").TrimStart('\r','\n');
String newautogenCode = s.Replace(" ", "").TrimStart('\r', '\n');
String newSrc = Regex.Replace(src, autogenRegex, "$1\r\n" + s + "$3", RegexOptions.Singleline);
if (oldautogenCode == newautogenCode)
{
Console.WriteLine("Source code is up-to-date.");
}
else
{
File.WriteAllText(srcFilename, newSrc, Encoding.UTF8);
}
} //UpdateSourceCodeHelperFunctions
} //class ClassCaller
所以:
ClassCaller.UpdateSourceCodeHelperFunctions(2);
函数重新生成自动生成的代码部分以支持 - 出于演示目的,我现在只支持 2 个要调用的参数,但通常您需要更多参数 - 但这会增加自动生成的代码大小。
自动生成的代码只能在调试模式下更新,而不是在发布配置中。(但这在发布时根本不需要)。
也许这不是您问题的直接答案,但我认为它符合您的想法。
反射调用要求所有参数类型 100% 正确匹配 - 否则反射将无法找到所需的方法。
此解决方案也有局限性 - 例如,如果某些参数是可选的,它将无法找到正确的方法 - 例如:
void DoMethod( int a, int b = 0 );
所以你可以打电话:
DoMethod(5);
但不是:
DoCall("DoMethod", 5);
它必须是完整的参数集:
DoCall("DoMethod", 5, 0);
我知道通过反射调用的方法在消耗时间方面可能会很昂贵,所以在使用它之前要三思而后行。
更新 31.5.2016我还发现 C# 的“动态”关键字也可用于动态调用特定方法,而无需了解反射方法调用详细信息,但动态仅对实例进行操作,使用 ClassCaller 仍然更容易进行静态方法调用.