Указатели функций в С#
Я полагаю, что в некотором смысле либо (или оба) Delegate
, либо MethodInfo
могут претендовать на этот титул. Однако ни то, ни другое не обеспечивают синтаксической привлекательности, которую я ищу. Итак, короче говоря, есть ли способ, которым я могу написать следующее:
FunctionPointer foo = // whatever, create the function pointer using mechanisms
foo();
Я не могу использовать твердый делегат (т.е. используя ключевое слово Delegate
, чтобы объявить тип делегата), потому что нет способа узнать до времени выполнения список точных параметров. Для справки, вот что я сейчас играл в LINQPad, где B
будет (в основном) сгенерированным пользователем кодом, и поэтому будет Main
и, следовательно, для моих пользователей, я пытаюсь удалить .Call
:
void Main()
{
A foo = new B();
foo["SomeFuntion"].Call();
}
// Define other methods and classes here
interface IFunction {
void Call();
void Call(params object[] parameters);
}
class A {
private class Function : IFunction {
private MethodInfo _mi;
private A _this;
public Function(A @this, MethodInfo mi) {
_mi = mi;
_this = @this;
}
public void Call() { Call(null); }
public void Call(params object[] parameters) {
_mi.Invoke(_this, parameters);
}
}
Dictionary<string, MethodInfo> functions = new Dictionary<string, MethodInfo>();
public A() {
List<MethodInfo> ml = new List<MethodInfo>(this.GetType().GetMethods());
foreach (MethodInfo mi in typeof(Object).GetMethods())
{
for (int i = 0; i < ml.Count; i++)
{
if (ml[i].Name == mi.Name)
ml.RemoveAt(i);
}
}
foreach (MethodInfo mi in ml)
{
functions[mi.Name] = mi;
}
}
public IFunction this[string function] {
get {
if (!functions.ContainsKey(function))
throw new ArgumentException();
return new Function(this, functions[function]);
}
}
}
sealed class B : A {
public void SomeFuntion() {
Console.WriteLine("SomeFunction called.");
}
}
Ответы
Ответ 1
Вы говорите, что хотите сохранить число и тип параметров открытыми, но вы можете сделать это с помощью delgate:
public delegate object DynamicFunc(params object[] parameters);
Это то же самое, что у вас есть. Попробуйте следующее:
class Program
{
static void Main(string[] args)
{
DynamicFunc f = par =>
{
foreach (var p in par)
Console.WriteLine(p);
return null;
};
f(1, 4, "Hi");
}
}
Вы можете представить делегат-метод метода как очень похожий на ваш класс Function
: объект a a MethodInfo
. Поэтому нет необходимости переписывать его.
Также указатели на функции в C и С++ не ближе к тому, что вам нужно: они не могут быть привязаны к экземпляру объекта и функции, а также они статически типизированы, а не динамически типизированы.
Если вы хотите "обернуть" любой другой метод в делегате DynamicFunc, попробуйте следующее:
public static DynamicFunc MakeDynamicFunc(object target, MethodInfo method)
{
return par => method.Invoke(target, par);
}
public static void Foo(string s, int n)
{
Console.WriteLine(s);
Console.WriteLine(n);
}
а затем:
DynamicFunc f2 = MakeDynamicFunc(null, typeof(Program).GetMethod("Foo"));
f2("test", 100);
Обратите внимание, что я использую статический метод Foo
, поэтому я передаю null
для экземпляра, но если это был метод экземпляра, я бы передавал объект для привязки. Program
оказывается классом, в котором определены мои статические методы.
Конечно, если вы передаете неправильные типы аргументов, вы получаете ошибки во время выполнения. Я бы, вероятно, искал способ разработки вашей программы, чтобы как можно больше информации о типе было записано во время компиляции.
Ответ 2
Вот еще один бит кода, который вы могли бы использовать; Отражение происходит довольно медленно, поэтому, если вы ожидаете, что вызовы динамических функций будут вызываться часто, вам не нужен метод. Внутри делегата:
public delegate void DynamicAction(params object[] parameters);
static class DynamicActionBuilder
{
public static void PerformAction0(Action a, object[] pars) { a(); }
public static void PerformAction1<T1>(Action<T1> a, object[] p) {
a((T1)p[0]);
}
public static void PerformAction2<T1, T2>(Action<T1, T2> a, object[] p) {
a((T1)p[0], (T2)p[1]);
}
//etc...
public static DynamicAction MakeAction(object target, MethodInfo mi) {
Type[] typeArgs =
mi.GetParameters().Select(pi => pi.ParameterType).ToArray();
string perfActName = "PerformAction" + typeArgs.Length;
MethodInfo performAction =
typeof(DynamicActionBuilder).GetMethod(perfActName);
if (typeArgs.Length != 0)
performAction = performAction.MakeGenericMethod(typeArgs);
Type actionType = performAction.GetParameters()[0].ParameterType;
Delegate action = Delegate.CreateDelegate(actionType, target, mi);
return (DynamicAction)Delegate.CreateDelegate(
typeof(DynamicAction), action, performAction);
}
}
И вы можете использовать его следующим образом:
static class TestDab
{
public static void PrintTwo(int a, int b) {
Console.WriteLine("{0} {1}", a, b);
Trace.WriteLine(string.Format("{0} {1}", a, b));//for immediate window.
}
public static void PrintHelloWorld() {
Console.WriteLine("Hello World!");
Trace.WriteLine("Hello World!");//for immediate window.
}
public static void TestIt() {
var dynFunc = DynamicActionBuilder.MakeAction(null,
typeof(TestDab).GetMethod("PrintTwo"));
dynFunc(3, 4);
var dynFunc2 = DynamicActionBuilder.MakeAction(null,
typeof(TestDab).GetMethod("PrintHelloWorld"));
dynFunc2("extraneous","params","allowed"); //you may want to check this.
}
}
Это будет немного быстрее; каждый динамический вызов будет включать в себя 1 typecheck для каждого парама, 2 вызова делегата и одну конструкцию массива из-за передачи параметров в стиле.