Development Testing Blog

C# Bug: Using Reflection with Default Parameters

ATBG reader Laurence had a question about a bug in his C# program caused by the interaction between Activator.CreateInstance and constructors with default parameters:


Consider the following code:

public class C
{
  public C() {}
}

public class D
{
  public D(int x = 123) {}
}


We can create instances of these classes with new C() and new D(), and the reflection method Activator.CreateInstance<C>() works. But Activator.CreateInstance<D>() fails unexpectedly at runtime with a missing method exception. A call to Activator.CreateInstance(typeof(D), 123) succeeds. The question is: why do we get a missing method exception here? Couldn’t the compiler or the runtime do the right thing?

Good question. First off, let’s understand what happens when you make a constructor (or any method) with a default value for a parameter. The compiler does not generate the class as though you had written:

public class D
{
  public D() : this(123) {}
  public D(int x) {}
}

If it did then Activator.CreateInstance<D>() would succeed, because there really would be a default constructor described in metadata. The compiler actually does not modify the code generated for the class at all except to put an attribute on the parameter noting its default value. The modification happens instead at the call site; when you say new D() the compiler actually generates new D(123). (Notice that this means that if you change the value of the default without recompiling callers in other assemblies, they will continue to use the old value! But that’s another bug.)

That explains why the C# compiler does not generate a default constructor. Why not then make Activator.CreateInstance<D>() check to see if there is a non-default constructor where every parameter is annotated with a default value?

There is no reason in principle why the authors of that API couldn’t do that. But remember, the purpose of Activator.CreateInstance and other reflection-style APIs is not to make a late-bound version of C#. There’s nothing particularly special about C# from the runtime’s point of view. There could be a dozen languages each with different semantics around missing arguments and default parameter values, and keeping up with all the subtle language rules would become burdensome. I am not surprised that the runtime team has declined to take on this work, as it is a lot of expense for a very small benefit.

Thanks to Laurence for the interesting question. For further reading, see my 2011 blog articles on optional argument corner cases.

  1. But how does it work when you have compiled DLL with such class? Is there any metadata provided on default value? Maybe there is hidden attribute or something. How does compiler knows what value to provide when you call new D()?

    1. Your conjecture is correct; the metadata contains an attribute that has the default value.

      Activator.CreateInstance could of course read this attribute. But like I said, the purpose of Reflection is not to encode the overload resolution rules of the C# language; different languages might handle that attribute differently, or use another mechanism entirely to indicate a default value for a parameter.

    2. Guest wrote:

      But how does it work when you have compiled DLL with such class? Is there any metadata provided on default value? Maybe there is hidden attribute or something. How does compiler knows what value to provide when you call new D()?
      From the article:

      The compiler actually does not modify the code generated for the class at all except to put an attribute on the parameter noting its default value.

    1. If you use Roslyn to analyze code that contains a call to a method that has a default value for a parameter, Roslyn will correctly do overload resolution and give you the method symbol.

      Lambdas converted to expression trees in C# may not contain calls that use default parameter values or named arguments.

      1. > Lambdas converted to expression trees in C# may not contain calls that use default parameter values or named arguments

        Interesting, I didn’t know that. Would you like to explain why? Or is this a case of those “by default, there are no features, it takes a decision and a budget to build features”? If this was too expensive to build, I’d like to understand what made it expensive?

  2. There’s also the fact that if I gave a parameterized constructor (and no parameterless constructor) then maybe it’s because I consider the parameter to be key to the creation of the object. Maybe new D() doesn’t make any sense whatsoever, but new D(123) does.

    If new D() doesn’t make sense then Activator.CreateInstance(typeof(D)) probably shouldn’t either.

  3. OK, but why doesn’t compiler generate the 2 constructors you mentioned? Is there a case where that would be incorrect? Wouldn’t that make the feature work a lot better when interoping with other languages that don’t honor your attribute?

    1. @eric in this case there is only one optional parameter, but what if there is more than one? or a mix of required and optional parameters? The compiler would need to generate every possible permutation.

    2. That’s a good question which I did not address in this posting. Think about the more general case of methods, rather than constructors. Suppose you have something crazy but legal like: void M(int x, int y = 0) {} void M(int q, int y, int z = 0). Now what methods should the compiler generate? M(int x), M(int x, int y), M(int q, int y) and M(int q, int y, int z) ? That’s not a legal set of methods! There are a few rare situations where the compiler will generate extra methods for you, but in general the compiler team tries to avoid that situation whenever possible because it gets very complicated making sure that you’re introducing a consistent set.

      1. What does the crazy but legal thing mean – what gets called if you call M(5, 3) ?

        If it’s not even possible to generate an equivalent set of methods, how is the compiler of the call site supposed to resolve the call unambiguously?

        1. The compiler resolves the ambiguity by going with the method where the number of formal parameters is exactly equal to the number of arguments. See the C# specification for more details; the rules for resolving ambiguities are quite complex.

      2. Interesting… But here, M(1, 2) is ambiguous, should it call M(int x, int y) or M(int q, int y) ?
        Isn’t it possible to just have the compiler generate an error in this case?

  4. The interesting question imo is why the compiler team didn’t go with the shown solution of having a argument-less constructor but instead put an annotation in the code. Seems to me that the first solution would’ve been not only simpler but avoided the problem of recompiling code and changing the default parameter too (and make it less confusing for people who don’t know about this detail from either C++ or reading the spec).

    1. That is a good question. Though that would work for the simple case of a class with no default constructor and a single constructor with one optional parameter, that solution does not scale well to the more general problem of arbitrary methods with arbitrarily many parameters, some of which have default values and some of which do not. The C# team tends to prioritize solving the more general version of a problem rather than filling the language with little one-off tricks.

      1. Ah yes, a solution that scales exponentially with optional parameters is not especially great even for relatively small number of parameters.. at least if you have to assume (as the c# certainly has to) that people may not always follow best practices.

        10 optional parameters shouldn’t lead to a gigantic class after all. Makes sense, thanks.

  5. I’m interested in your choice of language. I would have called this “Using Reflection with Parameter Defaults” The use of “properties” in this context seems confusing. Am I missing something or just being pedantic?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Current day month ye@r *