If you write C# on a daily basis, chances are that you use generic classes and functions on a daily basis as well (really, how could you not!). Using generic classes/functions is really easy - but the flip side of that coin, writing generic classes/functions, can be difficult. Today we are going to look at one of the tools in your arsenal for writing generic classes and functions - the where clause.
So when you are writing a generic class or method, by default that class or method can be used with any C# type - reference type, value type, random type from some other person's code - it could be anything. Now, sometimes, this is fine - you really don't care what the type will be - but other times you might like the list of possibilities to be somewhat more finite. This is where the where clause comes into play. It allows you to put various types of constraints on the type parameter for a generic method or function.
Take a look at the following example of a generic function:
where T : DependencyObject
{
if (obj == null)
return null;
T correctlyTyped = obj as T;
if (obj != null)
return correctlyTyped;
if (obj is Visual)
{
obj = VisualTreeHelper.GetParent(obj);
}
else
{
FrameworkContentElement fce = obj as FrameworkContentElement;
if (fce != null)
obj = fce.Parent;
else
throw new ArgumentException(
"Cannot Walk Parent Tree of " + obj.GetType().ToString());
}
return FindVisualParent<T>(obj);
}
This is a very handy (in my opinion) utility function for WPF that finding the first parent of the given type by walking up the visual tree from the given dependency object. But we don't really care about the utility at the moment - we care about the generics. So, as you probably saw, there is a where clause at the end of the function definition. This constrains the type T to be something derived from DependencyObject. This is extremely useful in this case because it doesn't make sense to try and find something in the parent tree that is not a DependencyObject.
There is another thing that having a constraint gets us in this case - we are able to return null out of the function. That's right, if we didn't have a constraint, we couldn't return null - because for some values of T, null would not be a valid value (remember - someone could pass a value type in). But because DependencyObject is an object (and therefore a reference type), we are allowed to return null.
Before we move on to more complicated examples, let's take a look at the various types of possible constraints.
where T: struct
This constrains T to value types (things like ints, bools, and more complicated structs) - pretty simple.
where T : class
This constrains T to reference types. Doing this allows you to use things like null.
where T : new()
This guarantees that T will have a public constructor that takes no arguments - generally you use this constraint if you will need to create a new instance of the type.
where T : <base class>
We already saw this one - it is what we were using in the above example. Using this, you are also automatically constraining the type to be a reference type. However, you are not necessarily guaranteeing that you get the ability to create new instances (even if the base class has a public no-argument constructor - because a sub class might not).
where T : <interface>
This type of constraint will guarantee that T implements the given interface. Because both classes and structs can implement interfaces, this does not constrain T to be a reference or value type.
where T : R
This means that the type supplied for T must be the same as or derive from the type supplied for R.
Ok, time for some more examples. Take a look at this handy extension method for IEnumerable, which puts two constraints at once on one of the types:
this IEnumerable<TInput> input, Converter<TInput, TOutput> convert)
where TCollection : ICollection<TOutput>, new()
{
TCollection output = new TCollection();
foreach (TInput item in input)
output.Add(convert(item));
return output;
}
This method takes an IEnumerable full of instances of TInput, converts them all to instances of TOuput, and adds these new instances to a collection of type TCollection, which is then returned. There are two constraints here on TCollection. First, that it implements ICollection<TOutput> - this makes sure that we can add instances of TOuput to the collection. And second, that we can create new instance of this collection (since all we are doing is requiring an interface, we have to call this out separately).
Below is some sample code showing how this function can be used:
myDoubles.Push(1.1);
myDoubles.Push(2.1);
myDoubles.Push(1.8);
myDoubles.Push(2.9);
var myInts = myDoubles.ConvertAll<double, int, LinkedList<int>>(
val => (int)Math.Round(val));
//myInts now has 3, 2, 2, 1 in it
Ok, now we have seen how to put multiple constraints on a single type (you just separate them by commas), lets take a look at how to put constraints on multiple different types. Take a look at the following:
where T : class, IComparable
where R : new()
{
//My Class Code
}
It is that simple, all you need to do is list them out like that, one where clause right after another. I did a class here just to show generics on a class, but is the exact same thing for functions (and everything that we have talked about above for functions also applies to generic classes).
A couple other random things to note here about constraints. One is that you can't put conflicting constraints on a type - you can't ask for both class and struct. Another is that you can't use a sealed class or an actual value type as a type constraint - it wouldn't really make sense anyway, because you can't derive from them (so there is only ever one exact type that will match). Oh, and for some reason, if you are using a new() constraint, it always needs to be the last constraint in the list for that type. I haven't seen a reason as to why that is the case - but you get a compile error if you don't.
And that is it for the where clause and what you can do with it. I recommend adding constraints when it does make sense - it makes thinking about the code you are writing a good bit easier, especially just by constraining to reference or value types. If anyone has any comments or questions on specific uses of the where clause, drop a comment below and I'll do my best to answer you.
01/08/2009 - 10:07
Hi Michael,
I'd like to share with you a problem that I've come across with generic functions. I lack your formal (ok, any) training in programming so I'm happy for you to tell me I'm doing it all wrong!
I have a fairly tall class hierarchy in the framework that I created for use with content management applications. Some might say too tall; for example, in the code I'm going to paste below, the inheritance tree goes: TDbBase -> TObjectBase -> TDynamicContent -> TOrderedDynamicContent -> TDynamicPage
Each level of the tree adds useful functionality (obviously!), the TDbBase providing certain methods to interact with the database, TDynamicContent providing methods and properties for content versioning etc.
To the problem: having quite recently drunk the generics kool-aid, I wrote several generic functions to enable the managing of any flavour of TOrderedDynamicContent object (nudging up or down in display order, editing, deleting etc) without all that "case"ing, casting and so on. All nice enough, but the technique I'm using in my class hierarchy for modifying some properties of derived classes away from their defaults (defined in a base class) is property / method hiding, which falls apart in a generic function, at least the way I'm doing it.
I've boiled down my problem to the following snippet. Essentially, the object of type T that's instantiated within the generic function can be a descendant of the constraint type in the "where" clause (e.g TDynamicPage : TOrderedDynamicContent : TDynamicContent), but methods called on that object will invoke the *constraint* type implementation (TDynamicContent) despite that being hidden in the derived type (TDynamicPage).
Am I making any sense? I feel that I may lack some of the language to describe the problem very succinctly.
So I want to invoke the implementation of whatever type is passed in - how would I do that?
Thanks for reading (if you got this far!)
Jon.
{
NonGenericThing();
GenericThing();
}
private void NonGenericThing()
{
TDynamicPage dp = new TDynamicPage();
string bob = dp.GetAddNewLink(); // Calls method on derived TDynamicPage object (desired).
}
private void GenericThing<T>() where T : TDynamicContent, new()
{
T dp = new T();
string bob = dp.GetAddNewLink(); // Calls method on TDynamicContent (not desired!) when GenericThing() is called with T : TDynamicPage.
}
01/08/2009 - 11:47
If I understand your problem correctly, it comes down to something like this (and actually doesn't have much to do with generics):
DynamicContent content = dp;
//Calls the GetAddNewLink on DynamicPage
string foo = page.GetAddNewLink();
//Calls the GetAddNewLink on DynamicContent,
//but you want it to call GetAddNewLink on DynamicPage
string bar = content.GetAddNewLink();
To get the behavior you want, you have to declare
GetAddNewLinkas a virtual function inDynamicContent, and override it inDynamicPage, like so:{
public virtual string GetAddNewLink()
{ return "I am DynamicContent"; }
}
public class DynamicPage : DynamicContent
{
public override string GetAddNewLink()
{ return "I am DynamicPage"; }
}
Does that answer your question?
01/08/2009 - 15:44
Yes it does!
I was unwisely using "new" in the derived classes to hide the base method. This worked as I expected when I was working directly with the derived classes (dp = new DynamicPage()) but not with the generic method.
As you say, this wasn't so much to do with generics. I had thought that when the generic method received an object of type TDynamicPage (which derives from TDynamicContent and therefore satisfies the type constraint) it would be an identical situation to simply passing a TDynamicPage object to a non-generic function.
Of course (I think!) the truth is that all the compiler knows is that T is of type TDynamicContent (it may be derived from that, but all it can be sure of is that it possesses at least the qualities of TDynamicContent).
When I set a breakpoint in the generic method and saw that dp was of type TDynamicPage I couldn't see why the base method would be called. My current understanding is that without the "virtual" hint in the base method the compiler will look no further than this method, whereas with the "virtual" hint it knows to seek any overriding methods. Am I anywhere near the truth?
As you can probably tell I'm still a bit muddled, mainly I think because I'm not entirely clear what is passed to the generic method and how it is interpreted (a TDynamicPage cast to TDynamicContent? But my development environment knows it's a TDynamicPage) but at least you've given me a working solution to my current problem, so thanks for that!
History may record that I should have stuck to Aero Eng...
01/09/2009 - 07:18
Yeah, your pretty much on target there. I just looked around the site and noticed that there is no tutorial about virtual/new/override - I’m going to put that on my list of things to write up.
The debugger is quite smart, which is why it was able to show you that the object was actually a TDynamicPage - and I can see why that would confuse things in this case.
01/09/2009 - 07:50
Thanks, it’s good to know I’m not completely off track.
I’ll be checking out your tutorial when you find time to write it! When I was poking around I found a fairly helpful MS page on this subject at http://msdn.microsoft.com/en-us/library/ms173153.aspx which answered some of my questions.
Thanks for your advice,
Jon.
06/09/2010 - 07:29
Thanks for a good post, the part about multiple generic types with multiple constraints was what I needed!
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.