【问题标题】:Function pointers in C#C#中的函数指针
【发布时间】:2009-07-26 11:26:49
【问题描述】:

我想在某些方面,DelegateMethodInfo 中的任何一个(或两者)都符合这个称号。但是,两者都没有提供我正在寻找的语法上的好处。所以,简而言之,有什么方法可以写出以下内容:

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.");
    }
}

【问题讨论】:

    标签: c# reflection delegates


    【解决方案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 类非常相似:一个对象和一个MethodInfo。所以没必要重写。

    此外,C 和 C++ 中的函数指针并不符合您的需要:它们不能绑定到对象实例函数,而且它们是静态类型的,而不是动态类型的。 p>

    如果您想在 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 恰好是我定义静态方法的类。

    当然,如果你传递了错误的参数类型,那么你会在运行时出错。我可能会寻找一种方法来设计您的程序,以便在编译时捕获尽可能多的类型信息。

    【讨论】:

    • 虽然我认为您是思考这个问题的天才,但当我尝试使用以下方法创建委托时遇到绑定错误:return (DynamicFunction)Delegate.CreateDelegate(typeof(DynamicFunction), this, functions [功能]);
    • 这要复杂得多...等等。
    • B 类的编译时间将是运行时,正如我所说,它是用户生成的,所以遗憾的是,任何错误都必须由他们处理(并且有很好的规定,错误报告等,以做到这一点)。
    • 很公平 - 套用 Einstein 的话说,程序应该尽可能是静态类型的,但不能是静态类型的。
    【解决方案2】:

    这是您可以使用的另一段代码;反射相当慢,所以如果你希望你的动态函数调用被频繁调用,你不希望在委托中调用 method.Invoke:

    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 次类型检查、2 次委托调用以及由于参数样式传递而导致的一个数组构造。

    【讨论】:

    • 我似乎确实记得 Invoke 很慢并改用 CreateDelegate,这就是为什么我开始使用该解决方案的原因,因为完全有可能每次调用这些函数很多次第二...我会在我没有半睡半醒的时候查看这个细节(阅读:8小时左右)
    • 出于好奇,我针对我的方法对这种技术(使用两个参数)进行了计时,发现我的方法每次调用的开销为 百万分之一秒。所以我怀疑开销在实际应用程序中不太可能成为问题。取决于您的用户将在他们的功能中做什么样的事情。
    猜你喜欢
    • 1970-01-01
    • 2016-06-23
    • 2010-11-19
    • 2021-10-30
    • 1970-01-01
    • 2018-07-28
    • 1970-01-01
    相关资源
    最近更新 更多