Cos'è Func, come e quando viene utilizzato


115

Cos'è Func<>e a cosa serve?


4
È solo una scorciatoia per i delegati con una firma specifica. Per comprendere appieno le risposte di seguito è necessario comprendere i delegati ;-)
Theo Lenndorff

2
Nella risposta di @Oded si diceIf you have a function that needs to return different types, depending on the parameters, you can use a Func delegate, specifying the return type.
LCJ

Risposte:


76

Func<T>è un tipo di delegato predefinito per un metodo che restituisce un valore del tipo T.

In altre parole, puoi usare questo tipo per fare riferimento a un metodo che restituisce un valore di T. Per esempio

public static string GetMessage() { return "Hello world"; }

può essere referenziato in questo modo

Func<string> f = GetMessage;

Ma può anche rappresentare una funzione statica di un argomento =)
Ark-kun

2
@ Ark-kun no, non è corretto. La definizione di Func<T>is delegate TResult Func<out TResult>(). Nessun argomento. Func<T1, T2>sarebbe una funzione che accetta un argomento.
Brian Rasmussen

4
No, ho ragione. static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-kun

1
Questo è un metodo di estensione speciale.
Brian Rasmussen

L'unica cosa speciale è l' Extensionattributo che viene letto solo dai compilatori C # / VB.Net, non CLR. Fondamentalmente, i metodi di istanza (a differenza delle funzioni statiche) hanno un parametro 0th nascosto "this". Quindi, il metodo di istanza a 1 argomento è molto simile alla funzione statica a 2 argomenti. Quindi, abbiamo delegati che memorizzano l' oggetto di destinazione e il puntatore a funzione . I delegati possono memorizzare il primo argomento in target o non farlo.
Ark-kun

87

Consideralo come un segnaposto. Può essere molto utile quando si dispone di codice che segue un certo schema ma non è necessario che sia legato a nessuna funzionalità particolare.

Ad esempio, considera il Enumerable.Selectmetodo di estensione.

  • Lo schema è: per ogni elemento in una sequenza, seleziona un valore da quell'elemento (ad esempio, una proprietà) e crea una nuova sequenza composta da questi valori.
  • Il segnaposto è: una funzione di selezione che ottiene effettivamente i valori per la sequenza descritta sopra.

Questo metodo accetta una funzione Func<T, TResult>anziché qualsiasi funzione concreta. Ciò consente di utilizzarlo in qualsiasi contesto in cui si applica il modello sopra.

Quindi, ad esempio, diciamo che ho un List<Person>e voglio solo il nome di ogni persona nell'elenco. Posso farlo:

var names = people.Select(p => p.Name);

Oppure dì che voglio l' età di ogni persona:

var ages = people.Select(p => p.Age);

Immediatamente, puoi vedere come sono stato in grado di sfruttare lo stesso codice che rappresenta un pattern (con Select) con due diverse funzioni ( p => p.Namee p => p.Age).

L'alternativa sarebbe scrivere una versione diversa Selectogni volta che si desidera scansionare una sequenza per un diverso tipo di valore. Quindi, per ottenere lo stesso effetto di cui sopra, avrei bisogno di:

// Presumably, the code inside these two methods would look almost identical;
// the only difference would be the part that actually selects a value
// based on a Person.
var names = GetPersonNames(people);
var ages = GetPersonAges(people);

Con un delegato che funge da segnaposto, mi libero di dover scrivere lo stesso schema più e più volte in casi come questo.


66

Func<T1, T2, ..., Tn, Tr> rappresenta una funzione, che accetta argomenti (T1, T2, ..., Tn) e restituisce Tr.

Ad esempio, se hai una funzione:

double sqr(double x) { return x * x; }

Puoi salvarlo come una sorta di variabile di funzione:

Func<double, double> f1 = sqr;
Func<double, double> f2 = x => x * x;

E poi usa esattamente come useresti sqr:

f1(2);
Console.WriteLine(f2(f1(4)));

eccetera.

Ricorda però che è un delegato, per informazioni più avanzate fare riferimento alla documentazione.


1
Risposta eccellente, ma per compilare la parola chiave statica è necessaria
boctulus

16

Trovo Func<T>molto utile quando creo un componente che necessita di essere personalizzato "al volo".

Prendi questo esempio molto semplice: un PrintListToConsole<T>componente.

Un oggetto molto semplice che stampa questo elenco di oggetti sulla console. Vuoi consentire allo sviluppatore che lo utilizza di personalizzare l'output.

Ad esempio, vuoi lasciargli definire un particolare tipo di formato numerico e così via.

Senza Func

Per prima cosa, devi creare un'interfaccia per una classe che prende l'input e produce la stringa da stampare sulla console.

interface PrintListConsoleRender<T> {
  String Render(T input);
}

Quindi devi creare la classe PrintListToConsole<T>che prende l'interfaccia creata in precedenza e la usa su ogni elemento della lista.

class PrintListToConsole<T> {

    private PrintListConsoleRender<T> _renderer;

    public void SetRenderer(PrintListConsoleRender<T> r) {
        // this is the point where I can personalize the render mechanism
        _renderer = r;
    }

    public void PrintToConsole(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderer.Render(item));
        }
    }   
}

Lo sviluppatore che deve utilizzare il tuo componente deve:

  1. implementare l'interfaccia

  2. passare la vera classe al PrintListToConsole

    class MyRenderer : PrintListConsoleRender<int> {
        public String Render(int input) {
            return "Number: " + input;
        }
    }
    
    class Program {
        static void Main(string[] args) {
            var list = new List<int> { 1, 2, 3 };
            var printer = new PrintListToConsole<int>();
            printer.SetRenderer(new MyRenderer());
            printer.PrintToConsole(list);
            string result = Console.ReadLine();   
        }   
    }

Usare Func è molto più semplice

All'interno del componente si definisce un parametro di tipo Func<T,String>che rappresenta un'interfaccia di una funzione che accetta un parametro di input di tipo T e restituisce una stringa (l'output per la console)

class PrintListToConsole<T> {

    private Func<T, String> _renderFunc;

    public void SetRenderFunc(Func<T, String> r) {
        // this is the point where I can set the render mechanism
        _renderFunc = r;
    }

    public void Print(List<T> list) {
        foreach (var item in list) {
            Console.Write(_renderFunc(item));
        }
    }
}

Quando lo sviluppatore utilizza il tuo componente passa semplicemente al componente l'implementazione del Func<T, String>tipo, cioè una funzione che crea l'output per la console.

class Program {
    static void Main(string[] args) {
        var list = new List<int> { 1, 2, 3 }; // should be a list as the method signature expects
        var printer = new PrintListToConsole<int>();
        printer.SetRenderFunc((o) => "Number:" + o);
        printer.Print(list); 
        string result = Console.ReadLine();
    }
}

Func<T>consente di definire al volo un'interfaccia di metodo generico. Definisci il tipo di input e il tipo di output. Semplice e conciso.


2
Grazie per aver postato questo Marco. Mi ha davvero aiutato. Ho cercato di capire func per un po 'e anche di utilizzarlo attivamente nella mia programmazione. Questo esempio cancellerà il percorso. Ho dovuto aggiungere il metodo StampaFunc in quanto era stato omesso nel codice originale che ne impediva la visualizzazione.
Siwoku Adeola

1
Penso che ci sia una linea persa nel campione Func, dov'è la chiamata per la funzione di stampa o StampaFunc?
Bashar Abu Shamaa

11

Func<T1,R>e gli altri predefiniti generici Funcdelegati ( Func<T1,T2,R>, Func<T1,T2,T3,R>e altri) sono delegati generici che restituiscono il tipo dell'ultimo parametro generico.

Se si dispone di una funzione che deve restituire tipi diversi, a seconda dei parametri, è possibile utilizzare un Funcdelegato, specificando il tipo restituito.


7

È solo un delegato generico predefinito. Usandolo non è necessario dichiarare ogni delegato. C'è un altro delegato predefinito Action<T, T2...>, che è lo stesso ma restituisce void.


0

Forse non è troppo tardi per aggiungere alcune informazioni.

Somma:

Il Func è un delegato personalizzato definito nello spazio dei nomi di sistema che consente di puntare a un metodo con la stessa firma (come fanno i delegati), utilizzando da 0 a 16 parametri di input e che deve restituire qualcosa.

Nomenclatura e come2uso:

Func<input_1, input_2, ..., input1_6, output> funcDelegate = someMethod;

Definizione:

public delegate TResult Func<in T, out TResult>(T arg);

Dove viene utilizzato:

Viene utilizzato nelle espressioni lambda e nei metodi anonimi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.