O que é Func, como e quando é usado

115

O que é Func<>e para que é usado?

Aprendendo
fonte
4
É apenas um atalho para delegados com uma assinatura específica. Para entender completamente as respostas abaixo, você precisará entender os delegados ;-)
Theo Lenndorff
2
Na resposta de @Oded dizIf 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

Respostas:

76

Func<T> é um tipo de delegado predefinido para um método que retorna algum valor do tipo T .

Em outras palavras, você pode usar esse tipo para fazer referência a um método que retorna algum valor de T. Por exemplo

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

pode ser referenciado assim

Func<string> f = GetMessage;
Brian Rasmussen
fonte
Mas também pode representar uma função estática de um argumento =)
Ark-kun
2
@ Ark-kun não, isso não é correto. A definição de Func<T>é delegate TResult Func<out TResult>(). Sem argumentos. Func<T1, T2>seria uma função que leva um argumento.
Brian Rasmussen
4
Não, estou certo. static int OneArgFunc(this string i) { return 42; } Func<int> f = "foo".OneArgFunc;. =)
Ark-kun
1
Esse é um método de extensão especial.
Brian Rasmussen
A única coisa especial sobre isso é o Extensionatributo que só é lido pelos compiladores C # / VB.Net, não CLR. Basicamente, os métodos de instância (ao contrário das funções estáticas) têm um 0º parâmetro oculto "este". Portanto, o método de instância de 1 argumento é muito semelhante à função estática de 2 argumentos. Então, temos delegados que armazenam o objeto de destino e o ponteiro de função . Os delegados podem armazenar o primeiro argumento no destino ou não fazer isso.
Ark-kun
87

Pense nisso como um espaço reservado. Pode ser muito útil quando você tem um código que segue um certo padrão, mas não precisa estar vinculado a nenhuma funcionalidade específica.

Por exemplo, considere o Enumerable.Selectmétodo de extensão.

  • O padrão é: para cada item em uma sequência, selecione algum valor desse item (por exemplo, uma propriedade) e crie uma nova sequência consistindo desses valores.
  • O espaço reservado é: alguma função de seletor que realmente obtém os valores para a sequência descrita acima.

Este método assume uma Func<T, TResult>função concreta em vez de qualquer. Isso permite que seja usado em qualquer contexto onde o padrão acima se aplique.

Por exemplo, digamos que tenho um List<Person>e quero apenas o nome de todas as pessoas da lista. Eu posso fazer isso:

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

Ou diga que eu quero o idade de cada pessoa:

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

De imediato, você pode ver como consegui alavancar o mesmo código que representa um padrão (com Select) com dois funções diferentes ( p => p.Nameep => p.Age ).

A alternativa seria escrever uma versão diferente de Selectcada vez que você quisesse varrer uma sequência para um tipo diferente de valor. Portanto, para obter o mesmo efeito acima, eu precisaria:

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

Com um delegado atuando como espaço reservado, eu me liberto de ter que escrever o mesmo padrão repetidamente em casos como este.

Dan Tao
fonte
66

Func<T1, T2, ..., Tn, Tr> representa uma função, que recebe argumentos (T1, T2, ..., Tn) e retorna Tr.

Por exemplo, se você tem uma função:

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

Você pode salvá-lo como algum tipo de variável de função:

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

E então use exatamente como usaria o sqr:

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

etc.

Porém, lembre-se de que é um delegado; para informações mais avançadas, consulte a documentação.

Grozz
fonte
1
Excelente resposta, mas para compilar a palavra-chave estática é necessária
boctulus
16

Acho Func<T>muito útil quando crio um componente que precisa ser personalizado "na hora".

Veja este exemplo muito simples: um PrintListToConsole<T>componente.

Um objeto muito simples que imprime essa lista de objetos no console. Você deseja permitir que o desenvolvedor que o utiliza personalize a saída.

Por exemplo, você deseja permitir que ele defina um tipo específico de formato de número e assim por diante.

Sem Func

Primeiro, você deve criar uma interface para uma classe que recebe a entrada e produz a string para imprimir no console.

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

Em seguida, você deve criar a classe PrintListToConsole<T>que pega a interface criada anteriormente e a usa sobre cada elemento da 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));
        }
    }   
}

O desenvolvedor que precisa usar seu componente deve:

  1. implementar a interface

  2. passe a aula real para o 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();   
        }   
    }

Usar Func é muito mais simples

Dentro do componente, você define um parâmetro do tipo Func<T,String>que representa uma interface de uma função que recebe um parâmetro de entrada do tipo T e retorna uma string (a saída para o 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 o desenvolvedor usa seu componente, ele simplesmente passa para o componente a implementação do Func<T, String>tipo, que é uma função que cria a saída para o 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>permite definir uma interface de método genérico em tempo real. Você define qual é o tipo de entrada e qual é o tipo de saída. Simples e conciso.

Marco Staffoli
fonte
2
Obrigado por postar este Marco. Isso realmente me ajudou. Eu tenho tentado entender o func por um tempo e também usá-lo ativamente na minha programação. Este exemplo limpará o caminho. Tive que adicionar o método StampaFunc, pois ele foi deixado de fora no código original, o que impediu sua exibição.
Siwoku Adeola
1
Acho que falta uma linha no exemplo Func, onde está a chamada para a função de impressão ou StampaFunc?
Bashar Abu Shamaa
11

Func<T1,R>e os outros predefinidos genéricos Funcdelegados ( Func<T1,T2,R>, Func<T1,T2,T3,R>e outros) são representantes genéricos que retornam o tipo do último parâmetro genérico.

Se você tem uma função que precisa retornar diferentes tipos, dependendo dos parâmetros, você pode usar um Funcdelegado, especificando o tipo de retorno.

Oded
fonte
7

É apenas um delegado genérico predefinido. Usando-o, você não precisa declarar todos os delegados. Existe outro delegado predefinido Action<T, T2...>, que é o mesmo, mas retorna vazio.

Stefan Steinegger
fonte
0

Talvez não seja tarde demais para acrescentar algumas informações.

Soma:

O Func é um delegado personalizado definido no namespace System que permite apontar para um método com a mesma assinatura (como fazem os delegados), usando 0 a 16 parâmetros de entrada e que deve retornar algo.

Nomenclatura e como usar:

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

Definição:

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

Onde é usado:

É usado em expressões lambda e métodos anônimos.

overRideKode
fonte