¿Por qué Func <T, bool> en lugar de Predicate <T>?

211

Esta es solo una pregunta de curiosidad que me preguntaba si alguien tenía una buena respuesta para:

En .NET Framework Class Library tenemos, por ejemplo, estos dos 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 qué usan en Func<TSource, bool>lugar de Predicate<TSource>? Parece que la Predicate<TSource>única es utilizado por List<T>y Array<T>, mientras Func<TSource, bool>es utilizado por casi todo Queryabley Enumerablemétodos y métodos de extensión ... ¿qué pasa con eso?

Svish
fuente
21
Ugh sí, el uso inconsistente de estos también me vuelve loco.
George Mauer

Respuestas:

170

Si bien Predicatese ha introducido al mismo tiempo que List<T>y Array<T>, en .net 2.0, las diferentes Funcy Actionvariantes provienen de .net 3.5.

Por lo tanto, esos Funcpredicados se usan principalmente para mantener la coherencia en los operadores LINQ. A partir de .net 3.5, sobre el uso Func<T>y Action<T>los estados de la guía :

Utilice los nuevos tipos LINQ Func<>y en Expression<>lugar de delegados y predicados personalizados

Jb Evain
fuente
77
Genial, nunca he visto esos guildelines antes =)
Svish
66
Aceptará esto como respuesta, ya que tiene la mayoría de los votos. y porque Jon Skeet tiene muchas repeticiones ...: p
Svish
44
Esa es una directriz extraña como está escrita. Seguramente debería decir "Utilice los nuevos tipos LINQ" Func <> "y" Acción <> "[...]". La expresión <> es una cosa completamente diferente.
Jon Skeet
3
Bueno, en realidad tienes que colocar eso en el contexto de la guía, que se trata de escribir un proveedor LINQ. Entonces, sí, para los operadores LINQ, la guía es usar Func <> y Expression <> como parámetros para los métodos de extensión. Estoy de acuerdo en que agradecería una guía separada sobre el uso de Func y Action
Jb Evain
Creo que el punto de Skeet es que la Expresión <> no es una guía. Es una función del compilador de C # para reconocer ese tipo y hacer algo diferente con él.
Daniel Earwicker
116

Me he preguntado esto antes. Me gusta el Predicate<T>delegado, es agradable y descriptivo. Sin embargo, debe tener en cuenta las sobrecargas de Where:

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

Eso también le permite filtrar según el índice de la entrada. Eso es agradable y consistente, mientras que:

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

no lo sería

Jon Skeet
fuente
11
El predicado <int, bool> sería algo feo: un predicado generalmente (IME de la informática) se basa en un solo valor. Podría ser Predicate <Pair <T, int >> por supuesto, pero eso es aún más feo :)
Jon Skeet
tan cierto ... jeje. No, supongo que el Func es más limpio.
Svish
2
Aceptó la otra respuesta debido a las pautas. ¡Ojalá pudiera marcar más de una respuesta! Sería bueno si pudiera marcar un número de respuestas como "Muy notable" o "También tener un buen punto que debe tenerse en cuenta además de la respuesta"
Svish
66
Hm, acabo de ver que escribí ese primer comentario erróneamente: P, por supuesto, mi sugerencia iba a ser Predicate <T, int> ...
Svish
44
@ JonSkeet Sé que esta pregunta es antigua y todo eso, pero ¿sabes qué es irónico? El nombre del parámetro en el Wheremétodo de extensión es predicate. Heh = P.
Conrad Clark el
32

Seguramente la razón real para usar en Funclugar de un delegado específico es que C # trata a los delegados declarados por separado como tipos totalmente diferentes.

A pesar de que Func<int, bool>y Predicate<int>ambos tienen idénticos tipos de argumentos y de retorno, no son una asignación compatible. Entonces, si cada biblioteca declara su propio tipo de delegado para cada patrón de delegado, esas bibliotecas no podrán interactuar a menos que el usuario inserte delegados "puente" para realizar conversiones.

    // 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
    }

Al alentar a todos a usar Func, Microsoft espera que esto aliviará el problema de los tipos de delegados incompatibles. Los delegados de todos jugarán muy bien juntos, porque solo se emparejarán según sus parámetros / tipos de retorno.

No resuelve todos los problemas, porque Func(y Action) no puede tener outo refparámetros, pero esos son menos utilizados.

Actualización: en los comentarios Svish dice:

Aún así, cambiar un tipo de parámetro de Func a Predicate y viceversa, ¿no parece haber ninguna diferencia? Al menos todavía se compila sin ningún problema.

Sí, siempre que su programa solo asigne métodos a los delegados, como en la primera línea de mi Mainfunción. El compilador genera silenciosamente código para crear un nuevo objeto delegado que reenvía el método. Entonces, en mi Mainfunción, podría cambiar x1para ser de tipo ExceptionHandler2sin causar un problema.

Sin embargo, en la segunda línea trato de asignar el primer delegado a otro delegado. Incluso si el segundo tipo de delegado tiene exactamente el mismo parámetro y tipos de retorno, el compilador da error CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Quizás esto lo aclare:

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
}

Mi método IsNegativees perfectamente bueno para asignar a las variables py f, siempre que lo haga directamente. Pero entonces no puedo asignar una de esas variables a la otra.

Daniel Earwicker
fuente
Aún así, cambiar un tipo de parámetro de Func <T, bool> a Predicate <T> y viceversa, ¿no parece haber ninguna diferencia? Al menos todavía se compila sin ningún problema.
Svish
Parece que MS está tratando de disuadir a los desarrolladores de pensar que son lo mismo. ¿Me pregunto porque?
Matt Kocaj
1
Cambiar el tipo de parámetro hace una diferencia si la expresión que le está pasando se ha definido por separado para la llamada al método, ya que se escribirá como Func<T, bool>o en Predicate<T>lugar de tener el tipo inferido por el compilador.
Adam Ralph
30

El consejo (en 3.5 y superior) es usar Action<...>y Func<...>- para el "¿por qué?" - una ventaja es que " Predicate<T>" solo tiene sentido si sabe lo que significa "predicado"; de lo contrario, debe buscar en el buscador de objetos (etc.) para encontrar el firmante.

Por el contrario, Func<T,bool>sigue un patrón estándar; Puedo decir de inmediato que esta es una función que toma Ty devuelve un bool- no es necesario que entienda ninguna terminología - solo aplique mi prueba de verdad.

Para "predicado" esto podría haber estado bien, pero agradezco el intento de estandarizar. También permite mucha paridad con los métodos relacionados en esa área.

Marc Gravell
fuente