149

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?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Mike Pateras
  • 14,715
  • 30
  • 97
  • 137
  • 14
    Not related to what you ask, but be aware that `new TimeSpan(2000)` does not mean 2000 milliseconds, it means 2000 "ticks" which is 0.2 milliseconds, or one 10,000-th of two seconds. – Jeppe Stig Nielsen Jun 15 '13 at 07:59

9 Answers9

203

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.

Josh
  • 68,005
  • 14
  • 144
  • 156
  • Thats a very nice little solution. – Ian Sep 23 '10 at 16:00
  • 21
    Now document the default value in ``, because it's not visible in the signature. – Colonel Panic Jun 22 '12 at 11:02
  • 4
    I can't do this, I'm using the special value null for something else. – Colonel Panic Jun 22 '12 at 11:04
  • 5
    @MattHickford - Then you'll have to provide an overloaded method or take milliseconds as the parameter. – Josh Jun 23 '12 at 13:00
  • 19
    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. – Jeppe Stig Nielsen Jun 15 '13 at 08:03
  • 9
    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... – JoeCool Feb 25 '16 at 16:26
  • 2
    @JoeCool It's not that unusual. Many methods in the framework accept IFormatProvider and use a default implementation if you pass null for example. – Josh Feb 25 '16 at 16:27
  • 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. – Virgil Apr 11 '19 at 06:29
  • You did not answer the first question. – pooya13 Jul 14 '23 at 01:40
36

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)
{
    //...
}
phoog
  • 42,068
  • 6
  • 79
  • 117
  • 5
    +1 for that great technique. Default parameters should only be used with const types, really. Else, it's unreliable. – Lazlo Jan 04 '11 at 05:22
  • 3
    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. – OlduwanSteve Apr 22 '14 at 16:48
  • 1
    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 – Newteq Developer Aug 01 '21 at 13:54
31

This works fine:

void Foo(TimeSpan span = default(TimeSpan))

Note: default(TimeSpan) == TimeSpan.Zero

Jack Miller
  • 6,843
  • 3
  • 48
  • 66
  • 6
    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)`. – Rob Kennedy Dec 18 '12 at 23:48
  • 3
    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) – johan mårtensson Nov 03 '17 at 11:39
17

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.

KyleMit
  • 30,350
  • 66
  • 462
  • 664
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 2
    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. – CodesInChaos Aug 08 '11 at 21:43
  • So the reason why it can't be const is because C# docs say so? That's not a very convincing argument. Obviously the timespan "two seconds" is known at compile time as I can write it down. It is the technical limitation of the language that it is not supported as such. – pooya13 Jul 14 '23 at 01:47
  • Any `TimeSpan()` should be supported as `const`. – pooya13 Jul 14 '23 at 01:48
12
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.

nawfal
  • 70,104
  • 56
  • 326
  • 368
7

TimeSpan is a special case for the DefaultValueAttribute and is specified using any string that can be parsed via the TimeSpan.Parse method.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
Steve Robbins
  • 13,672
  • 12
  • 76
  • 124
dahall
  • 318
  • 3
  • 6
3

My suggestion:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

BTW TimeSpan.FromSeconds(2.0) does not equal new TimeSpan(2000) - the constructor takes ticks.

Owen Blacker
  • 4,117
  • 2
  • 33
  • 70
tymtam
  • 31,798
  • 8
  • 86
  • 126
2

Other answers have given great explanations as to why an optional parameter cannot be a dynamic expression. But, to recount, default parameters behave like compile time constants. That means the compiler has to be able to evaluate them and come up with an answer. There are some people who want C# to add support for the compiler evaluating dynamic expressions when encountering constant declarations—this sort of feature would be related to marking methods “pure”, but that isn’t a reality right now and might never be.

One alternative to using a C# default parameter for such a method would be to use the pattern exemplified by XmlReaderSettings. In this pattern, define a class with a parameterless constructor and publicly writable properties. Then replace all options with defaults in your method with an object of this type. Even make this object optional by specifying a default of null for it. For example:

public class FooSettings
{
    public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);

    // I imagine that if you had a heavyweight default
    // thing you’d want to avoid instantiating it right away
    // because the caller might override that parameter. So, be
    // lazy! (Or just directly store a factory lambda with Func<IThing>).
    Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
    public IThing Thing
    {
        get { return thing.Value; }
        set { thing = new Lazy<IThing>(() => value); }
    }

    // Another cool thing about this pattern is that you can
    // add additional optional parameters in the future without
    // even breaking ABI.
    //bool FutureThing { get; set; } = true;

    // You can even run very complicated code to populate properties
    // if you cannot use a property initialization expression.
    //public FooSettings() { }
}

public class Bar
{
    public void Foo(FooSettings settings = null)
    {
        // Allow the caller to use *all* the defaults easily.
        settings = settings ?? new FooSettings();

        Console.WriteLine(settings.Span);
    }
}

To call, use that one weird syntax for instantiating and assigning properties all in one expression:

bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02

Downsides

This is a really heavyweight approach to solving this problem. If you are writing a quick and dirty internal interface and making the TimeSpan nullable and treating null like your desired default value would work fine, do that instead.

Also, if you have a large number of parameters or are calling the method in a tight loop, this will have the overhead of class instantiations. Of course, if calling such a method in a tight loop, it might be natural and even very easy to reuse an instance of the FooSettings object.

Benefits

As I mentioned in the comment in the example, I think this pattern is great for public APIs. Adding new properties to a class is a non-breaking ABI change, so you can add new optional parameters without changing the signature of your method using this pattern—giving more recently compiled code more options while continuing to support old compiled code with no extra work.

Also, because C#’s built in default method parameters are treated as compiletime constants and baked into the callsite, default parameters will only be used by code once it is recompiled. By instantiating a settings object, the caller dynamically loads the default values when calling your method. This means that you can update defaults by just changing your settings class. Thus, this pattern lets you change default values without having to recompile callers to see the new values, if that is desired.

Community
  • 1
  • 1
binki
  • 7,754
  • 5
  • 64
  • 110
0

To specify a default value for struct type parameters, I would suggest to use overload:

void Foo() 
{
    Foo(TimeSpan.FromSeconds(2.0));
}

void Foo(TimeSpan time)
{
    // ...
}
  • This answer is a duplicate of another answer posted long before it -https://stackoverflow.com/a/4557502/8998456. Even the code example provided is exactly the same. In the future please make sure to read other answers before posting your own. – Kacper May 20 '23 at 13:38