Co to jest Func, jak i kiedy jest używane

115

Co to jest Func<>i do czego służy?

uczenie się
źródło
4
To tylko skrót dla delegatów z określonym podpisem. Aby w pełni zrozumieć poniższe odpowiedzi, musisz zrozumieć delegatów ;-)
Theo Lenndorff
2
W odpowiedzi @Oded jest napisaneIf 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

Odpowiedzi:

76

Func<T> jest predefiniowanym typem delegata dla metody, która zwraca pewną wartość typu T .

Innymi słowy, możesz użyć tego typu, aby odwołać się do metody, która zwraca pewną wartość T. Na przykład

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

mogą być określane w ten sposób

Func<string> f = GetMessage;
Brian Rasmussen
źródło
Ale może również reprezentować statyczną jednoargumentową funkcję =)
Ark-kun
2
@ Ark-kun nie, to nie jest poprawne. Definicja Func<T>jest delegate TResult Func<out TResult>(). Żadnych argumentów.Func<T1, T2>byłaby funkcją, która przyjmuje jeden argument.
Brian Rasmussen
4
Nie, mam rację. static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-kun
1
To wyjątkowa metoda rozszerzania.
Brian Rasmussen
Jedyną specjalną rzeczą jest Extensionatrybut, który jest odczytywany tylko przez kompilatory C # / VB.Net, a nie CLR. Zasadniczo metody instancji (w przeciwieństwie do funkcji statycznych) mają ukryty zerowy parametr „ten”. Zatem metoda instancji z 1 argumentem jest bardzo podobna do funkcji statycznej z 2 argumentami. Następnie mamy delegatów, którzy przechowują obiekt docelowy i wskaźnik funkcji . Delegaci mogą przechowywać pierwszy argument w miejscu docelowym lub tego nie robić.
Ark-kun,
87

Potraktuj to jako symbol zastępczy. Może to być całkiem przydatne, gdy masz kod, który jest zgodny z określonym wzorcem, ale nie musi być powiązany z żadną konkretną funkcjonalnością.

Weźmy na przykład pod uwagę Enumerable.Selectmetodę rozszerzenia.

  • wzór jest: za każdy element w sekwencji, wybierz jakąś wartość z tej pozycji (na przykład nieruchomości) i utworzyć nową sekwencję składającą się z tymi wartościami.
  • Symbol zastępczy to: jakaś funkcja selektora, która w rzeczywistości pobiera wartości dla sekwencji opisanej powyżej.

Ta metoda przyjmuje Func<T, TResult>zamiast dowolnej konkretnej funkcji. Pozwala to na użycie go w dowolnym kontekście, w którym ma zastosowanie powyższy wzorzec.

Na przykład, powiedzmy, że mam plik List<Person> i chcę tylko nazwiska każdej osoby na liście. Mogę to zrobić:

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

Albo powiedz, że chcę wiek każdej osoby:

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

Od razu widać, jak udało mi się wykorzystać ten sam kod reprezentujący wzorzec (z Select) z dwiema różnymi funkcjami ( p => p.Nameip => p.Age ).

Alternatywą byłoby zapisanie innej wersji za Selectkażdym razem, gdy chciałbyś przeskanować sekwencję pod kątem innego rodzaju wartości. Aby osiągnąć taki sam efekt jak powyżej, potrzebowałbym:

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

Dzięki delegatowi pełniącemu rolę elementu zastępczego, uwalniam się od konieczności ciągłego pisania tego samego wzoru w takich przypadkach.

Dan Tao
źródło
66

Func<T1, T2, ..., Tn, Tr> reprezentuje funkcję, która przyjmuje (T1, T2, ..., Tn) argumenty i zwraca Tr.

Na przykład, jeśli masz funkcję:

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

Możesz zapisać to jako rodzaj zmiennej funkcji:

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

Następnie użyj dokładnie tak, jak używałbyś sqr:

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

itp.

Pamiętaj jednak, że jest to delegat, więcej szczegółowych informacji znajdziesz w dokumentacji.

Grozz
źródło
1
Doskonała odpowiedź, ale do skompilowania słowa kluczowego static jest konieczna
boctulus
16

znajduję Func<T> bardzo przydatne podczas tworzenia komponentu, który wymaga personalizacji „w locie”.

Weźmy ten bardzo prosty przykład: a PrintListToConsole<T> komponent.

Bardzo prosty obiekt, który drukuje listę obiektów na konsoli. Chcesz, aby deweloper, który go używa, spersonalizował dane wyjściowe.

Na przykład chcesz pozwolić mu zdefiniować określony typ formatu liczb i tak dalej.

Bez Func

Najpierw musisz utworzyć interfejs dla klasy, która pobiera dane wejściowe i tworzy ciąg do wydrukowania na konsoli.

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

Następnie musisz utworzyć klasę, PrintListToConsole<T>która przejmuje poprzednio utworzony interfejs i używa go do każdego elementu listy.

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

Deweloper, który musi użyć Twojego komponentu, musi:

  1. zaimplementować interfejs

  2. przekazać prawdziwą klasę do 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();   
        }   
    }

Korzystanie z Func jest znacznie prostsze

Wewnątrz komponentu definiujesz parametr typu, Func<T,String>który reprezentuje interfejs funkcji, która przyjmuje parametr wejściowy typu T i zwraca ciąg (dane wyjściowe dla konsoli)

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

Gdy programista używa twojego komponentu, po prostu przekazuje do komponentu implementację Func<T, String>typu, czyli funkcji, która tworzy dane wyjściowe dla konsoli.

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>pozwala zdefiniować ogólny interfejs metody w locie. Definiujesz, jakiego typu jest wejście i jaki jest typ wyjścia. Proste i zwięzłe.

Marco Staffoli
źródło
2
Dzięki za wysłanie tego Marco. Naprawdę mi pomogło. Od jakiegoś czasu próbuję zrozumieć func i aktywnie używać go w programowaniu. Ten przykład oczyści ścieżkę. Musiałem dodać metodę StampaFunc, ponieważ została ona pominięta w oryginalnym kodzie, co uniemożliwiło jej wyświetlenie.
Siwoku Adeola
1
Myślę, że brakuje linii w próbce Func. Gdzie jest wywołanie funkcji drukowania lub StampaFunc?
Bashar Abu Shamaa
11

Func<T1,R>i inne predefiniowane generyczne Funcdelegaci ( Func<T1,T2,R>, Func<T1,T2,T3,R>i inne) są generyczne delegaci które zwracają typ ostatniego parametru rodzajowego.

Jeśli masz funkcję, która musi zwracać różne typy, w zależności od parametrów, możesz użyć Funcdelegata, określając zwracany typ.

Oded
źródło
7

Jest to po prostu wstępnie zdefiniowany delegat ogólny. Używając go, nie musisz deklarować każdego delegata. Istnieje inny predefiniowany delegat, Action<T, T2...>który jest taki sam, ale zwraca void.

Stefana Steineggera
źródło
0

Może nie jest za późno, aby dodać informacje.

Suma:

Func to niestandardowy delegat zdefiniowany w przestrzeni nazw System, który umożliwia wskazanie metody z tym samym podpisem (co robią delegaci), używając od 0 do 16 parametrów wejściowych i która musi coś zwrócić.

Nazewnictwo i sposób użycia:

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

Definicja:

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

Gdzie jest używany:

Jest używany w wyrażeniach lambda i metodach anonimowych.

overRideKode
źródło