Использование GetMethod? как он знает, какой из двух перегруженных методов использовать?
следует ли вместо этого использовать Expression.Call? как мы имеем дело с параметрами ref и out?
Ответ 2
Я искал какой-то подобный подход, но, к сожалению, его не нашел - но потом решил решить его сам. Однако - во время прототипирования я обнаружил, что "выход" и "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", поскольку он может поддерживать направление и направление, но "выход" поддерживает только направление.
Но кроме того, как кто-то мог заметить - если вам нужно поддерживать все виды перестановок параметров - простого кодирования недостаточно - вам нужно написать генератор кода - что я сделал в конце.
Таким образом, тестовый код выглядит примерно так:
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 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);
Функция регенерирует автоматически сгенерированную часть кода для поддержки - для демонстрационных целей я поддерживаю теперь только два параметра, которые вызывают, но обычно вам нужно больше параметров, но это увеличит размер автогенерированного кода.
Автогенерируемый код может обновляться только в режиме отладки, а не в конфигурации выпуска. (Но в релизе это вообще не требуется).
Возможно, это не прямой ответ на ваш вопрос, но я думаю, что это следует за вашей идеей.
Отразить вызов требует, чтобы все типы аргументов соответствовали 100% правильно - иначе отражение не сможет найти требуемый метод.
Также это решение имеет ограничение - например, он не сможет найти правильный метод, если некоторые параметры являются необязательными - например:
void DoMethod( int a, int b = 0 );
Итак, вы можете позвонить:
DoMethod(5);
Но не:
DoCall("DoMethod", 5);
должен быть полный набор аргументов:
DoCall("DoMethod", 5, 0);
Я понимаю, что метод вызова через отражение может быть дорогостоящим в потребляемом времени, поэтому подумайте дважды, прежде чем использовать его.
Обновление 31.5.2016. Я также узнал, что ключевое слово С# "dynamic" можно использовать также для динамического вызова определенного метода, не зная детали вызова метода отражения, но динамика работает только с экземплярами, статические вызовы метода по-прежнему легче сделать с помощью ClassCaller.