Por que Func <T, bool> em vez de Predicado <T>?

210

Esta é apenas uma pergunta de curiosidade que eu queria saber se alguém tinha uma boa resposta para:

Na Biblioteca de classes do .NET Framework, temos, por exemplo, esses dois métodos:

public static IQueryable<TSource> Where<TSource>(
    this IQueryable<TSource> source,
    Expression<Func<TSource, bool>> predicate
)

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate
)

Por que eles usam em Func<TSource, bool>vez de Predicate<TSource>? Parece que o Predicate<TSource>é utilizado apenas por List<T>e Array<T>, enquanto Func<TSource, bool>é utilizado por praticamente todos Queryablee Enumerablemétodos e métodos de extensão ... O que há com isso?

Svish
fonte
21
Sim, o uso inconsistente desses também me deixa louco.
187 George Mauer

Respostas:

170

Embora Predicatetenha sido introduzido ao mesmo tempo que List<T>e Array<T>, no .net 2.0, os diferentes Funce Actionvariantes vêm do .net 3.5.

Portanto, esses Funcpredicados são usados ​​principalmente para consistência nos operadores LINQ. A partir do .net 3.5, sobre o uso Func<T>e Action<T>a diretriz declara :

Use os novos tipos LINQ Func<>e, em Expression<>vez de delegados e predicados personalizados

Jb Evain
fonte
7
Cool, nunca vi essas guildelines antes =)
Svish
6
Aceitará isso como resposta, já que tem mais votos. e por Jon Skeet tem abundância de rep ...: p
Svish
4
Essa é uma orientação bizarra, como está escrito. Certamente deve indicar "Use os novos tipos de LINQ" Func <> "e" Action <> "[...]". Expressão <> é uma coisa completamente diferente.
Jon Skeet
3
Bem, na verdade você precisa colocar isso no contexto da diretriz, que trata de escrever um provedor LINQ. Então, sim, para operadores LINQ, a diretriz é usar Func <> e Expressão <> como parâmetros para os métodos de extensão. Concordo eu apreciaria uma orientação específica sobre o uso de Func e Ação
Jb Evain
Acho que o argumento de Skeet é que Expressão <> não é uma diretriz. É um recurso do compilador C # para reconhecer esse tipo e fazer algo diferente com ele.
21119 Daniel Earwicker
116

Eu já me perguntei isso antes. Eu gosto do Predicate<T>delegado - é legal e descritivo. No entanto, você precisa considerar as sobrecargas de Where:

Where<T>(IEnumerable<T>, Func<T, bool>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

Isso permite que você filtre com base no índice da entrada também. Isso é legal e consistente, ao passo que:

Where<T>(IEnumerable<T>, Predicate<T>)
Where<T>(IEnumerable<T>, Func<T, int, bool>)

não seria.

Jon Skeet
fonte
11
Predicado <int, bool> seria um pouco feio - geralmente um predicado (IME da ciência da computação) se baseia em um único valor. Poderia ser predicado <Par <T, int >> é claro, mas isso é ainda mais feio :)
Jon Skeet
tão verdade ... hehe. Não, acho que o Func é mais limpo.
Svish
2
Aceitou a outra resposta por causa das diretrizes. Gostaria de poder marcar mais de uma resposta! Seria bom se eu pudesse marcar um número de resposta como "muito notável" ou "Também ter um bom ponto que deve ser observado, além da resposta"
Svish
6
Hm, apenas viu que eu escrevi aquele primeiro comentário erroneamente: P minha sugestão foi, naturalmente, vai ser predicado <T, int> ...
Svish
4
@ JonSkeet Eu sei que esta pergunta é antiga e tudo, mas sabe o que é irônico? O nome do parâmetro no Wheremétodo de extensão é predicate. Heh = P.
Conrad Clark
32

Certamente, o motivo real para usar em Funcvez de um delegado específico é que o C # trata delegados declarados separadamente como tipos totalmente diferentes.

Embora Func<int, bool>e Predicate<int>ambos têm os tipos de argumento e de retorno idênticos, eles não são atribuição compatível. Portanto, se todas as bibliotecas declarassem seu próprio tipo de delegado para cada padrão de delegado, essas bibliotecas não seriam capazes de interoperar a menos que o usuário insira delegados "em ponte" para realizar conversões.

    // declare two delegate types, completely identical but different names:
    public delegate void ExceptionHandler1(Exception x);
    public delegate void ExceptionHandler2(Exception x);

    // a method that is compatible with either of them:
    public static void MyExceptionHandler(Exception x)
    {
        Console.WriteLine(x.Message);
    }

    static void Main(string[] args)
    {
        // can assign any method having the right pattern
        ExceptionHandler1 x1 = MyExceptionHandler; 

        // and yet cannot assign a delegate with identical declaration!
        ExceptionHandler2 x2 = x1; // error at compile time
    }

Ao incentivar todos a usar o Func, a Microsoft espera que isso alivie o problema de tipos de delegados incompatíveis. Os delegados de todos jogam bem juntos, porque eles serão correspondidos com base em seus tipos de parâmetro / retorno.

Não resolve todos os problemas, porque Func(e Action) não podem ter outou refparâmetros, mas esses são menos usados.

Atualização: nos comentários Svish diz:

Ainda assim, mudar um tipo de parâmetro de Func para Predicate e vice-versa não parece fazer nenhuma diferença? Pelo menos ainda compila sem problemas.

Sim, desde que o seu programa atribua apenas métodos aos delegados, como na primeira linha da minha Mainfunção. O compilador silenciosamente gera código para um novo objeto delegado que encaminha para o método. Portanto, na minha Mainfunção, eu poderia mudar x1para ser do tipo ExceptionHandler2sem causar problemas.

No entanto, na segunda linha, tento atribuir o primeiro delegado a outro delegado. Mesmo pensando que o segundo tipo de delegado tem exatamente o mesmo parâmetro e tipos de retorno, o compilador dá erro CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Talvez isso torne mais claro:

public static bool IsNegative(int x)
{
    return x < 0;
}

static void Main(string[] args)
{
    Predicate<int> p = IsNegative;
    Func<int, bool> f = IsNegative;

    p = f; // Not allowed
}

Meu método IsNegativeé uma coisa perfeitamente boa para atribuir às variáveis pe f, desde que eu o faça diretamente. Mas não posso atribuir uma dessas variáveis ​​à outra.

Daniel Earwicker
fonte
Ainda assim, mudar um tipo de parâmetro de Func <T, bool> para Predicate <T> e vice-versa, não parece fazer nenhuma diferença? Pelo menos ainda compila sem problemas.
Svish
Parece que a Microsoft está tentando desencorajar os desenvolvedores a pensar que são os mesmos. Eu quero saber porque?
precisa saber é o seguinte
1
Alternar o tipo de parâmetro faz diferença se a expressão que você está passando para ela tiver sido definida separadamente para a chamada do método, pois ela será digitada como um Func<T, bool>ou Predicate<T>ao invés de ter o tipo inferido pelo compilador.
Adam Ralph
30

O conselho (em 3.5 e acima) é usar o Action<...>e Func<...>- para o "por quê?" - uma vantagem é que "Predicate<T> " só tem sentido se você souber o que "predicado" significa - caso contrário, você precisa procurar no navegador de objetos (etc) para encontrar a assinatura.

Por outro lado, Func<T,bool>segue um padrão padrão; Eu posso dizer imediatamente que esta é uma função que recebe Te retorna umbool - não precisa entender nenhuma terminologia - apenas aplique meu teste de verdade.

Para "predicado", isso pode ter sido bom, mas agradeço a tentativa de padronização. Também permite muita paridade com os métodos relacionados nessa área.

Marc Gravell
fonte