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 where T : Animal { public abstract void MakeFriends(IEnumerable newFriends); } class Cat : Animal { public override void MakeFriends(IEnumerable 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 { public override void MakeFriends(IEnumerable 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 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 newFriends) {} } class Tiger: Animal { // illegal! Public override void MakeFriends(IEnumerable 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 where T : IComparable 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.
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 where T : Animal { public abstract void MakeFriends(IEnumerable newFriends); } class Cat : Animal { public override void MakeFriends(IEnumerable 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 { public override void MakeFriends(IEnumerable 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 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 newFriends) {} } class Tiger: Animal { // illegal!
Public override void MakeFriends(IEnumerable 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 where T : IComparable 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 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.
Thanks Eric, it is an interesting area. I have a few other questions around constraints but I guess I should post them as another question :-). – Simon Francesco Sep 26 '10 at 21:48.
The reason constraint is there is because TextInput type itself has such a constraint like so. Public abstract class TextInput where T: TextInput{ //... } Also note that TextInput 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 { } 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 where T: TextInput{ public T Length(int length) { Attr(length); return (T)this; } } public class FileUpload : TextInput { 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 . 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#.
Igor I think your comment about the type inheriting from a constrained type is getting is part of the issue that it won't compile without the constraint. But my real question is about the syntax though, where T seems to refer to the both the type and the constraint ie both TextInput of T and T itself which seems ambiguous. – Simon Francesco Sep 24 '10 at 0:56 Thanks just had a read of the CRTP.
Chuckle, my example has a 'this is by design' type feel, in the sense that you can define circularity as unintended recursion, or in this case what looks like circularity is really recursion. It also shows how hard it is to become fluent in a given programming language :) – Simon Francesco Sep 24 '10 at 1:35 Haha, I was chucking at you using the term fluent. That is very appropriate here as this pattern is known as Fluent Interface (method chaining).
– Igor Zevaka Sep 24 '10 at 1:46.
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 where T : IComparable 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 AddNullItem(this List list, bool value) where T : List { list. Add(null); return list; } The reason it won't compile without a constraint is that value types can't be null. List is a reference type, so by forcing where T : List 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, etc. The correct constraint is: /* corrected constraint so the compiler won't complain about null */ public static List AddNullItem(this List 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 AddNullItem(this List 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.
I agree about the making no sense, but it compiles and does what I want it to. Your example is too simple to capture the use case. – Simon Francesco Sep 24 '10 at 0:17 Sorry I hit enter, I meant to continue... I am thinking of a List where I could have a list of bananas and have an extension method such as list.AddNullItem() so the generic parameter, is itself generic.
– Simon Francesco Sep 24 '10 at 0:25 I hope the additional information I added will help you to see why it didn't compile without the constraint, but the constraint is not necessarily correct. – Ben Voigt Sep 24 '10 at 0:28 Thanks for your efforts Ben, I appreciate it, I think on balance I should remove the analogy, because it does not drive at the issue I am trying to solve. I also knew at the time that the primitive vs object issue might cloud the question.
I am going to edit my original question to do this. – Simon Francesco Sep 24 '10 at 0:35.
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 Max(this IComparable value, T other) where T : IComparable { 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).
Public static TextInput ReadOnly(this TextInput element, bool value) where T: TextInput Let's break it down: TextInput is the return type. TextInput is the type being extended (the type of the first parameter to the static method) ReadOnly is the name of the function that extends a type whose definition includes T, i.e. TextInput.
Where T: TextInput is the constraint on T from ReadOnly, such that T can be used in a generic TextInput. (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.
You have all the same assumptions as I do except we draw different conclusions. It feels circular to me because using mathematical or logical substition the constraint implies that we should be able to do something like this: ReadOnly where T : TextInput => ReadOnly where T : TextInput => ReadOnly .... rinse and repeat ad infinitum :). – Simon Francesco Sep 24 '10 at 1:21 if you think of where T: TextInput as just saying, "where T is the generic part of TextInput", it should click.Hopefully.
:) – Jeff Meatball Yang Sep 24 '10 at 1:46 In other words, don't read the where clause as logical substitution. It's not. It's meant to define how T is related to another object, which is more like composition rather than substitution.
– Jeff Meatball Yang Sep 24 '10 at 1:49 Sorry Jeff, but that constraint means "T is a subclass of (inherits from) TextInput" – Ben Voigt Sep 24 '10 at 3:59.
I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.