I have written an extension method in csharp for an MVCContrib Html helper and was surprised at the form of the generic constraint, which on the face of it seems to circular
The way you are using it makes no sense at all. But using a generic parameter in a constraint on that same parameter is quite normal, here's a more obvious example:
class MySortedList<T> where T : IComparable<T>
The constraint expresses the fact that there must be an ordering between objects of type T in order to put them into sorted order.
EDIT: I'm going to deconstruct your second example, where the constraint is actually wrong but helps it compile.
The code in question is:
/*analogous method for comparison*/
public static List<T> AddNullItem<T>(this List<T> list, bool value)
where T : List<T>
{
list.Add(null);
return list;
}
The reason it won't compile without a constraint is that value types can't be null
. List<T>
is a reference type, so by forcing where T : List<T>
you force T
to be a reference type which can be null. But you also make AddNullItem
nearly useless, since you can no longer call it on List<string>
, etc. The correct constraint is:
/* corrected constraint so the compiler won't complain about null */
public static List<T> AddNullItem<T>(this List<T> list)
where T : class
{
list.Add(null);
return list;
}
NB: I also removed the second parameter which was unused.
But you can even remove that constraint if you use default(T)
, which is provided for exactly this purpose, it means null
when T
is a reference type and all-zero for any value type.
/* most generic form */
public static List<T> AddNullItem<T>(this List<T> list)
{
list.Add(default(T));
return list;
}
I suspect that your first method also needs a constraint like T : class
, but since I don't have all the classes you're using I can't say for certain.
public static TextInput<T> ReadOnly<T>(this TextInput<T> element, bool value)
where T: TextInput<T>
Let's break it down:
TextInput<T>
is the return type.
TextInput<T>
is the type being extended (the type of the first parameter to the static method)
ReadOnly<T>
is the name of the function that extends a type whose definition includes T, i.e. TextInput<T>
.
where T: TextInput<T>
is the constraint on T from ReadOnly<T>
, such that T can be used in a generic TextInput<TSource>
. (T is TSource!)
I don't think it's circular.
If you take out the constraint, I would expect that element
is trying to be casted to the generic type (not a TextInput of the generic type), which is obviously not going to work.
The reason constraint is there is because TextInput
type itself has such a constraint like so.
public abstract class TextInput<T> where T: TextInput<T>{
//...
}
Also note that TextInput<T>
is abstract and the only way to make an instance of such class is to derive from it in a CRTP-like fashion:
public class FileUpload : TextInput<FileUpload> {
}
The extension method will not compile without that constraint, that's why it's there.
The reason for having CRTP in the first place is to enable strongly typed methods enabling Fluent Interface on the base class, so consider such example:
public abstract class TextInput<T> where T: TextInput<T>{
public T Length(int length) {
Attr(length);
return (T)this;
}
}
public class FileUpload : TextInput<FileUpload> {
FileUpload FileName(string fileName) {
Attr(fileName);
return this;
}
}
So when you have a FileUpload
instance, Length
returns an instance of FileUpload
, even though it's defined on the base class. This makes the following syntax possible:
FileUpload upload = new FileUpload();
upload //FileUpload instance
.Length(5) //FileUpload instance, defined on TextInput<T>
.FileName("filename.txt"); //FileUpload instance, defined on FileUpload
EDIT To address OP's comments about recursive class inheritance. This is a well known pattern in C++ called Curiously Recurring Template Pattern. Have a read of it here. Up until today I didn't know it was possible in C#. I suspect that constraint has something to do with enabling the use of this pattern in C#.
I can only guess what the code you've posted does. That said, I can see merit in a generic type constraint such as this. It would make sense (to me) in any scenario where you want an argument of some type that can perform certain operations on arguments of the same type.
Here's an unrelated example:
public static IComparable<T> Max<T>(this IComparable<T> value, T other)
where T : IComparable<T>
{
return value.CompareTo(other) > 0 ? value : other;
}
Code like this would allow you to write something like:
int start = 5;
var max = start.Max(6).Max(3).Max(10).Max(8); // result: 10
The namespace FluentHtml
is what should sort of tip you off that this is the intention of the code (to enable the chaining of method calls).
UPDATE: This question was the basis of my blog article on the 3rd of February 2011. Thanks for the great question!
This is legal, it is not circular, and it is fairly common. I personally dislike it.
The reasons I dislike it are:
1) It is excessively clever; as you've discovered, clever code is hard for people unfamiliar with the intricacies of the type system to intuitively understand.
2) It does not map well to my intuition of what a generic type "represents". I like classes to represent categories of things, and generic classes to represent parameterized categories. It is clear to me that a "list of strings" and a "list of numbers" are both kinds of lists, differing only in the type of the thing in the list. It is much less clear to me what "a TextInput of T where T is a TextInput of T" is. Don't make me think.
3) This pattern is frequently used in an attempt to enforce a constraint in the type system which is actually not enforcable in C#. Namely this one:
abstract class Animal<T> where T : Animal<T>
{
public abstract void MakeFriends(IEnumerable<T> newFriends);
}
class Cat : Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
The idea here is "a subclass Cat of Animal can only make friends with other Cats."
The problem is that the desired rule is not actually enforced:
class Tiger: Animal<Cat>
{
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
Now a Tiger can make friends with Cats, but not with Tigers.
To actually make this work in C# you'd need to do something like:
abstract class Animal
{
public abstract void MakeFriends(IEnumerable<THISTYPE> newFriends);
}
where "THISTYPE" is a magical new language feature that means "an overriding class is required to fill in its own type here".
class Cat : Animal
{
public override void MakeFriends(IEnumerable<Cat> newFriends) {}
}
class Tiger: Animal
{
// illegal!
public override void MakeFriends(IEnumerable<Cat> newFriends) { ... }
}
Unfortunately, that's not typesafe either:
Animal animal = new Cat();
animal.MakeFriends(new Animal[] {new Tiger()});
If the rule is "an animal can make friends with any of its kind" then an animal can make friends with animals. But a cat can only make friends with cats, not tigers! The stuff in the parameter positions has got to be valid contravariantly; in this hypothetical case we'd be requiring covariance, which isn't going to work.
I seem to have digressed somewhat. Returning to the subject of this curiously recurring pattern: I try to only use this pattern for common, easily understood situations like the ones mentioned by other answers:
class SortedList<T> where T : IComparable<T>
That is, we need every T to be comparable to every other T if we have any hope of making a sorted list of them.
To actually be flagged as circular there has to be a bona-fide circularity in dependencies:
class C<T, U> where T : U where U : T
An interesting area of type theory (that at present the C# compiler handles poorly) is the area of non-circular but infinitary generic types. I have written an infinitary type detector but it did not make it into the C# 4 compiler and is not a high priority for possible hypothetical future versions of the compiler. If you're interested in some examples of infinitary types, or some examples of where the C# cycle detector messes up, see my article on that:
http://blogs.msdn.com/b/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx