2009-06-10 1 views
12

I Just non riesco a avvolgere la mia mente intorno a loro.I funzionamenti quando dovrei usarli che cosa è il loro uso previsto

Come ho capito, aggiunge in modo dinamico la logica a una classe. Le classi all'interno del quadro sono preparate per questo?

Perché dovrei semplicemente estendere la classe e aggiungervi la funzionalità nell'estensione. Sarei accessibile a livello globale e molto più facile da mantenere.

che ho letto ci sono 4 tipi funtore:

Comparer
Chiusura
predicato
Transformer

probabilmente dovremmo gestire ogni uno di loro.

p.s. c'è qualcosa di simile in VB?

Quindi posso affermare che penso che le espressioni lambda siano funtori. Questo chiarisce le cose per me un po ':) (hehe)

  • Le espressioni lambda sono funtori?
  • Le funzioni anonime sono funtori?

Ma ho fatto questa domanda perché mi sono imbattuto in un altro tipo di fucntors cioè questi:

delegate void FunctorDelegate(int value); 
class Addition { 
    FunctorDelegate _delegate; 

    public Addition AddDelegate(FunctorDelegate deleg) { 
     _delegate += deleg; 
     return this; 
    } 
    public int AddAllElements(IList< int> list) { 
     int runningTotal = 0; 
     foreach(int value in list) { 
      runningTotal += value; 
      _delegate(value); 
     } 
     return runningTotal; 
    } 
} 

e quindi chiamando con questo:

int runningTotal = new Addition() 
    .AddDelegate(new FunctorDelegate(
        delegate(int value) { 
         if ((value % 2) == 1) { 
          runningOddTotal += value; 
         } 
        })) 
    .AddDelegate(new FunctorDelegate(
        delegate(int value) { 
         if ((value % 2) == 0) { 
          runningEvenTotal += value; 
         } 
        })) 
    .AddAllElements(list); 

Quindi niente cose stile lambda di fantasia .

Ora ho questo esempio ma non è del tutto chiaro perché questa è una soluzione "buona".

I delegati (funtori) vengono utilizzati come espressioni lambda o metodi anonimi "nella maggior parte dei casi" solo lì come scorciatoia per il programmatore? Ci sono per quanto posso vedere solo in alcuni casi in cui sono in realtà la scelta preferita per un problema.

+0

(risposto al commento) –

+0

Sono propenso a dire che * non è * una buona soluzione; forse stavano cercando di mostrare * l'uso potenziale *, piuttosto che l'uso tipico. L'unica cosa "carina" (e sono generoso qui) su quel codice è che enumera solo una volta i dati - ma ci sono altre risposte a questo. Ad esempio, PushLINQ (MiscUtil) consente più aggregazioni su un feed di dati "solo una volta" con (IMO) molta più eleganza. –

+0

hehe questo codice era per mostrare il suo potenziale utilizzo ma ha fatto anche un brutto lavoro in questo :). L'ho postato per spiegare il "altro" tipo di funtore (methos anonimo) in cui mi sono imbattuto. Ma non era/non è chiaro dove nella mia vita quotidiana questa sarebbe diventata la "LA" soluzione. Fredrik ne ha parlato un po '. – albertjan

risposta

20

Credo che tu stia confondendo termini di lingue diverse. Sembra che tu stia usando "Functor" nel senso C++ o Java, ad es. see the wikipedia page. In C++, è un oggetto di una classe che sovraccarica l'operatore di chiamata di funzione, quindi può essere usato come una funzione ma con stato.

Questo è logicamente la stessa cosa di un delegato associato a un metodo di istanza in C# (o qualsiasi linguaggio .NET).

Ci sono tre modi per scrivere una cosa del genere. Innanzitutto, è possibile scrivere un metodo normale e quindi assegnare il nome del metodo a una variabile delegata.

void MyMethod() { Console.WriteLine("Hi!"); } 

void Foo() 
{ 
    Action a = MyMethod; 
    a(); 
} 

In secondo luogo, è possibile utilizzare la sintassi metodo anonimo, introdotta in C# 2.0:

void Foo() 
{ 
    Action a = delegate { Console.WriteLine("Hi!"); } 
    a(); 
} 

In terzo luogo, è possibile utilizzare la sintassi lambda, introdotto in C# 3.0:

void Foo() 
{ 
    Action a =() => Console.WriteLine("Hi!"); 
    a(); 
} 

Il vantaggio degli ultimi due è che il corpo del metodo può leggere e scrivere variabili locali nel metodo di contenimento.

Il vantaggio della sintassi lambda rispetto ai metodi anon è che è più succinto e digita inferenza sui parametri.

Update: Il vantaggio di anon-metodi (delegate parole chiave) oltre lambda è che è possibile omettere i parametri del tutto se non ne hanno bisogno:

// correct way using lambda 
button.Click += (sender, eventArgs) => MessageBox.Show("Clicked!"); 

// compile error - wrong number of arguments 
button.Click +=() => MessageBox.Show("Clicked!"); 

// anon method, omitting arguments, works fine 
button.Click += delegate { MessageBox.Show("Clicked!"); }; 

io conosco solo un situazione in cui questo vale la pena conoscere, che è in fase di inizializzazione di un evento in modo che non si controlla per null prima della cottura esso:

event EventHandler Birthday = delegate { }; 

evita un sacco di sciocchezze altrove.

Infine, si dice che ci sono quattro tipi di funtore. In effetti ci sono infiniti tipi di delegati, anche se alcuni autori potrebbero avere i loro preferiti e ovviamente ci saranno alcuni modelli comuni.Un Action o Command non accetta parametri e restituisce void e un predicato prende un'istanza di un certo tipo e restituisce true o false.

In C# 3.0, è possibile montare un delegato con un massimo di quattro parametri di qualsiasi tipo che ti piace:

Func<string, int, double> f; // takes a string and an in, returns a double 

Re: Aggiornamento Domanda

Lei chiede (credo) se ci sono molti casi d'uso per lambda. Ci sono più di quanto possa essere elencato!

Spesso li si vede nel mezzo di espressioni più grandi che operano su sequenze (elenchi calcolati al volo). Supponiamo che io ho una lista di persone, e voglio una lista di persone esattamente quaranta anni:

var exactlyForty = people.Where(person => person.Age == 40); 

Il metodo Where è un metodo di estensione sull'interfaccia IEnumerable<T>, dove T in questo caso è una sorta di Person classe .

Questo è noto in .NET come "Linq to Objects", ma noto altrove come pura programmazione funzionale su sequenze o flussi o elenchi "pigri" (tutti i nomi diversi per la stessa cosa).

+0

a destra. Ora stiamo arrivando da qualche parte. Quindi i functors (come nel nome) non sono realmente presenti in .net sono delegati. La concisione di lambda credo sia il gusto personale Non mi interessa digitare più per tenerlo leggibile. Ma il tipo di inferenza è sempre così forte ed è un vero pro per il sapore lambda. Aggiungo un po 'di più alla mia domanda. – albertjan

+1

Grazie per aver accettato, ho aggiunto un aggiornamento bonus sull'omissione di argomenti da delegare. –

+0

Grazie :) Penso che abbiamo fatto un ottimo lavoro per chiarire questo problema e creare un caso per lambda, delegati e funzioni anonime. Dove lavoro sono v. Paura di questa roba. Ma hey non puoi fermare il futuro giusto. :). – albertjan

7

In termini NET, io penso ciò che si sta descrivendo è il Delegate - ed esiste in tutti NET, non solo C#.

non sono sicuro che una "chiusura" sarebbe un "tipo" nella stessa era come un operatore di confronto/predicato/trasformatore, dal momento che in C# termini di chiusura è semplicemente un dettaglio di implementazione, ma può essere qualsiasi di quelli tre.

In .NET, i delegati sono utilizzati in due modi principali:

  • come il meccanismo di gestione degli eventi
  • di fornire la programmazione funzionale in stile

La prima è importante, ma suona come sei più interessato al secondo. In realtà, funzionano come le interfacce a metodo singolo ...considerare:

List<int> vals = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
List<int> evenVals = vals.FindAll(i => i % 2 == 0); // predicate 
List<string> valsAsStrings = vals.ConvertAll(i => i.ToString()); // transformer 
// sort descending 
vals.Sort((x, y) => y.CompareTo(x)); // comparer 

Una chiusura è più dove portare ulteriore scopo da fuori delegato in delegato:

int max = int.Parse(Console.ReadLine()); // perhaps 6 
List<int> limited = vals.FindAll(i => i <= max); 

qui il max viene catturato nella delegato come chiusura.

Re "Le classi all'interno del framework sono prepairate per questo?" - molti lo sono, e LINQ diventa un lungo modo per consentire questo ancora più ampio. LINQ fornisce metodi di estensione sopra (per esempio) tutti IEnumerable<T> - il che significa che le collezioni senza accesso delegato a base di loro acquisire gratuitamente:

int[] data = { 1,2,3,4,5,6,7,8,9 }; 
var oddData = data.Where(i => i % 2 == 1); 
var descending = data.OrderBy(i => -i); 
var asStrings = data.Select(i => i.ToString()); 

Qui i Where e OrderBy metodi sono metodi di estensione LINQ che prendono i delegati.

+0

Ciao grazie per la risposta. Sapevo che avevano qualcosa a che fare con i delegati.Non penso sia possibile fare alcuna seria programmazione in C# senza incorrere in delegati. Li ho usati principalmente per funzioni asincrone ed eventi. Ma ho fatto questa domanda perché mi sono imbattuto in un'altra forma di funtori. Non sapevo che il roba del predicato di linq fosse anche dei funtori. Aggiornerò la domanda sugli stili – albertjan

+0

Il materiale LINQ è ... complicato ;-p I metodi di estensione su IEnumerable utilizzano delegati, ma i metodi di estensione su IQueryable utilizzano gli alberi espressione, che sembrano identici al chiamante (presupponendo che utilizzino o istruzioni lambda o sintassi di query) ma che sono ** completamente, 100% ** diversi ;-p –

+0

Quindi solo una parte delle espressioni di linq lamba sono dei funtori. riiiight. :) Amo il ragazzo che ha pensato che fosse una buona idea: P Funziona, e io uso la roba tutti i giorni, ma come o perché funziona solo poche persone lo sanno davvero :) – albertjan

0

Sono sicuro che intendi espressioni Lambda. Quelle sono piccole funzioni che puoi scrivere molto velocemente e hanno la caratteristica "=>" Operatore. Queste sono una nuova funzionalità di C# 3.0.

Questo esempio sarà un classico Transformer; per utilizzarne uno abbiamo bisogno di un delegato per definire la firma per la funzione Lambda.

delegate int Transformer(int i); 

Ora Dichiarare un Lambda con questo delegato:

Transformer sqr = x => x * x; 

possiamo usarla come una normale funzione:

Console.WriteLine(sqr(3)); //9 

Questi sono utilizzati in LINQ interroga un sacco, ad esempio per Ordina (Comparatore), per cercare attraverso (Predicato).

Il libro "C# Pocket Reference" (a parte beign migliori in circolazione, a mio parere, ha un ottimo parte sul Lambda (ISBN 978-0-596-51922-3)

+0

Come una parte - con .NET 3.5 sarebbe più comune usare un Func piuttosto che definire un nuovo delegato di Transformer per fare la stessa cosa ... –

+0

Non sono solo le espressioni lambda. Come abbiamo stabilito (credo) solo una parte o le espressioni lambda sono in realtà funtori. – albertjan

1

interessante con la terminologia.; la mia interpretazione spontanea del termine "Functor" era che si riferiva a metodi anonimi, in modo che sarà il mio prendere su di esso

Questi sono alcuni dei miei usi tipici:..

confronti (di solito per l'ordinamento di una lista) :

List<int> ints = new List<int>(); 
ints.AddRange(new int[] { 9, 5, 7, 4, 3, 5, 3 }); 
ints.Sort(new Comparison<int>(delegate(int x, int y) 
    { 
     return x.CompareTo(y); 
    })); 
// yes I am aware the ints.Sort() would yield the same result, but hey, it's just 
// a conceptual code sample ;o) 

// and the shorter .NET 3.5 version: 
ints.Sort((x, y) => 
{ 
    return x.CompareTo(y); 
}); 

Userò questo approccio per i confronti, piuttosto che averlo nel proprio metodo e usare un delegato per quel metodo, nei casi in cui questo particolare tipo si verifica solo in un posto. Se è probabile che vorrò usare lo stesso confronto da qualche altra parte, potrà vivere secondo il proprio metodo riutilizzabile.

Un altro dei miei usi più comuni è nei test di unità, quando il test si basa su alcuni eventi che vengono generati.Ho trovato che, per essere essenziale quando i flussi di lavoro di unit test in Workflow Foundation:

WorkflowRuntime runtime = WorkflowHost.Runtime; 
WorkflowInstance instance = runtime.CreateWorkflow(typeof(CreateFile)); 
EventHandler<WorkflowEventArgs> WorkflowIdledHandler = delegate(object sender, WorkflowEventArgs e) 
{ 
    // get the ICreateFileService instance from the runtime 
    ISomeWorkflowService service = WorkflowHost.Runtime.GetService<ISomeWorkflowService>(); 

    // set the desired file content 
    service.DoSomeWork(instance.InstanceId, inputData); 
}; 
// attach event handler 
runtime.WorkflowIdled += WorkflowIdledHandler; 

instance.Start(); 
// perform the test, and then detach the event handler 
runtime.WorkflowIdled -= WorkflowIdledHandler; 

In questo caso è più semplice avere il gestore di eventi dichiarati come metodi anonimi in quanto utilizza la variabile instance che viene definito nel test di unità portata del metodo. Se avessi intstead deciso di impiantare il gestore di eventi come metodo separato, avrei anche bisogno di trovare un modo per raccogliere instance, probabilmente introducendo un membro di livello di classe, che non sembrerebbe un design perfetto in un test di unità classe.

ci sono più casi in cui ho trovato questo nel mio codice, ma di solito hanno uno o due cose in comune:

  • non ho alcun interesse in riferimento a quel pezzo di codice da qualsiasi altro luogo che in quella luogo particolare.
  • Il metodo ha bisogno di accedere ai dati che sarebbero fuori del campo di applicazione di un metodo normale
+0

Questo aspetto è più simile a quello che pensavo inizialmente fossero i funtori. Penso che le ultime due sentenze del tuo post siano davvero toccate da me, questo spiega perché dovrei usare functors/anonymousmethods/lambdaexpression. – albertjan

0

La vera risposta è che un funtore è un tipo di oggetto matematico ed è "reificato" da lingue diverse in modi diversi. Ad esempio, supponiamo di avere un oggetto "contenitore" che memorizza una serie di altri oggetti dello stesso tipo. (Ad esempio, un set o una matrice) Quindi, se la tua lingua aveva un metodo che ti permettesse di 'mappare' sul contenitore, in modo da poter chiamare un metodo su ogni oggetto nel contenitore, il contenitore sarebbe un functor .

In altre parole, un functor è un contenitore con un metodo che consente di passare un metodo alle sue cose contenute.

Ogni lingua ha il suo modo di farlo e talvolta confondono l'utilizzo. Ad esempio, C++ utilizza i puntatori di funzioni per rappresentare il "passaggio" di un metodo e chiama il puntatore a funzione un "functor". I delegati sono solo un modo per gestire i metodi che puoi passare. Stai usando la terminologia "erroneamente", proprio come fa il C++.

Haskell ha ragione. Si dichiara che un tipo implementa l'interfaccia del functor e quindi si ottiene il metodo di mappatura.

Le funzioni (come lambda) sono anche funzioni, ma può essere un po 'difficile pensare a una funzione come a un "contenitore". In breve, una funzione è un "contenitore" attorno ad un valore di ritorno, costruito in modo tale che il valore di ritorno (eventualmente) dipende dagli argomenti della funzione.