Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't the new() generic constraint satisfied by a class with optional parameters in the constructor?

The following code fails to compile, producing a "Widget must be a non-abstract type with a public parameterless constructor" error. I would think that the compiler has all of the information it needs. Is this a bug? An oversight? Or is there some scenario where this would not be valid?

public class Factory<T> where T : new()
{
    public T Build()
    {
        return new T();
    }
}

public class Widget
{
    public Widget(string name = "foo")
    {
        Name = name;
    }

    public string Name { get; set; }
}

public class Program
{
    public static void Main()
    {
        var widget = new Widget(); // this is valid
        var factory = new Factory<Widget>(); // compiler error
    }
}
like image 407
Joshua Flanagan Avatar asked Apr 30 '10 17:04

Joshua Flanagan


People also ask

What does where T new () mean?

where T : new() Means that the type T must have a parameter-less constructor. Having this constraint will allow you to do something like T field = new T(); in your code which you wouldn't be able to do otherwise. You then combine the two using a comma to get: where T : class, new()

Which of the following generic constraints restricts the generic type parameter to an object of the class?

Value type constraint If we declare the generic class using the following code then we will get a compile-time error if we try to substitute a reference type for the type parameter.

How do you indicate that a class has a generic type parameter?

A generic type is declared by specifying a type parameter in an angle brackets after a type name, e.g. TypeName<T> where T is a type parameter.

What is the purpose of the class constraint on a type parameter?

Object, you'll apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class.


1 Answers

While this logically should work, it unfortunately does not. The CLR still sees your constructor as a parameter-based constructor.

Remember that, while C# supports optional parameters, this is done at the compiler level, at compile time. The underlying type still only contains a constructor taking a single parameter . As far as the CLR is concerned, the "default parameters" are converted to attributes, like so:

public Widget(([Optional, DefaultParameterValue("foo")] string name) { // ...

The CLR is a multi-language runtime. Generics are made to work at the CLR level, for all languages, so the constraints must be true in languages without default parameters, as well. Languages are not required to understand the OptionalAttribute, nor the DefaultParameterValueAttribute, so this cannot work uniformly for all languages, hence it's not allowed.


Edit:

In response to your comment:

What I don't understand is why the C# compiler cannot generate the necessary code to satisfy the CLR

Theoretically, the C# compiler team could have the language generate two separate constructors, instead of one constructor marked with attributes. This would, potentially, explode into many constructors, as named parameters create the capabilities for many, many possible combinations of "constructors" (or method calls for methods), especially when multiple arguments are available. I personally am glad that they did not, since it would cause confusion due to an overabundance of methods and constructors in the generated types, which would cause the public API to look very different than the code that generated it. Take the following constructor:

public Widget(
          int id = 0, 
          string name = "foo", 
          float width=1.0f, 
          float height=1.0f, 
          float depth=1.0f
       ) { // ... 

Were you to automatically generate all of the possible combinations here, the compiler would need to generate 120 constructors for this single constructor, since there are N! possible ways to call this...

like image 52
Reed Copsey Avatar answered Sep 28 '22 09:09

Reed Copsey