C# 4.0: Can I use a TimeSpan as an optional parameter with a default value?
Solution 1
You can work around this very easily by changing your signature.
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
I should elaborate - the reason those expressions in your example are not compile-time constants is because at compile time, the compiler can't simply execute TimeSpan.FromSeconds(2.0) and stick the bytes of the result into your compiled code.
As an example, consider if you tried to use DateTime.Now instead. The value of DateTime.Now changes every time it's executed. Or suppose that TimeSpan.FromSeconds took into account gravity. It's an absurd example but the rules of compile-time constants don't make special cases just because we happen to know that TimeSpan.FromSeconds is deterministic.
Solution 2
My VB6 heritage makes me uneasy with the idea of considering "null value" and "missing value" to be equivalent. In most cases, it's probably fine, but you might have an unintended side effect, or you might swallow an exceptional condition (for example, if the source of span
is a property or variable that should not be null, but is).
I would therefore overload the method:
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
Solution 3
This works fine:
void Foo(TimeSpan span = default(TimeSpan))
Note: default(TimeSpan) == TimeSpan.Zero
Solution 4
The set of values which can be used as a default value are the same as can be used for an attribute argument. The reason being that default values are encoded into metadata inside of the DefaultParameterValueAttribute
.
As to why it can't be determined at compile time. The set of values and expressions over such values allowed at compile time is listed in official C# language spec:
C# 6.0 - Attribute parameter types:
The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
- One of the following types:
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
.- The type
object
.- The type
System.Type
.- An enum type.
(provided it has public accessibility and the types in which it is nested (if any) also have public accessibility)- Single-dimensional arrays of the above types.
The type TimeSpan
does not fit into any of these lists and hence cannot be used as a constant.
Solution 5
void Foo(TimeSpan span = default(TimeSpan))
{
if (span == default(TimeSpan))
span = TimeSpan.FromSeconds(2);
}
provided default(TimeSpan)
is not a valid value for the function.
Or
//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
if (span == new TimeSpan())
span = TimeSpan.FromSeconds(2);
}
provided new TimeSpan()
is not a valid value.
Or
void Foo(TimeSpan? span = null)
{
if (span == null)
span = TimeSpan.FromSeconds(2);
}
This should be better considering chances of null
value being a valid value for the function are rare.
Mike Pateras
Young developer, interested in gaming and game development, along with many other things.
Updated on July 08, 2022Comments
-
Mike Pateras almost 2 years
Both of these generate an error saying they must be a compile-time constant:
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0)) void Foo(TimeSpan span = new TimeSpan(2000))
First of all, can someone explain why these values can't be determined at compile time? And is there a way to specify a default value for an optional TimeSpan object?
-
Ian over 13 yearsThats a very nice little solution.
-
Lazlo over 13 years+1 for that great technique. Default parameters should only be used with const types, really. Else, it's unreliable.
-
CodesInChaos over 12 yearsSlight nit-pick: Calling a static method doesn't fit in any of the list.
TimeSpan
can fit the last one on this listdefault(TimeSpan)
is valid. -
Colonel Panic almost 12 yearsNow document the default value in
<param>
, because it's not visible in the signature. -
Colonel Panic almost 12 yearsI can't do this, I'm using the special value null for something else.
-
Josh almost 12 years@MattHickford - Then you'll have to provide an overloaded method or take milliseconds as the parameter.
-
Rob Kennedy over 11 yearsWelcome to Stack Overflow. Your answer appears to be that you can provide a default parameter value, as long as it's the one very specific value that the compiler allows. Have I understood that right? (You can edit your answer to clarify.) This would be a better answer if it showed how to take advantage of what the compiler allows to get to what the question originally sought, which was to have arbitrary other
TimeSpan
values, such as that given bynew TimeSpan(2000)
. -
Jeppe Stig Nielsen almost 11 yearsCan also use
span = span ?? TimeSpan.FromSeconds(2.0);
with the nullable type, in method body. Orvar realSpan = span ?? TimeSpan.FromSeconds(2.0);
to get a local variable which is not nullable. -
OlduwanSteve about 10 yearsThis is the 'time honoured' approach that default values replaced, and for this situation I think this is the least ugly answer ;) On its own it doesn't necessarily work so well for interfaces though, because you really want the default value in one place. In this case I have found extension methods to be a useful tool: the interface has one method with all the parameters, then a series of extension methods declared in a static class alongside the interface implement the defaults in various overloads.
-
JoeCool about 8 yearsThe thing I don't like about this is that it implies to the user of the function that this function "works" with a null span. But that's not true! Null is not a valid value for span as far as the actual logic of the function is concerned. I wish there were a better way that didn't seem like a code smell...
-
Josh about 8 years@JoeCool It's not that unusual. Many methods in the framework accept IFormatProvider and use a default implementation if you pass null for example.
-
johan mårtensson over 6 yearsAn alternative that uses some specific default value would be to use a private static readonly TimeSpan defaultTimespan = Timespan.FromSeconds(2) combined with default constructor and constructor taking a timespan. public Foo() : this(defaultTimespan) and public Foo(Timespan ts)
-
Virgil about 5 yearsJust want to nitpick on your answer - the compiler absolutely can "simply execute TimeSpan.FromSeconds(2.0) and stick the bytes of the result into your compiled code."; it's just that the language design doesn't allow them to do it safely. E.g. you could have an attribute that instructs the compiler that "this is method is safe to evaluate at compile time if argument is constant" - and then everything would work out. Sadly though, C# has no such thing.
-
Newteq Developer almost 3 yearsGreat approach! I really prefer this over all the others that have been mentioned. It gives you a lot more control over the default value