2010-01-20 5 views
10

Sono uno sviluppatore C++ che ha usato i segnali & slot in C++ che a me sembra essere analogo ai delegati in C#. Mi sono trovato in perdita nella ricerca della funzionalità fornita da "bind" e sento che mi manca qualcosa.(Come) è possibile associare/rebind un metodo per lavorare con un delegato di una firma diversa?

Mi sembra che qualcosa come il seguente, che è possibile in C++ dovrebbe essere possibile in C# con i delegati. Ecco alcuni psudo-codice per che cosa avrei fatto in C++:

Slot<void> someCallback; 

int foo(int i) 
{ 
    std::cout << "Value: " << i << "\n"; 
    return i; 
} 

int main() 
{ 
    int i = 0; 
    Slot<int> someCallback = bind(fun_ptr(foo), i); 
    ++i; // added to show that late evaluation would be a non-trivial difference 
    int result = someCallback(); 
    assert(result == 0); 
    return 0; 
} 

Purtroppo, non sono riuscito a trovare alcun riferimento al legame/rebinding per quanto riguarda il C# delegati. Mi sto perdendo qualcosa? C'è un modo radicalmente diverso di farlo in C#?

risposta

13

In C# che fare qualcosa di simile:

class Program { 
    static Action Curry<T>(Action<T> action, T parameter) { 
     return() => action(parameter); 
    } 

    static void Foo(int i) { 
     Console.WriteLine("Value: {0}", i); 
    } 
    static void Main(string[] args) { 
     Action curried = Curry(Foo, 5); 
     curried(); 
    } 
} 

chiaramente il metodo Foo corrisponde al metodo Foo, basta con le chiamate appropriate Console.WriteLine invece di std::cout.

Successivamente, si dichiara un metodo Curry che accetta un Action<T> e restituisce un Action. In generale, un Action<T> è un delegato che accetta un singolo parametro di tipo T e restituisce void. In particolare, Foo è un Action<int> perché accetta un parametro di tipo int e restituisce void. Per quanto riguarda il tipo di ritorno di Curry, è dichiarato come Action. Un Action è un delegato che non ha parametri e restituisce void.

La definizione di Curry è piuttosto interessante. Stiamo definendo un'azione utilizzando un'espressione lambda che è una forma molto speciale di un delegato anonimo. Effettivamente

() => action(parameter) 

dice che il parametro void viene mappato action valutato a parameter.

Infine, nel Main stiamo dichiarando un'istanza di Action denominata curried che è il risultato dell'applicazione di Curry per Foo con il parametro 5. Questo ha lo stesso ruolo di bind(fun_ptr(foo), 5) nell'esempio C++.

Infine, invochiamo il delegato appena formato curried tramite la sintassi curried(). Questo è come someCallback() nell'esempio.

Il termine di fantasia per questo è currying.

Come esempio più interessante, si consideri il seguente:

class Program { 
    static Func<TArg, TResult> Curry<TArg, TResult>(
     Func<TArg, TArg, TResult> func, 
     TArg arg1 
    ) { 
     return arg => func(arg1, arg); 
    } 

    static int Add(int x, int y) { 
     return x + y; 
    } 

    static void Main(string[] args) { 
     Func<int, int> addFive = Curry<int, int>(Add, 5); 
     Console.WriteLine(addFive(7)); 
    } 
} 

Qui stiamo dichiarando un metodo Curry che accetta un delegato (Func<TArg, TArg, TResult> che accetta due parametri dello stesso tipo TArg e restituisce un valore di qualche altro digitare TResult e un parametro di tipo TArg e restituisce un delegato che accetta un singolo parametro di tipo TArg e restituisce un valore di tipo TResult (Func<TArg, TResult>).

Quindi, come test, dichiariamo un metodo Add che accetta due parametri di tipo int e restituisce un parametro di tipo int (a Func<int, int, int>). Quindi in Main viene creato un nuovo delegato denominato addFive che funziona come un metodo che ne aggiunge cinque al parametro di input. Così

Console.WriteLine(addFive(7)); 

stampe 12 sulla console.

+0

Grazie per la tua risposta dettagliata. Ho contrassegnato l'altro come risposta accettata perché è più conciso, anche se mancano alcuni dettagli importanti che il tuo ha incluso. – Catskul

+0

Sicuro. Spero solo che il colore extra aiuti. :-) – jason

+0

Risposta estremamente utile, questo. Introdotto un concetto brillante, curry, in un modo facile da capire. Aggiungerà sicuramente questo alla mia cassetta degli attrezzi mentale. –

4

provare il seguente

class Example { 
    static void foo(int i) { 
    Console.WriteLine(i); 
    } 
    public static void Main() { 
    Action someCallback =() => foo(5); 
    someCallback(); 
    } 
} 

O per qualcosa di ancora più vicino al C++ contatore parte

class Example { 
    static void foo(int i) { 
    Console.WriteLine(i); 
    } 
    static Action bind<T>(Action<T> action, T value) { 
    return() => action(value); 
    } 
    public static void Main() { 
    Action someCallback = bind(foo, 5); 
    someCallback(); 
    } 
} 

Spiegazione. Quello che sta succedendo qui è che sto creando un nuovo delegato per mezzo di un'espressione lambda. Il lambda è l'espressione che inizia con () =>. In questo caso crea un delegato che non accetta argomenti e non produce alcun valore. È compatibile con il tipo Action.

+0

Woah. Interessante. Cosa vedo qui? – Catskul

+2

Sta creando un nuovo delegato usando un'espressione lambda. Il delegato chiama "foo (5)". È come creare un nuovo metodo al volo per chiamare foo (5) per te, quindi assegnarlo al delegato di someCallback. –

+0

'L'azione ' è un delegato fornito dal framework. È una funzione che accetta un parametro (di tipo T) e restituisce nulla (vuoto). Assegna una funzione anonima a quel delegato (che ha chiamato 'someCallback'). I parents vuoti indicano che non richiede argomenti, l'espressione dopo '=>' è il corpo della funzione. – Sapph