3

I have the following code, that I use as sample for illustrating different scenarios:

 public static void MethodWithOptionalGuid(Guid id = default(Guid)) { } public static void MethodWithOptionalInteger(int id = 2) { } public static void MethodWithOptionalString(string id = "33344aaa") { } public static void MethodWithoutOptionalParameter(int id, Guid longId) { } static void Main(string[] args) { var methods = typeof(Program).GetMethods(BindingFlags.Public | BindingFlags.Static).ToList(); foreach (var method in methods) { PrintMethodDetails(method); } Console.ReadLine(); } static void PrintMethodDetails(MethodInfo method) { Console.WriteLine(method.Name); foreach (var parameter in method.GetParameters().ToList()) { Console.WriteLine(parameter.Name + " of type " + parameter.ParameterType.ToString() + " with default value:" + parameter.DefaultValue); } Console.WriteLine(); } 

It prints the following:

MethodWithOptionalGuid id of type System.Guid with default value: MethodWithOptionalInteger id of type System.Int32 with default value:2 MethodWithOptionalString id of type System.String with default value:33344aaa MethodWithoutOptionalParameter id of type System.Int32 with default value: longId of type System.Guid with default value: 

The output seems fine for the last 3 methods.

My question is regarding the first one, MethodWithOptionalGuid: why the Guid's default value is not recognized?

I expected to receive something like "0000000-..." . I also tried initializing the optional parameter with new Guid() and same result. I tried as well other structs, like TimeSpan and the behavior is the same.

I expected that all value types would behave the same (as seen in integer example).

Extra: I found this thing while trying to use in Asp.Net MVC an action with an optional Guid parameter and failed (had to make the Guid nullable). Went through MVC code and found that it uses the DefaultValue at some point. So I made this code sample to better illustrate my issue.

4
  • @vc74 I updated my question. While I agree that for the last 3 methods, the output is what I expected, I have trouble in understanding the output for the first method. Commented Mar 23, 2016 at 8:59
  • I think OP's question is mainly about MethodWithOptionalGuid. There a default value is specified and I too wonder why default(Guid) does not result in "0000.." Commented Mar 23, 2016 at 9:00
  • @RaphaëlAlthaus It also says there that default(Guid) | new Guid() is a compiler known value and it should work. The root cause explains why you can't use Guid.Empty. Commented Mar 23, 2016 at 9:06
  • 2
    @RaphaëlAlthaus I don't get it: the code compiles fine and if you output the value of id in MethodWithOptionalGuid it is "00000-"... so why parameter.DefaultValue is null instead of Guid.Emtpy? Commented Mar 23, 2016 at 9:06

4 Answers 4

6

The why is pretty relevant and something you really need to heed when you want to do something like this. Dragons live here. If you use ildasm.exe to look at the method then you'll see:

 .param [1] = nullref 

A null as a default for a structure type, uh-oh. The semantics for the .param directive is described in the CLI spec, Ecma-335, chapter II.15.4.1.4. It is very short, I'll copy-paste the entire chapter (edited for readability)

MethodBodyItem ::= .param ‘[’ Int32 ‘]’ [ ‘=’ FieldInit ]

This directive stores in the metadata a constant value associated with method parameter number Int32, see §II.22.9. While the CLI requires that a value be supplied for the parameter, some tools can use the presence of this attribute to indicate that the tool rather than the user is intended to supply the value of the parameter. Unlike CIL instructions, .param uses index 0 to specify the return value of the method, index 1 to specify the first parameter of the method, index 2 to specify the second parameter of the method, and so on.

[Note: The CLI attaches no semantic whatsoever to these values—it is entirely up to compilers to implement any semantic they wish (e.g., so-called default argument values). end note]

The [Note] is most relevant. When you use Reflection then you play the role of the compiler and it is up to you to interpret the default value correctly. In other words, you have to know that in C# the default value for a structure type is null.

This is otherwise a limitation in what can be stored in a [FieldInit]. The initialization value must be stored in the metadata of the assembly and is subject to the same kind of limitations of an [attribute] declaration. Just simple value types can be used, not structures. So by convention the C# designers worked around this by using null instead. Good enough for the C# compiler to understand that default(Guid) was intended.

That's not where the trouble ends, there are other ways to indicate an optional argument, the [OptionalAttribute] was used prior to C# v4.0. And is still used today, by COM interop libraries and other languages like VB.NET and C++/CLI. That's where the dragons live.

Ignoring those beasts for now, workaround code for what you have could look like this:

 var def = parameter.DefaultValue; if (parameter.ParameterType.IsValueType && def == null) { def = Activator.CreateInstance(parameter.ParameterType); } Console.WriteLine(parameter.Name + " of type " + parameter.ParameterType.ToString() + " with default value:" + def); 
Sign up to request clarification or add additional context in comments.

2 Comments

Shall we say that everything that can't be a const will be evaluated to null ? So only simple types, enum types and string won't be evaluated to null ? (with a special case for default(strting) as string is a ref type, than is of course null)
That invokes just another dragon. You cannot declare a const Guid but as you can tell it works fine as a default value. So no, you can't say that.
1

Sorry - I can't tell you WHY it doesn't return the correct value but I've a workarround using this extension method:

public static object GetDefaultValue(this ParameterInfo p) { if (!p.Attributes.HasFlag(ParameterAttributes.HasDefault)) { return null; } if (p.DefaultValue != null || p.RawDefaultValue != null) { return p.DefaultValue ?? p.RawDefaultValue; } return p.ParameterType.IsValueType ? Activator.CreateInstance(p.ParameterType) : null; } 

Maybe it will help.

1 Comment

Thx for the answer. Unfortonately, I'm more interested in the WHY. I've updated my question explaining how I got here. Thx again.
1

Open your testfile with a decompiler of your choice and you will see that your code actually get compiled to:

public static void MethodWithOptionalGuid(Guid id = null) { } 

This is why the DefaultValue property returns null. When you use this parameter now, lets say you add:

Console.WriteLine(id); 

The compiler will insert a

box [mscorlib]System.Guid 

statement before the WriteLine. Now the output will be the expected "0000000-..."

Comments

1

I guess the answer can be found in c# language specifications (someone may find something more relevant)

Point 4.1.2

When the operands of an expression are all simple type constants, it is possible for the compiler to evaluate the expression at compile-time. Such an expression is known as a constant-expression (§7.19). Expressions involving operators defined by other struct types are not considered to be constant expressions.

Through const declarations it is possible to declare constants of the simple types (§10.4). It is not possible to have constants of other struct types, but a similar effect is provided by static readonly fields.

Even if we're not talking about expressions or consts here, it seems that only simple types (structs like int) can be evaluated /computed at compile time. That's why default(int) can be evaluated and then would display 0 in your code.

But other struct can't be computed at compile time, so I would say that it's the reason why they're evaluated to null.

When you set a default parameter value, it has to be a compile-time constant.

The tricky part is that you can put default(Guid) or new Guid() as an accepted compile-time constant (BUT evaluated to null), but... it can't be a const.

For example, you could not do

const Guid g = new Guid(); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.