Дополнительные делегаты в С#

Это простой пример двух перегрузок методов расширения

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

Я хотел бы объединить их в один, установив делегат как необязательный:

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

Однако компилятор выдает ошибку:

Значение параметра по умолчанию для предиката должно быть константой времени компиляции

Я не вижу, как моя функция alwaysTrue не является постоянной, но эй, компилятор знает лучше:)

Есть ли способ сделать параметр делегата необязательным?

Ответы

Ответ 1

Это не константа, потому что вы создали делегата из группы методов... что не является константой времени компиляции в языке С#.

Если вы не против злоупотреблять значением null, вы можете использовать:

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

(Вы все равно можете сделать метод AlwaysTrue и использовать преобразование группы методов, но приведенный выше подход очень немного эффективнее, создав экземпляр делегата только один раз.)

Ответ 2

Что вам нужно сделать, так это разрешить его null, а затем считать, что всегда верно.

У вас есть два варианта: удвоить код, чтобы исключить вызов делегата, это будет выполняться быстрее в тех случаях, когда вы не передаете делегат.

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

Или, предоставите фиктивную реализацию по своему усмотрению:

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

Также подумайте, действительно ли вы хотите это сделать. Необязательные параметры эффекта на скомпилированном коде - это то, что код вызова теперь предоставляет значение по умолчанию, а это означает, что он всегда будет вызывать перегрузку, которая принимает список и делегат.

Если вы позже захотите вернуться назад, вам необходимо убедиться, что весь код, вызывающий метод, перекомпилирован, так как он не будет волшебным образом начать использовать метод, который не предоставляет делегата.

Другими словами, этот вызов:

var even = list.Even();

Будет выглядеть так, как было написано вот так:

var even = list.Even(null);

Если вы теперь измените метод, который будет перегружен еще раз, если вышеупомянутый вызов не перекомпилирован, тогда он всегда будет вызывать тот, у кого есть делегат, просто предоставляя null для этого параметра.

Ответ 3

Вы можете использовать значение null -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));
    }
}

Ответ 4

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