C#:委托、紧凑型访问者、"universal callable" 参数类型

C#: delegates, compact visitor, "universal callable" argument type

我正在寻找一种在 C# 中实现访问者的紧凑方法。 该代码将在 "object hierarchy walker" 函数中用于 Unity3D。

主要问题是我不知道如何在C#中将"universal callable argument"声明为方法参数。

    static void visitorTest(var visitor){ // <<---- which type?
        int i = 0;
        visitor(i);
    }

可以很容易地用C++模板函数表达

template<class Visitor> void visitorTest(Visitor visitor){
    visitor(i);
}

理想情况下 vistior 应该接受 class、方法(或静态方法)和某种 "lambda" 表达式。接受 "class" 是可选的。

我确实尝试使用 here and here 中的信息用 C# 编写它,但我没有做对。

我缺少一些主要与委托、Action、方法和 Func 之间的转换相关的基础知识,如果有人指出我还不知道的确切内容或者只是向我举个例子以便我理解,那就太好了我自己解决(修复两个编译错误比解释一切更省时)。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTest
{
    class Program
    {
        public delegate void Visitor(int i);
        public void visitorTest(Visitor visitor){
            int[] tmp = new int[10];
            for (int i = 0; i < tmp.Length; i++){
                tmp[i] = i;
            }
            foreach(var i in tmp){
                visitor(i);
            }
        }
        public static void funcCallback(int arg) {
            System.Console.WriteLine("func: " + arg.ToString());
        }
        static void Main(string[] args)
        {
            //An object reference is required for the non-static field, method, or property 'ConsoleTest.Program.visitorTest(ConsoleTest.Program.Visitor)
            visitorTest(new Visitor(funcCallback));

            int mul = 2;
            Action< int> lambda = (i) => System.Console.WriteLine("lambda: " + (2*i).ToString());

            //The best overloaded method match for 'ConsoleTest.Program.visitorTest(ConsoleTest.Program.Visitor)' has some invalid arguments    
            //Argument 1: cannot convert from 'System.Action<int>' to 'ConsoleTest.Program.Visitor'
            visitorTest(lambda);
            }
        }

}

C++ 代码示例:

理想情况下,我想要等效于此代码片段 (C++):

#include <vector>
#include <iostream>

template<class Visitor> void visitorTest(Visitor visitor){
    //initialization, irrelevant:
    std::vector<int> tmp(10);
    int i = 0;
    for(auto& val:  tmp){
        val =i;
        i++;
    }

    //processing:
    for(auto& val: tmp)
        visitor(val);
}

//function visitor
void funcVisitor(int val){
    std::cout << "func: " << val << std::endl;
}

//class visitor
class ClassVisitor{
public:
    void operator()(int arg){
        std::cout << "class: " << arg*val << std::endl;
    }
    ClassVisitor(int v)
        :val{v}{
    }
protected:
    int val;
};

int main(){
    visitorTest(funcVisitor);
    visitorTest(ClassVisitor(2));
    int arg = 3;
    /*
     * lambda visitor: equivalent to
     * 
     * void fun(int x){
     * }
     */
    visitorTest([=](int x){ std::cout << "lambda: " << arg*x << std::endl;});
}

输出:

func: 0
func: 1
func: 2
func: 3
func: 4
func: 5
func: 6
func: 7
func: 8
func: 9
class: 0
class: 2
class: 4
class: 6
class: 8
class: 10
class: 12
class: 14
class: 16
class: 18
lambda: 0
lambda: 3
lambda: 6
lambda: 9
lambda: 12
lambda: 15
lambda: 18
lambda: 21
lambda: 24
lambda: 27

visitorTest 是一个通用(模板)函数,可以将 lambda 表达式、class 或函数作为回调。

funcTest为回调函数

classTest 是一个 class 回调。

并且 main() 中的最后一行有 lambda 回调。


我可以通过提供各种抽象基础轻松地实现基于 class 的回调,但我希望有更灵活的方法,因为写完整的 class 通常太冗长,而写这种简单的东西的抽象基础是矫枉过正。

网上的信息表明,使用 Linq 和 Delegates 是可行的,但我无法在它们之间进行转换,或者只是传递 delegates。

建议?

删除您的访问者委托,只需将输入参数指定为 Action,它可以是采用一个 int 参数的任何方法。

using System;

namespace Testing
{
    internal class Program
    {
        public static void visitorTest(Action<int> visitor)
        {
            int[] tmp = new int[10];
            for (int i = 0; i < tmp.Length; i++)
            {
                tmp[i] = i;
            }
            foreach (var i in tmp)
            {
                visitor(i);
            }
        }

        public static void funcCallback(int arg)
        {
            System.Console.WriteLine("func: " + arg.ToString());
        }

        private static void Main(string[] args)
        {
            //An object reference is required for the non-static field, method, or property 'ConsoleTest.Program.visitorTest(ConsoleTest.Program.Visitor)
            visitorTest(funcCallback);

            int mul = 2;
            Action<int> lambda = (i) => System.Console.WriteLine("lambda: " + (2 * i).ToString());

            //The best overloaded method match for 'ConsoleTest.Program.visitorTest(ConsoleTest.Program.Visitor)' has some invalid arguments    
            //Argument 1: cannot convert from 'System.Action<int>' to 'ConsoleTest.Program.Visitor'
            visitorTest(lambda);
            Console.Read();
        }
    }
}

输出如下:

func: 0
func: 1
func: 2
func: 3
func: 4
func: 5
func: 6
func: 7
func: 8
func: 9
lambda: 0
lambda: 2
lambda: 4
lambda: 6
lambda: 8
lambda: 10
lambda: 12
lambda: 14
lambda: 16
lambda: 18

经过一番折腾,我想通了如何正确使用委托。更新后的示例如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTest
{
    class Program
    {
        public delegate void Visitor(int i);
        public static void visitorTest(Visitor visitor){
            int[] tmp = new int[10];
            for (int i = 0; i < tmp.Length; i++){
                tmp[i] = i;
            }
            foreach(var i in tmp){
                visitor(i);
            }
        }
        public static void funcCallback(int arg) {
            System.Console.WriteLine("func: " + arg.ToString());
        }
        static void Main(string[] args)
        {
            visitorTest(funcCallback);

            int mul = 2;

            visitorTest((int i)=>{System.Console.WriteLine("lambda: " + (mul*i).ToString());});
            Action<int> lambda = (int i) => { System.Console.WriteLine("lambda: " + (3 * i).ToString()); };
            visitorTest(lambda.Invoke);
        }
    }
}

我也放弃了将 class 实例作为回调传递,因为无论如何我都可以通过 lambda 来做到这一点。

解释(以防以后有人偶然发现):

        public delegate void Visitor(int i);

这一行声明了"delegate" type named Visitor,本质上有点等同于C++函数pointer.After声明它可以作为参数:

        public static void visitorTest(Visitor visitor){

并使用正常的函数调用语法进行调用。

                visitor(i);

方法和lambda表达式可以正常赋值给这个类型。

            visitorTest(funcCallback);

            visitorTest((int i)=>{System.Console.WriteLine("lambda: " + (mul*i).ToString());});

此外,this SO thread discussess differences between Action<>/Func<> and delegates