15

This is a simple example of two extension methods overloads

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

I'd like to be able to merge them into one, by setting a delegate to be optional:

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

However, compiler throws an error:

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

I don't see how my alwaysTrue function is not constant, but hey, compiler knows better :)

Is there any way to make the delegate parameter optional?

Kornelije Petak
  • 9,412
  • 15
  • 68
  • 96

4 Answers4

20

It's not constant because you've created a delegate from a method group... that's not a compile-time constant as far as the C# language is concerned.

If you don't mind abusing the meaning of null slightly you could use:

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

(You could still make AlwaysTrue a method and use a method group conversion, but the above approach is very slightly more efficient by creating the delegate instance just once.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Sometimes I wish there would be multiple accepts on SO. All the answers are correct, however Jon's answer seems most profitable for me cause he made me go and read about method groups to actually learn something I didn't know. – Kornelije Petak Jun 28 '11 at 09:09
  • Why do you say `AlwaysTrue` is slightly more efficient as an anonymous method than a named method? I'm not understanding your *method group conversion* part. `predicate = predicate ?? AlwaysTrue;` can be written even when `AlwaysTrue` is a method, and in that case where `predicate` is a null, method is called. Even then the delegate is only created only once right? So now it is about named method vs anonymous method. Is the latter more performant? – nawfal Dec 22 '13 at 00:59
  • @nawfal: Using a field means that when you call `Even`, it doesn't create a new delegate instance, because it reuses the one from the field. With a method group conversion, assuming that `predicate` is null, that will create a new delegate instance on every call - the delegate isn't cached. – Jon Skeet Dec 22 '13 at 08:45
3

What you have to do is allow it to be null, and then treat that as always true.

You have two options for this, double the code to leave out the delegate call, this will perform faster in the cases where you don't pass the delegate.

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

Or, provide a dummy implementation as you wanted:

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

Also, consider whether you really want to do this. The effect optional parameters have on compiled code is that the calling code now provides the default value, which means it will always call the overload which take a list and a delegate.

If you later on want to go back, you need to ensure all code that calls the method is recompiled, since it won't magically start using the method that doesn't provide a delegate.

In other words, this call:

var even = list.Even();

Will look like it was written like this:

var even = list.Even(null);

If you now change the method to be overloaded yet again, if the above call isn't recompiled, then it will always call the one with a delegate, just providing null for that parameter.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
2

You could use a null-default value:

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));
    }
}
ulrichb
  • 19,610
  • 8
  • 73
  • 87
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)));
}
George Duckett
  • 31,770
  • 9
  • 95
  • 162