The purpose is genericity. Often, you have a class whose behavior does not really depend on the type it's used with.
Rather than Pair
, consider the .NET class List<T>
. It stores lists of... some type. The list doesn't, and shouldn't care what type it stores. It just has to guarantee that only that type can be stored.
A List<string>
allows me to store strings. A List<int>
allows me to store ints.
If we didn't have generics, we would have to either throw away type safety, and use a single List class which just stores Object
's, but then there would be nothing to prevent me from storing an Apple
in a list of Banana
s.
Or we could write a new List
class for every type we need to store. We could have ListOfApples
, ListOfBananas
, ListOfInts
and so on.
I think generics offer a nicer solution.
The Pair
example might be a bit less obvious, because often
, if we need to store precisely two values, it's because they have some clear relationship which should be represented by creating a specific object. But sometimes, they don't. Sometimes they are just "the first value" and "the second value". Perhaps you have a function which simply returns two values. It's easier then to return a Pair<T1, T2
, than to return one value and handle the other by having an out
parameter.
You often don't need a pair, no. But if you really need "a way to store two values of different types inside a single object", if there is no real behavior associated with it, then a Pair
class represents that nicely. And then why not make it reusable by allowing the types to change?
Also, how the heck can you handle error coding in something completely generic? I'd fear doing any kind of math manipulation or actually any kind of manipulation, when I have no idea what kind of datatypes I'll have to handle. If it requires me to write error-handling for all datatypes out there, then I REALLY can not see the advantages of these generic parameters.
Simple: .NET doesn't let you do that. In your Pair example, the compiler will complain if you try to use a function on it which is not guaranteed to be available. In your case, you'll only be able to use the functions defined on the Object
base class (and of course, generic functions and classes which also work on any type).
So you can't use any kind of math manipulation on these generic types. You can't do much more with them than storing them, and passing them around (and calling .ToString()
).
But sometimes, you don't need anything more than that. For example if you're just creating a List
class, or a Pair
.
You can also specify constraints, narrowing it down a bit. For example:
void DoStuffWithStream(T arg) where T : Stream
{
// in this function that T is some type derived from Stream, so we can use all methods that would work on a Stream.
}
This will produce a compile error if I try to call it with an int
, but will work with all the various Stream
subclassees. And now I know a bit more about the class, so I'll be able to do some operations meaningfully.
But going back to error handling, what errors would you need to handle in the List
class? Not a lot of errors can occur.
You have a class whose job it is to store a variable number of objects of some unknown (but fixed) type. It can insert new objects (which might throw an out of memory exception), and it can retrieve objects. It can let us search for objects, and then of course we might have the error case when the thing we searched for could not be found. But that's basically it. There is no error handling specific to the generic type. Why would there be? We don't do anything type-specific to the objects we store, so they don't really get an opportunity to throw any type-specific errors.