0

I'm looking for a compact way to implement visitor in C#. The code is going to be used in Unity3D in "object hierarchy walker" function.

The main problem is that I don't know how to declare "universal callable argument" as a method parameter in C#.

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

which can be easily expressed in C++ template functions

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

Ideally vistior should accept class, method (or static method) and some sort of "lambda" expression. Accepting "class" is optional.

I did try to write it in C# using information from here and here, but I did not get it right.

I'm missing some fundamental knowledge mostly related to conversion between delegates, Action, methods and Func, would be nice if someone pointed what exactly I don't know yet or just threw example at me so I can figure it out myself (fixing two compile errors would be less time consuming than explaining everything).


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++ code example:

Ideally I would like to have equivalent of this code fragment (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;});
}

Output:

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 is a universal (template) function that can take lambda expression, class or a function as a callback.

funcTest is function callback.

classTest is a class callback.

and the last line within main() has lambda callback.


I can make class based callback easily by providing abstract base of sorts, but I'd like to have more flexible approach, because writing full blown class is often too verbose, and writing abstract base for something this simple is overkill.

Online information suggests that the way to do it is to use Linq and Delegates, but I have trouble converting between them or just passing along the delegate.

Advice?

Community
  • 1
  • 1
SigTerm
  • 26,089
  • 6
  • 66
  • 115

2 Answers2

1

Remove your Visitor delegate, and just specify the input parameter as Action, which can be any method that takes one int parameter.

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();
        }
    }
}

Output looks like:

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
Callum Bradbury
  • 936
  • 7
  • 14
0

After a bit of messing around, I figured out how to use delegates properly. Updated example is below.

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

I also gave up on on passing class instances as callbacks, because I can do that through lambdas anyway.

Explanation (in case someone stumbles into this later):

        public delegate void Visitor(int i);

this line declares "delegate" type named Visitor, which is essentially somewhat equivalent to C++ function pointer.After declaration it can be used as parameter:

        public static void visitorTest(Visitor visitor){

And called using normal function call syntax.

                visitor(i);

Methods and lambda expressions can be assigned to this type normally.

            visitorTest(funcCallback);

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

Also, this SO thread discussess differences between Action<>/Func<> and delegates.

Community
  • 1
  • 1
SigTerm
  • 26,089
  • 6
  • 66
  • 115