C #: делегаты, компактный посетитель, «универсальный вызываемый» тип аргумента

Я ищу компактный способ реализовать посетителя в C #.
Код будет использоваться в Unity3D в функции «обходчика иерархии объектов».

Основная проблема заключается в том, что я не знаю, как объявить «универсальный вызываемый аргумент» в качестве параметра метода в C #.

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

что может быть легко выражено в шаблонных функциях C ++

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

в идеале vistior должен принимать класс, метод (или статический метод) и своего рода лямбда-выражение. Принятие «класса» не является обязательным.

Я пытался написать это в 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 является универсальной (шаблонной) функцией, которая может принимать лямбда-выражение, класс или функцию в качестве обратного вызова.

funcTest это обратный вызов функции.

classTest это обратный вызов класса.

и последняя строка в main () имеет лямбда-колбэк.


Я могу легко сделать обратный вызов на основе классов, предоставив абстрактную базу своего рода, но я бы хотел использовать более гибкий подход, потому что написание полноценного класса часто слишком многословно, а написание абстрактной базы для чего-то такого простого — излишне.

Онлайновая информация говорит о том, что способ сделать это — использовать Linq и Делегаты, но у меня возникают проблемы с конвертацией между ними или просто передачей делегата.

Совет?

0

Решение

Удалите ваш делегат Visitor и просто укажите входной параметр как 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
1

Другие решения

После недолгих раздумий я понял, как правильно использовать делегатов. Обновленный пример ниже.

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

Я также отказался от передачи экземпляров классов в качестве обратных вызовов, потому что в любом случае я могу сделать это через лямбды.

Объяснение (в случае, если кто-то наткнется на это позже):

        public delegate void Visitor(int i);

эта строка объявляет «делегат» тип named Visitor, который по сути эквивалентен указателю на функцию C ++. После объявления его можно использовать в качестве параметра:

        public static void visitorTest(Visitor visitor){

И вызывается с использованием обычного синтаксиса вызова функции.

                visitor(i);

Методы и лямбда-выражения могут быть назначены этому типу обычно.

            visitorTest(funcCallback);

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

Также, этот поток так обсуждают различия между действием<> / Func<> и делегаты.

0

По вопросам рекламы [email protected]