2011-06-28 18 views
14

To jest prosty przykład z dwóch metod rozszerzenie przeciążaopcjonalne delegatów w 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)); 
    } 
} 

Chciałbym, aby móc połączyć je w jeden, ustawiając delegata być opcjonalnie:

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

jednak kompilator zgłasza błąd:

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

nie widzę jak moja funkcja alwaysTrue nie jest stała, ale hej, kompilator kno lepiej :)

Czy istnieje sposób na opcjonalne ustawienie parametru delegata?

+0

Bardzo ciekawe pytanie +1 –

Odpowiedz

19

Nie jest stała, ponieważ utworzyłeś delegata z grupy metod ... to nie jest stała czasu kompilacji w odniesieniu do języka C#.

Jeśli nie przeszkadza nadużywa znaczenie null nieznacznie można użyć:

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

(Ty mógłby jeszcze zrobić AlwaysTrue metodę i używać konwersji grupy metoda, ale powyższe podejście jest bardzo nieznacznie bardziej efektywny poprzez utworzenie instancji delegata tylko raz.)

+0

Czasami żałuję, że nie byłby wielokrotnością przyjmuje na SO. Wszystkie odpowiedzi są poprawne, jednak odpowiedź Jona wydaje mi się najbardziej opłacalna, ponieważ zmusił mnie do przeczytania o grupach metod, aby dowiedzieć się czegoś, czego nie znałem. –

+0

Dlaczego mówisz, że "AlwaysTrue" jest nieco bardziej efektywny jako metoda anonimowa niż metoda nazwana? Nie rozumiem twojej * grupy konwersji * grupy metod. 'predicate = predicate? AlwaysTrue; 'można zapisać nawet wtedy, gdy' AlwaysTrue' jest metodą, iw tym przypadku, gdy 'predicate' jest wartością null, wywoływana jest metoda. Nawet wtedy delegat jest tworzony tylko raz, prawda? Teraz chodzi o metodę nazwaną a metodę anonimową. Czy ten drugi jest bardziej wydajny? – nawfal

+0

@nawfal: Użycie pola oznacza, że ​​po wywołaniu 'Even' nie tworzy nowej instancji delegata, ponieważ używa ona ponownie z pola. Z konwersją grupy metod, zakładając, że 'predykat' ma wartość null, utworzy nową instancję delegata dla każdego wywołania - delegat nie jest buforowany. –

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, pobity przez @ Jon! –

3

Co trzeba zrobić, to pozwolić mu być null, a następnie traktuj to jako zawsze prawdziwe.

Masz dwie opcje, podwójne kod, aby pominąć wywołanie delegata, to będzie działać szybciej w przypadkach, gdy nie przekazujesz delegata.

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(); 
} 

Albo zapewnić realizację obojętne jak chciałeś:

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(); 
} 

Należy także rozważyć, czy na pewno chcesz to zrobić. Efektowy parametr opcjonalny na skompilowanym kodzie polega na tym, że kod wywołujący zapewnia teraz wartość domyślną, co oznacza, że ​​zawsze wywoła przeciążenie, które pobiera listę i delegata.

Jeśli później będziesz chciał wrócić, musisz upewnić się, że cały kod, który wywołuje metodę, jest rekompilowany, ponieważ nie zacznie magicznie używać metody, która nie zapewnia delegata.

Innymi słowy, to wezwanie:

var even = list.Even(); 

będzie wyglądać jak było napisane tak:

var even = list.Even(null); 

Jeśli teraz zmienić metodę być przeciążony jeszcze raz, jeśli powyższe wezwanie nie jest rekompilowany, a następnie zawsze wywoła ten z delegatem, podając tylko ten parametr jako null.

2

Można użyć null wartość default:

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