Подтвердить что ты не робот

Указатели функций в С#

Я полагаю, что в некотором смысле либо (или оба) 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.");
    }
}
4b9b3361

Ответ 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 вызова делегата и одну конструкцию массива из-за передачи параметров в стиле.