Was ist Func, wie und wann wird es verwendet?

115

Was ist Func<>und wofür wird es verwendet?

Lernen
quelle
4
Dies ist nur eine Verknüpfung für Delegierte mit einer bestimmten Signatur. Um die Antworten unten vollständig zu verstehen, müssen Sie die Delegierten verstehen ;-)
Theo Lenndorff
2
In der Antwort von @Oded heißt esIf 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

Antworten:

76

Func<T>ist ein vordefinierter Delegatentyp für eine Methode, die einen Wert des Typs zurückgibt T.

Mit anderen Worten, Sie können diesen Typ verwenden, um auf eine Methode zu verweisen, die einen Wert von zurückgibt T. Z.B

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

kann so referenziert werden

Func<string> f = GetMessage;
Brian Rasmussen
quelle
Es kann aber auch eine statische Ein-Argument-Funktion darstellen =)
Ark-kun
2
@ Ark-Kun Nein, das ist nicht richtig. Die Definition von Func<T>ist delegate TResult Func<out TResult>(). Keine Argumente. Func<T1, T2>wäre eine Funktion, die ein Argument akzeptiert.
Brian Rasmussen
4
Nein, ich habe recht. static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-Kun
1
Das ist eine spezielle Erweiterungsmethode.
Brian Rasmussen
Das einzig Besondere daran ist das ExtensionAttribut, das nur von den C # / VB.Net-Compilern gelesen wird, nicht von CLR. Grundsätzlich haben Instanzmethoden (im Gegensatz zu statischen Funktionen) einen versteckten 0. "this" -Parameter. Die Instanzmethode mit 1 Argument ist der statischen Funktion mit 2 Argumenten sehr ähnlich. Dann haben wir Delegaten, die das Zielobjekt und den Funktionszeiger speichern . Delegierte können das erste Argument im Ziel speichern oder dies nicht tun.
Ark-Kun
86

Betrachten Sie es als Platzhalter. Dies kann sehr nützlich sein, wenn Sie Code haben, der einem bestimmten Muster folgt, aber nicht an eine bestimmte Funktionalität gebunden sein muss.

Betrachten Sie beispielsweise die Enumerable.SelectErweiterungsmethode.

  • Das Muster lautet: Wählen Sie für jedes Element in einer Sequenz einen Wert aus diesem Element aus (z. B. eine Eigenschaft) und erstellen Sie eine neue Sequenz, die aus diesen Werten besteht.
  • Der Platzhalter ist: eine Auswahlfunktion, die tatsächlich die Werte für die oben beschriebene Sequenz erhält.

Diese Methode übernimmt eine Func<T, TResult>anstelle einer konkreten Funktion. Dies ermöglicht die Verwendung in jedem Kontext, in dem das obige Muster gilt.

Nehmen wir zum Beispiel an, ich habe eine List<Person>und möchte nur den Namen jeder Person in der Liste. Ich kann dies tun:

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

Oder sagen Sie, ich möchte das Alter jeder Person:

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

Sie können sofort sehen, wie ich denselben Code nutzen konnte, der ein Muster (mit Select) mit zwei verschiedenen Funktionen ( p => p.Nameund p => p.Age) darstellt.

Die Alternative wäre, Selectjedes Mal, wenn Sie eine Sequenz nach einem anderen Wert durchsuchen möchten, eine andere Version zu schreiben . Um den gleichen Effekt wie oben zu erzielen, würde ich Folgendes benötigen:

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

Mit einem Delegierten als Platzhalter muss ich in solchen Fällen nicht immer wieder dasselbe Muster aufschreiben.

Dan Tao
quelle
66

Func<T1, T2, ..., Tn, Tr> stellt eine Funktion dar, die (T1, T2, ..., Tn) Argumente akzeptiert und Tr zurückgibt.

Zum Beispiel, wenn Sie eine Funktion haben:

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

Sie könnten es als eine Art Funktionsvariable speichern:

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

Und dann genau so verwenden, wie Sie sqr verwenden würden:

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

etc.

Denken Sie jedoch daran, dass es sich um einen Delegaten handelt. Weitere Informationen finden Sie in der Dokumentation.

Grozz
quelle
1
Hervorragende Antwort, aber zum Kompilieren von Schlüsselwörtern ist statisch erforderlich
Boctulus
16

Ich finde es Func<T>sehr nützlich, wenn ich eine Komponente erstelle, die "on the fly" personalisiert werden muss.

Nehmen Sie dieses sehr einfache Beispiel: eine PrintListToConsole<T>Komponente.

Ein sehr einfaches Objekt, das diese Liste von Objekten auf der Konsole druckt. Sie möchten, dass der Entwickler, der es verwendet, die Ausgabe personalisiert.

Sie möchten ihn beispielsweise eine bestimmte Art von Zahlenformat definieren lassen und so weiter.

Ohne Func

Zuerst müssen Sie eine Schnittstelle für eine Klasse erstellen, die die Eingabe übernimmt und die Zeichenfolge erzeugt, die auf der Konsole gedruckt werden soll.

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

Anschließend müssen Sie die Klasse erstellen PrintListToConsole<T>, die die zuvor erstellte Schnittstelle für jedes Element der Liste verwendet.

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

Der Entwickler, der Ihre Komponente verwenden muss, muss:

  1. Implementieren Sie die Schnittstelle

  2. Übergeben Sie die echte Klasse an die 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();   
        }   
    }

Mit Func ist es viel einfacher

Innerhalb der Komponente definieren Sie einen Parameter vom Typ Func<T,String>, der eine Schnittstelle einer Funktion darstellt , die einen Eingabeparameter vom Typ T verwendet und eine Zeichenfolge zurückgibt (die Ausgabe für die Konsole).

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

Wenn der Entwickler Ihre Komponente verwendet, übergibt er einfach die Implementierung des Func<T, String>Typs an die Komponente. Dies ist eine Funktion, die die Ausgabe für die Konsole erstellt.

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>Mit dieser Option können Sie im laufenden Betrieb eine generische Methodenschnittstelle definieren. Sie definieren, welcher Typ die Eingabe ist und welcher Typ die Ausgabe ist. Einfach und prägnant.

Marco Staffoli
quelle
2
Danke, dass du diesen Marco gepostet hast. Es hat mir wirklich geholfen. Ich habe eine Weile versucht, func zu verstehen und es auch aktiv in meiner Programmierung zu verwenden. In diesem Beispiel wird der Pfad gelöscht. Ich musste die StampaFunc-Methode hinzufügen, da diese im ursprünglichen Code weggelassen wurde, wodurch die Anzeige verhindert wurde.
Siwoku Adeola
1
Ich denke, im Func-Beispiel fehlt eine Zeile. Wo ist der Aufruf für die Druckfunktion oder StampaFunc?
Bashar Abu Shamaa
11

Func<T1,R>und die anderen vordefinierte generischen FuncTeilnehmer ( Func<T1,T2,R>, Func<T1,T2,T3,R>und anderen) sind generische Teilnehmer, die die Art des letzten generischen Parameters zurück.

Wenn Sie eine Funktion haben, die abhängig von den Parametern unterschiedliche Typen zurückgeben muss, können Sie einen FuncDelegaten verwenden, der den Rückgabetyp angibt.

Oded
quelle
7

Es ist nur ein vordefinierter generischer Delegat. Damit müssen Sie nicht jeden Delegaten deklarieren. Es gibt einen anderen vordefinierten Delegaten, Action<T, T2...>der derselbe ist, aber ungültig zurückgibt.

Stefan Steinegger
quelle
0

Vielleicht ist es nicht zu spät, um Informationen hinzuzufügen.

Summe:

Der Func ist ein benutzerdefinierter Delegat, der im System-Namespace definiert ist und es Ihnen ermöglicht, mit 0 bis 16 Eingabeparametern auf eine Methode mit derselben Signatur zu verweisen (wie dies bei Delegaten der Fall ist), die etwas zurückgeben muss.

Nomenklatur & how2use:

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

Definition:

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

Wo es verwendet wird:

Es wird in Lambda-Ausdrücken und anonymen Methoden verwendet.

overRideKode
quelle