15

In C#, you can define default parameters as described here. I was playing around with tuples and C#7 (using LinqPad with Preferences -> Queries -> Use Roslyn assemblies turned on) as follows:

void Main()
{   
    var result=AddTuples((1, 2),  (3, 4));
    Console.WriteLine($"Result is: {result}");
}

(int x, int y) AddTuples((int x, int y) a,  (int x, int y) b)
{
    return (a.x + b.x,  a.y + b.y);
}

This works fine, it shows the sum of a and b:

Result is: (4, 6)

Now I was trying to modify AddTuples to have default parameters, but somehow I couldn't figure out how. What I tried is something like:

(int x, int y) AddTuples2((int x, int y) a = (0, 0),  (int x, int y) b = (0, 0))
{
    return (a.x + b.x,  a.y + b.y);
}

But I am getting the error message:

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

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

(try it online with DotNetFiddle)

What am I doing wrong?


Update

Thank you for the great answers provided, I've learned a lot. Let me wrap up: To have default values for tuple parameters, there are 3 possible options:

  1. The old fashioned way: Overloading the method, provide defaults in method body. Disadvantage: If you have more than 2 tuples, overloading becomes cumbersome.
  2. Use nullable tuples, provide defaults in method body.
  3. Use tuple's default values
  4. Use a params array to allow more than 2 tuples in the method's parameters, provide defaults in method body

Note: Options 1., 2. and 4. allow to specify any desired default value, while option 3. is limited to the default values provided by the default keyword.

Matt
  • 25,467
  • 18
  • 120
  • 187
  • I imagine you won't be able to do this because struct instances cannot be constants, and tuples are really just structs. – Matthew Watson Jan 27 '17 at 11:00
  • 1
    Create another method without parameters and call your method with zeroed tuples from within – Jan Jan 27 '17 at 11:01
  • @Jan - Yes, I know this is always possible. I thought the designers of C#7 implemented a bit more syntactic sugar ... – Matt Jan 27 '17 at 11:06
  • Tuples are really just a kind of structs for which the compiler accepts a special initialization syntax, e.g. (int x, int y) a = (0,0), and structs in general are not compile-time constants, because nothing forbids a struct to have a constructor that produces arbitrary side effects, which in turn contradicts the compile-time constantness constraint. To implement the desired behavior you'll need to have multiple overloads of the method. – Eduard Malakhov Jan 27 '17 at 11:10
  • @EduardMalakhov: Yes, you are right with the first part. But look what Patrick did, it seems to be more elegant to me rather than creating multiple overloads. – Matt Jan 27 '17 at 11:14
  • 1
    @Matt It is more elegant indeed, but it comes at the cost of boxing, as explained here: https://msdn.microsoft.com/en-us/library/ms228597.aspx. I guess this is a merely the matter of preference. In this particular case I personally would prefer performance over elegance. – Eduard Malakhov Jan 27 '17 at 11:20
  • I dislike default parameters in any case - In most cases it turns out that a better-named method would be more readable. – Matthew Watson Jan 27 '17 at 11:22
  • @MatthewWatson - That's a personal opinion, and yes for some scenarios it might be better. Here it was rather a technical question if it is possible or not. – Matt Jan 27 '17 at 11:29

3 Answers3

9

a and b are not constants. Everything that creates a new instance (whether it is a struct or a class) is not considered a constant, and method signatures only allow constants as default values.

That said, there is no way to default tuples from the method signature, you have to create a separate method.

The only way out seems to be using nullable arguments:

(int x, int y) AddTuples2((int x, int y)? a = null, (int x, int y)? b = null)
{
    (int x, int y) aNN = a ?? (0,0);
    (int x, int y) bNN = b ?? (0,0);
    return (aNN.x + bNN.x, aNN.y + bNN.y);
}
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • 1
    Good solution, Patrick! **Note:** You don't need to introduce new local variables, `a` and `b` can be overwritten like `a = a ?? (0, 0);` in the function body. – Matt Jan 27 '17 at 11:11
  • 1
    Yes, but you have to do `a.Value.x` then. – Patrick Hofman Jan 27 '17 at 11:16
  • You are right, in this case `return (a.Value.x + b.Value.x, a.Value.y + b.Value.y);` is required - but it works, and I believe it looks a bit more readable! :-) – Matt Jan 27 '17 at 11:23
9

You can specify a default as long as you are happy with default-initialisation of the int components to zero:

public static (int x, int y) AddTuples(
    (int x, int y) a = default((int, int)), 
    (int x, int y) b = default((int, int)))
{
    return (a.x + b.x, a.y + b.y);
}

Unfortunately you can't provide specific default values for the tuple's components.

However, for your specific example (where you want to default to (0, 0)) this seems sufficient.


[Addendum]

Another approach to this specific example is to use a params array:

public static (int x, int y) AddTuples(params (int x, int y)[] tuples)
{
    return (tuples.Sum(t => t.x), tuples.Sum(t => t.y));
}

And then you can do:

Console.WriteLine($"Result is: {AddTuples()}");                       // (0, 0)
Console.WriteLine($"Result is: {AddTuples((1, 1))}");                 // (1, 1)
Console.WriteLine($"Result is: {AddTuples((1, 1), (2, 2))}");         // (3, 3)
Console.WriteLine($"Result is: {AddTuples((1, 1), (2, 2), (3, 3))}"); // (6, 6)
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • That is similar to **[Patricks solution](http://stackoverflow.com/a/41892450/1016343)**, but he actually solved it by checking for null and assigning the desired default values. – Matt Jan 27 '17 at 11:16
  • @Matt Actually, he had to change the parameter types from `(int, int)` to `(int, int)?` so it's somewhat different. This approach doesn't change the parameter types at all (but is restricted to defaulting to default values only, so is not as flexible). – Matthew Watson Jan 27 '17 at 11:18
  • 1
    Nice one. I didn't know you can pass defaults for tuples – Sergey Berezovskiy Jan 27 '17 at 11:22
  • It is good to know that this works as well, and if you just need `(0, 0)` for default it's fine. I gave you an upvote! – Matt Jan 27 '17 at 11:27
  • @SergeyBerezovskiy From C# 7 those tuples are structs, so you can define them like that. – Patrick Hofman Jan 27 '17 at 11:41
  • That's a great idea indeed - If I could, I would upvote a 2nd time (one for each approach) ! :-) – Matt Jan 27 '17 at 16:48
1

VisualStudio 2017 suggested the following simplification to Patricks answer:

(int x, int y) AddTuples2((int x, int y)? a = null, (int u, int v)? b = null)
{
    (int x, int y) = a ?? (0,0);
    (int u, int v) = b ?? (0,0);
    return (x + u, y + v);
}
DragonSpit
  • 458
  • 4
  • 9
  • Good hint, it looks simpler this way without having to specify `aNN` and `bNN` - instead introducing the variables `x` and `y`, `u` and `v` in the parameters - and then just use them. – Matt Sep 02 '19 at 11:15