2011-06-28 22 views
14

Este es un ejemplo sencillo de dos métodos de extensión sobrecargadelegados opcionales en C#

public static class Extended 
{ 
    public static IEnumerable<int> Even(this List<int> numbers) 
    { 
     return numbers.Where(num=> num % 2 == 0); 
    } 

    public static IEnumerable<int> Even(this List<int> numbers, Predicate<int> predicate) 
    { 
     return numbers.Where(num=> num % 2 == 0 && predicate(num)); 
    } 
} 

Me gustaría ser capaz de fusionar en uno, mediante el establecimiento de un delegado a ser opcional:

public static class Extended 
{ 
    public static IEnumerable<int> Even(this List<int> numbers, Predicate<in> predicate = alwaysTrue) 
    { 
     return numbers.Where(num=> num % 2 == 0 && predicate(num)); 
    } 

    public static bool alwaysTrue(int a) { return true; } 
} 

sin embargo, el compilador genera un error:

Default parameter value for 'predicate' must be a compile-time constant

no veo cómo mi función alwaysTrue no es constante, pero bueno, KNO compilador ws better :)

¿Hay alguna manera de hacer que el parámetro de delegado sea opcional?

+0

Muy interesante 1 –

Respuesta

19

No es constante porque ha creado un delegado de un grupo de métodos ... eso no es una constante de tiempo de compilación en lo que respecta al lenguaje C#.

Si no le importa abusar del significado de null poco que puede usar:

private static readonly Predicate<int> AlwaysTrue = ignored => true; 

public static List<int> Even(this List<int> numbers, 
          Predicate<int> predicate = null) 
{ 
    predicate = predicate ?? AlwaysTrue; 
    return numbers.Where(num=> num % 2 == 0 && predicate(num)); 
} 

(Usted podría aún ganar AlwaysTrue un método y utiliza una conversión de grupo Método, pero el enfoque anterior es muy poco más eficiente mediante la creación de la instancia delegado sólo una vez.)

+0

veces me gustaría no habría múltiples acepta el SO. Todas las respuestas son correctas, sin embargo, la respuesta de Jon me parece más rentable porque me hizo ir y leer sobre grupos de métodos para aprender algo que no sabía. –

+0

¿Por qué dice 'AlwaysTrue' es un poco más eficiente como método anónimo que un método con nombre? No entiendo tu parte del * method group conversion *. 'predicate = predicado ?? AlwaysTrue; 'puede escribirse incluso cuando' AlwaysTrue' es un método, y en ese caso donde 'predicate' es un valor nulo, se invoca el método. Incluso entonces, el delegado solo se crea una vez, ¿verdad? Entonces ahora se trata de un método llamado versus un método anónimo. ¿Es este último más eficiente? – nawfal

+0

@nawfal: Usar un campo significa que cuando llamas 'Even', no crea una nueva instancia de delegado, porque reutiliza la del campo. Con una conversión de grupo de métodos, suponiendo que 'predicate' es nulo, creará una nueva instancia de delegado en cada llamada: el delegado no se almacena en caché. –

1
public static List<int> Even(this List<int> numbers, Predicate<in> predicate = null) 
{ 
    return numbers.Where(num=> num % 2 == 0 && (predicate == null || predicate(num))); 
} 
+0

¡Doh, vencido por @Jon! –

3

lo que tienes que hacer es permitir que sea null, y luego trate eso como siempre verdadero.

Tiene dos opciones para esto, duplicar el código para omitir la llamada de delegado, esto funcionará más rápido en los casos en que no pase el delegado.

public static List<int> Even(this List<int> numbers, Predicate<int> predicate = null) 
{ 
    if (predicate == null) 
     return numbers.Where(num=> num % 2 == 0).ToList(); 
    else 
     return numbers.Where(num=> num % 2 == 0 && predicate(num)).ToList(); 
} 

O proporcionar una implementación ficticia, ya que queríamos:

public static List<int> Even(this List<int> numbers, Predicate<int> predicate = null) 
{ 
    predicate = predicate ?? new Predicate<int>(alwaysTrue); 
    return numbers.Where(num=> num % 2 == 0 && predicate(num)).ToList(); 
} 

También, considere si realmente quiere hacer esto. El efecto que los parámetros opcionales tienen en el código compilado es que el código de llamada ahora proporciona el valor predeterminado, lo que significa que siempre llamará a la sobrecarga que toma una lista y un delegado.

Si más adelante quiere volver, debe asegurarse de que se vuelve a compilar todo el código que llama al método, ya que mágicamente no comenzará a usar el método que no proporciona un delegado.

En otras palabras, esta llamada:

var even = list.Even(); 

se verá como se escribe así:

var even = list.Even(null); 

Si ahora cambia el método a ser sobrecargados una vez más, si la llamada anterior no se vuelve a compilar, entonces siempre llamará al que tiene un delegado, solo proporciona null para ese parámetro.

2

Se puede usar un valor -default null: pregunta

public static class Extended 
{ 
    public static IEnumerable<int> Even(this IEnumerable<int> numbers, 
             Predicate<int> predicate = null) 
    { 
     if (predicate==null) 
     { 
      predicate = i=>true; 
     } 

     return numbers.Where(num => num % 2 == 0 && predicate(num)); 
    } 
}