I'd like to know why the new constraint on a generic type parameter can only be applied without parameters, that is, one may constraint the type to have the parameterless constructor, but one cannot constraint the class to have, say, a constructor that receives a int as a parameter. I know ways around this, using reflection or the factory pattern, that works fine, ok. But I'd really like to know why, because I've been thinking about it and I really can't think of a difference between a parameterless constructor and one with parameters that would justify this restriction on the new constraint. What am I missing? Thanks a lot
@Eric: Let me go here with you for a sec:
Constructors are methods
Then I suppose no one would object if I'd go like this:
public interface IReallyWonderful { new(int a); string WonderMethod(int a); }
But once I have that, then I'd go:
public class MyClass<T> where T : IReallyWonderful { public string MyMethod(int a, int b) { T myT = new T(a); return myT.WonderMethod(b); } }
Which is what I wanted to do in the first place. So, sorry, but no, constructors are not methods, or at least not exactly.
On the difficulties of implementing this feature, well I'd really wouldn't know, and even if I did, I wouldn't have anything to say on a decision regarding the wisely expenditure of shareholder money. Something like that, I would've marked as an answer right away.
From an academic (my) point of view, and that is, without any regards for implementation costs, the question really is (I've rounded it up to this in the last few hours):
Should constructors be considered as part of the implementation of a class, or as part of the semantic contract (in the same way an interface is considered a semantic contract).
If we consider constructors as part of the implementation, then, constraining the constructor of a generic type parameter is not a very generic thing to do, since that'd be tying up your generic type to a concrete implementation, and one almost could say why use generics at all?
Example of constructor as part of the implementation (no sense in specifying any of the following constructors as part of the semantic contract defined by ITransformer
):
public interface ITransformer { //Operates with a and returns the result; int Transform(int a); } public class PlusOneTransformer : ITransformer { public int Transform(int a) { return a + 1; } } public class MultiplyTransformer : ITransformer { private int multiplier; public MultiplyTransformer(int multiplier) { this.multiplier = multiplier; } public int Transform(int a) { return a * multiplier; } } public class CompoundTransformer : ITransformer { private ITransformer firstTransformer; private ITransformer secondTransformer; public CompoundTransformer(ITransformer first, ITransformer second) { this.firstTransformer = first; this.secondTransformer = second; } public int Transform(int a) { return secondTransformer.Transform(firstTransformer.Transform(a)); } }
The problem is that constructors may also be considered as part of the semantic contract, like so:
public interface ICollection<T> : IEnumerable<T> { new(IEnumerable<T> tees); void Add(T tee); ... }
This means, it's always posible to build a collection from a sequence of elements, right? And that would make a very valid portion of a semantic contract, right?
Me, without taking into account any of the aspects regarding the wisely expenditure of shareholder money, would favour allowing constructors as parts of semantic contracts. Some developer messes it up and constraints a certain type to having a semantically incorrect constructor, well, what's the difference there from the same developer adding a semantically incorrect operation? After all, semantic contracts are that, because we all agreed they are, and because we all document our libraries really well ;)
@supercat is been trying to set some examples as how (quote from a comment)
It would also be hard to define exactly how constructor constraints should work, without resulting in surprising behaviors.
but I really must disagree. In C# (well, in .NET) surprises like "How to make a penguin fly?" simply don't happen. There are pretty straightforward rules as to how the compiler resolves method calls, and if the compiler can't resolve it, well, it won't pass, won't compile that is.
His last example was:
If they are contravariant, then one runs into trouble resolving which constructor should be called if a generic type has constraint new(Cat, ToyotaTercel), and the actual type just has constructors new(Animal, ToyotaTercel) and new(Cat, Automobile).
Well, lets try this (which in my opinion is a similar situation to that proposed by @supercat)
class Program { static void Main(string[] args) { Cat cat = new Cat(); ToyotaTercel toyota = new ToyotaTercel(); FunnyMethod(cat, toyota); } public static void FunnyMethod(Animal animal, ToyotaTercel toyota) { Console.WriteLine("Takes an Animal and a ToyotaTercel"); } public static void FunnyMethod(Cat cat, Automobile car) { Console.WriteLine("Takes a Cat and an Automobile"); } } public class Automobile { } public class ToyotaTercel : Automobile { } public class Animal { } public class Cat : Animal { }
And, wow, it won't compile with the error
The call is ambiguous between the following methods or properties: 'TestApp.Program.FunnyMethod(TestApp.Animal, TestApp.ToyotaTercel)' and 'TestApp.Program.FunnyMethod(TestApp.Cat, TestApp.Automobile)'
I don't see why the result should be different if the same probleme arouse out of a solution with parameterized constructor constraints, like so:
class Program { static void Main(string[] args) { GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>(); } } public class Automobile { } public class ToyotaTercel : Automobile { } public class Animal { } public class Cat : Animal { } public class FunnyClass { public FunnyClass(Animal animal, ToyotaTercel toyota) { } public FunnyClass(Cat cat, Automobile car) { } } public class GenericClass<T> where T: new(Cat, ToyotaTercel) { }
Now, of course, the compiler can't handle the constraint on the constructor, but if it could, why could't the error be, on the line GenericClass<FunnyClass> gc = new GenericClass<FunnyClass>();
similar to that obtained when trying to compile the first example, that of the FunnyMethod
.
Anyway, I'd go one step further. When one overrides an abstract method or implements a method defined on an interface, one is required to do so with exactly the same parameters type, no inheritors or ancestors allowed. So, when a parameterized constructor is required, the requirement should be met with an exact definition, not with anything else. In this case, the class FunnyClass
could never be specified as the type, for the generic parameter type of class GenericClass
.
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.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
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.
You can constrain the generic type by interface, thereby allowing only classes that implement that interface or classes that inherit from classes that implement the interface as the type parameter. The code below constrains a class to an interface.
Kirk Woll's quote from me of course is all the justification that is required; we are not required to provide a justification for features not existing. Features have enormous costs.
However, in this specific case I can certainly give you some reasons why I would push back on the feature if it came up in a design meeting as a possible feature for a future version of the language.
To start with: consider the more general feature. Constructors are methods. If you expect there to be a way to say "the type argument must have a constructor that takes an int" then why is it not also reasonable to say "the type argument must have a public method named Q that takes two integers and returns a string?"
string M<T>(T t) where T has string Q(int, int) { return t.Q(123, 456); }
Does that strike you as a very generic thing to do? It seems counter to the idea of generics to have this sort of constraint.
If the feature is a bad idea for methods, then why is it a good idea for methods that happen to be constructors?
Conversely, if it is a good idea for methods and constructors, then why stop there?
string M<T>(T t) where T has a field named x of type string { return t.x; }
I say that we should either do the whole feature or don't do it at all. If it is important to be able to restrict types to have particular constructors, then let's do the whole feature and restrict types on the basis of members in general and not just constructors.
That feature is of course a lot more expensive to design, implement, test, document and maintain.
Second point: suppose we decided to implement the feature, either the "just constructors" version or the "any member" version. What code do we generate? The thing about generic codegen is that it has been carefully designed so that you can do the static analysis once and be done with it. But there is no standard way to describe "call the constructor that takes an int" in IL. We would have to either add a new concept to IL, or generate the code so that the generic constructor call used Reflection.
The former is expensive; changing a fundamental concept in IL is very costly. The latter is (1) slow, (2) boxes the parameter, and (3) is code that you could have written yourself. If you're going to use reflection to find a constructor and call it, then write the code that uses reflection to find a constructor and call it. If this is the code gen strategy then the only benefit that the constraint confers is that the bug of passing a type argument that does not have a public ctor that takes an int is caught at compile time instead of runtime. You don't get any of the other benefits of generics, like avoiding reflection and boxing penalties.
Summary
This is an attempt to capture the current information and workarounds on this question and present it as an answer.
I find Generics combined with Constraints one of the most powerful and elegant aspects of C# (coming from a C++ templates background). where T : Foo
is great as it introduces capability to T while still constraining it to Foo at compile time. In many cases, it has made my implementation simpler. At first, I was a bit concerned as using generic types in this way can cause generics to grow through the code, but I have allowed it to do so and the benefits have greatly outweighed any downsides. However, the abstraction always falls down when it comes to constructing a Generic type that takes a parameter.
The Problem
When constraining a generic class, you are able to indicate that the generic must have a parameterless constructor and then instantiate it:
public class Foo<T> where T : new() { public void SomeOperation() { T something = new T(); ... } }
The problem is that one is only ably to constrain for parameterless constructors. This means that one of the workarounds suggested below needs to be used for constructors that have parameters. As described below, the workarounds have drawbacks ranging from requiring additional code to being very dangerous. Also, if I have a class that has a public parameterless constructor that is used by a generic method, but somewhere down the track that class is changed so that the constructor now has a parameter then I need to change the design of the template and surrounding code to use one of the workarounds rather than new().
This is something that Microsoft definitely knows about, see these links on Microsoft Connect just as a sample (not counting the confused Stack Overflow users asking the question) here here here here here here here.
They are all closed as 'Won't Fix' or 'By Design'. The sad thing about that is that the issue is then locked and it is no longer possible to vote them up. However you can vote here for the constructor feature.
The Workarounds
There are three main types of workarounds, none of which are ideal: -
The Explanations
A standard response is provided on Microsoft Connect which is:
"Thank you for your suggestion. Microsoft has received a number of suggestions on changes to the constraint semantics of generic types, as well as doing its own work in this area. However at this time Microsoft cannot give any undertaking that changes in this area will be part of a future product release. Your suggestion will be noted to help drive decisions in this area. In the meantime the code sample below..."
The workaround is actually not my recommended workaround of all the options as it is not type safe and results in a runtime exception if you ever happen to add another parameter to the constructor.
The best explanation I can find is offered by Eric Lippert in this very stack overflow post. I am very appreciative of this answer, but I think that further discussion is required on this at a user level and then at the technical level (probably by people who know more than me about the internals).
I also recently spotted that there is a good and detail by Mads Torgersen in this link (see "Posted by Microsoft on 3/31/2009 at 3:29 PM").
The problem is that constructors are different from other methods in that we can already constrain methods as much as we need to by way of derivation constraints (interface or base class). There may be some cases where method constraints are beneficial, I have never needed them, however I continuously hit the parameterless constructor limitation. Of course, a general (not constructor-only) solution would be ideal and Microsoft would need to decide on this themselves.
The Suggestions
Regarding the debatable benefit vs difficulty of implementation, I can appreciate this, but would make the following points: -
The Status
Not about to happen in any shape of form as far as I can tell.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With