I don't remember when I wrote generic class last time. Every time I think I need it after some thinking I make a conclusion I don't.
The second answer to this question made me to ask for clarification (since i can't comment yet, i made a new question).
So let's take given code as an example of case where one needs generics:
public class Repository<T> where T : class, IBusinessOBject
{
T Get(int id)
void Save(T obj);
void Delete(T obj);
}
It has type constraints: IBusinessObject
My usual way of thought is: the class is constrained to use IBusinessObject
, so are the classes that use this Repository
are. Repository stores these IBusinessObject
s, most likely clients of this Repository
will want to get and use objects through IBusinessObject
interface. So why not just to
public class Repository
{
IBusinessOBject Get(int id)
void Save(IBusinessOBject obj);
void Delete(IBusinessOBject obj);
}
Tho, the example is not good, as it is just another collection type and generic collection is classics. In this case type constraint looks odd too.
In fact the example class Repository<T> where T : class, IBusinessbBject
looks
pretty much similar to class BusinessObjectRepository
to me. Which is the thing generics are made to fix.
The whole point is: are generics good for anything except collections and don't type constraints make generic as specialized, as the use of this type constraint instead of generic type parameter inside the class does?
Best Answer
Let's first talk about pure parametric polymorphism and get into bounded polymorphism later.
Parametric Polymorphism
What does parametric polymorphism mean? Well, it means that a type, or rather type constructor is parameterized by a type. Since the type is passed in as a parameter, you cannot know in advance what it might be. You cannot make any assumptions based on it. Now, if you don't know what it might be, then what is the use? What can you do with it?
Well, you could store and retrieve it, for example. That's the case you already mentioned: collections. In order to store an item in a list or an array, I need to know nothing about the item. The list or array can be completely oblivious to the type.
But what about the
Maybe
type? If you are not familiar with it,Maybe
is a type which maybe has a value and maybe doesn't. Where would you use it? Well, for example, when getting an item out of a dictionary: the fact that an item might not be in the dictionary is not an exceptional situation, so you really shouldn't throw an exception if the item isn't there. Instead, you return an instance of a subtype ofMaybe<T>
, which has exactly two subtypes:None
andSome<T>
.int.Parse
is another candidate of something that really should return aMaybe<int>
instead of throwing an exception or the wholeint.TryParse(out bla)
dance.Now, you might argue that
Maybe
is kinda-sorta like a list which can only have zero or one elements. And thus kinda-sorta a collection.Then what about
Task<T>
? It's a type which promises to return a value at some point in the future, but doesn't necessarily have a value right now.Or what about
Func<T, …>
? How would you represent the concept of a function from one type to another type if you cannot abstract over types?Or, more generally: considering that abstraction and reuse are the two fundamental operations of software engineering, why wouldn't you want to be able to abstract over types?
Bounded Polymorphism
So, let's now talk about bounded polymorphism. Bounded polymorphism is basically where parametric polymorphism and subtype polymorphism meet: instead of a type constructor being completely oblivious about its type parameter, you can bind (or constrain) the type to be a subtype of some specified type.
Let's go back to collections. Take a hashtable. We said above that a list doesn't need to know anything about its elements. Well, a hashtable does: it needs to know that it can hash them. (Note: in C#, all objects are hashable, just like all objects can be compared for equality. That's not true for all languages, though, and is sometimes considered to be design mistake even in C#.)
So, you want to constrain your type parameter for the key type in the hashtable to be an instance of
IHashable
:Imagine if instead you had this:
What would you do with a
value
you get out of there? You can't do anything with it, you only know it's an object. And if you iterate over it, all you get yielded is a pair of something you know is anIHashable
(which doesn't help you much because it only has one propertyHash
) and something you know is anobject
(which helps you even less).Or something based on your example:
The item needs to be serializable because it is going to be stored on disk. But what if you have this instead:
With the generic case, if you put a
BankAccount
in, you get aBankAccount
back, with methods and properties likeOwner
,AccountNumber
,Balance
,Deposit
,Withdraw
, etc. Something you can work with. Now, the other case? You put in aBankAccount
but you get back aSerializable
, which has just one property:AsString
. What are you going to do with that?There are also some neat tricks you can do with bounded polymorphism:
F-bounded polymorphism
F-bounded quantification is basically where the type variable appears again in the constraint. This can be useful in some circumstances. E.g. how do you write an
ICloneable
interface? How do you write a method where the return type is the type of the implementing class? In a language with a MyType feature, that's easy:In a language with bounded polymorphism, you can do something like this instead:
Note that this is not as safe as the MyType version, because there is nothing stopping someone from simply passing the "wrong" class to the type constructor:
Abstract Type Members
As it turns out, if you have abstract type members and subtyping, you can actually get by completely without parametric polymorphism and still do all the same things. Scala is heading in this direction, being the first major language that started out with generics and then trying to remove them, which is exactly the other way around from e.g. Java and C#.
Basically, in Scala, just like you can have fields and properties and methods as members, you can also have types. And just like fields and properties and methods can be left abstract to be implemented in a subclass later, type members can also be left abstract. Let's go back to collections, a simple
List
, that would look something like this, if it were supported in C#: