2009-03-20 15 views
195

Ésta es sólo una cuestión curiosidad me preguntaba si alguien tenía una buena respuesta a:¿Por qué Func <T,bool> en lugar de Predicado <T>?

En la Biblioteca de clases de .NET Framework 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é utilizan Func<TSource, bool> en lugar de Predicate<TSource>? Parece que el Predicate<TSource> solo es usado por List<T> y Array<T>, mientras que Func<TSource, bool> es usado por casi todos los métodos Queryable y Enumerable y métodos de extensión ... ¿Qué pasa con eso?

+15

Ugh sí, el uso inconsistente de estos me vuelve loco también. –

Respuesta

155

Mientras Predicate se ha introducido al mismo tiempo que List<T> y Array<T>, en .NET 2.0, los diferentes Func y Action variantes provienen de .NET 3.5.

Por lo tanto, los predicados Func se utilizan principalmente para la coherencia en los operadores LINQ. A partir de .NET 3.5, sobre el uso de Func<T> y Action<T> la guideline states:

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

+7

Genial, nunca he visto esas guild antes = = – Svish

+4

Lo aceptaré como respuesta, ya que tiene la mayoría de los votos. y porque Jon Skeet tiene mucha representación ...: p – Svish

+4

Esa es una guía extraña como está escrita. Seguramente debería indicar "Use los nuevos tipos de LINQ" Func <> "y" Acción <> "[...]". La expresión <> es algo completamente diferente. –

106

me he preguntado esto antes. Me gusta el delegado Predicate<T>, 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 le permite filtrar en base al índice de la entrada también. Eso es bueno y consistente, mientras que:

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

no lo sería.

+4

¿Podría tener un predicado ? Pero veo que su punto =) – Svish

+11

Predicado sería algo feo: un predicado es usualmente (IME de la informática) predicado en un solo valor. Podría ser Predicate > por supuesto, pero eso es incluso más feo :) –

+0

tan cierto ... jeje. No, creo que el Func es más limpio. – Svish

26

El consejo (en 3.5 y superior) es utilizar Action<...> y Func<...> - para el "por qué?" - Una ventaja es que "Predicate<T>" solo tiene sentido si sabes lo que significa "predicado"; de lo contrario, necesitas ver el buscador de objetos (etc) para encontrar el signo.

Por el contrario Func<T,bool> sigue un patrón estándar; De inmediato puedo decir que esta es una función que toma un T y 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 aprecio el intento de estandarizar. También permite mucha paridad con los métodos relacionados en esa área.

+0

Buen punto también. Nos gusta estandarizar =) – Svish

28

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

Aunque Func<int, bool> y Predicate<int> tienen idénticos tipos de argumentos y de devolución, no son compatibles con la asignación. Por lo tanto, si cada biblioteca declarara su propio tipo de delegado para cada patrón de delegado, esas bibliotecas no podrían interactuar a menos que el usuario inserte delegados "de 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 animar a todos a utilizar Func, Microsoft espera que esto aliviará el problema de los tipos de delegado incompatibles. Los delegados de todos jugarán muy bien juntos, porque solo se combinarán en función de sus tipos de parámetros/devolución.

no resuelve todos los problemas, porque Func (y Action) no puede tener out o ref parámetros, pero los que se utilizan con menos frecuencia.

Actualización: en los comentarios Svish dice:

Sin embargo, el cambio de un tipo de parámetro de Func para predicado y atrás, no parece tener ningún diferencia? Al menos todavía 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 función Main. El compilador genera código silenciosamente a un nuevo objeto delegado que lo reenvía al método. Entonces, en mi función Main, pude cambiar x1 para que sea del tipo ExceptionHandler2 sin causar problemas.

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 los mismos tipos de devolución, el compilador genera el error CS0029: Cannot implicitly convert type 'ExceptionHandler1' to 'ExceptionHandler2'.

Tal vez esto hará que sea más 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 
} 

Mi método IsNegative es una buena cosa perfectamente para asignar a las variables p y f, siempre y cuando lo hago directamente. Pero luego no puedo asignar una de esas variables a la otra.

+0

Sin embargo, cambiar un tipo de parámetro de Func a Predicado y viceversa, no parece hacer ninguna diferencia? Al menos todavía compila sin ningún problema. – Svish

+0

Parece que MS está tratando de disuadir a los desarrolladores de pensar que son lo mismo. ¿Me pregunto porque? –

+0

Cambiar el tipo de parámetro hace una diferencia si la expresión que le está pasando se ha definido por separado a la llamada al método, ya que se escribirá como 'Func ' o 'Predicado ' en lugar de tener el tipo inferido por el compilador –

Cuestiones relacionadas