C# 4.0: Can I use a TimeSpan as an optional parameter with a default value?

55,874

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.

Share:
55,874
Mike Pateras
Author by

Mike Pateras

Young developer, interested in gaming and game development, along with many other things.

Updated on July 08, 2022

Comments

  • Mike Pateras
    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
    Ian over 13 years
    Thats a very nice little solution.
  • Lazlo
    Lazlo over 13 years
    +1 for that great technique. Default parameters should only be used with const types, really. Else, it's unreliable.
  • CodesInChaos
    CodesInChaos over 12 years
    Slight nit-pick: Calling a static method doesn't fit in any of the list. TimeSpan can fit the last one on this list default(TimeSpan) is valid.
  • Colonel Panic
    Colonel Panic almost 12 years
    Now document the default value in <param>, because it's not visible in the signature.
  • Colonel Panic
    Colonel Panic almost 12 years
    I can't do this, I'm using the special value null for something else.
  • Josh
    Josh almost 12 years
    @MattHickford - Then you'll have to provide an overloaded method or take milliseconds as the parameter.
  • Rob Kennedy
    Rob Kennedy over 11 years
    Welcome 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 by new TimeSpan(2000).
  • Jeppe Stig Nielsen
    Jeppe Stig Nielsen almost 11 years
    Can also use span = span ?? TimeSpan.FromSeconds(2.0); with the nullable type, in method body. Or var realSpan = span ?? TimeSpan.FromSeconds(2.0); to get a local variable which is not nullable.
  • OlduwanSteve
    OlduwanSteve about 10 years
    This 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
    JoeCool about 8 years
    The 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
    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
    johan mårtensson over 6 years
    An 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
    Virgil about 5 years
    Just 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
    Newteq Developer almost 3 years
    Great approach! I really prefer this over all the others that have been mentioned. It gives you a lot more control over the default value