Using a Generic return type that does not appear in the parameter list – when to use it

EDIT: Contrary to what people understood from this post, it is NOT a “Do not use Generics” post – I think Generics are a GREAT addition to the language/framework and I use them all the time. It is against a very specific usage of Generics

 Short Answer: Rarely.

Slightly longer answer: When you are aware of the alternatives.

Yet longer answer:

I have seen this a couple of times recently and thought I’d drop a line about it. As always, take it with a grain of salt, but here goes…

You find yourself wanting to write a method that returns a value to the caller. Said value can be of many types and you decide to create a template out of it – a recent example I saw (which is very typical to the misuse of this mechanism):

T GetRegValue<T>(string path); // Get the value of a registry path

This method calls into the Win32 wrappers and gets back an Object, the code then returns it to the caller in the following manner:

T result = (T)objectResult;

I have heard many reasons as to why people choose to use this mechanism – the two that come up most often:

1. It’s easier to understand/write/maintain or it is “cleaner”.
2. It’s more type-safe.

Lets go over those one by one…

It’s easier to understand/write/maintain or it is “cleaner”

The amount of code that needs typing in this case is exactly the same as if you had returned an object. Compare and contrast:

string result = (string)GetRegValue(“path”); // This is when using the “old and boring” way
string result = GetRegValue<string>(“path”); // This is when using the “new and shiny” way

It’s EXACTLY the same amount of code characters and I am pretty sure more people know how to read/parse casting operators (the (string) bit) than generic type parameters (the <string> bit). So it’s the same amount of code (not easier to write), it’s using a mechanism that less people know (not easier to understand). That leaves “easier to maintain” which I usually equate to “easier to understand”.

We are left with it’s “cleaner” but that’s a whole new blog entry – to me both mechanisms look alike from the cleanliness point of view.

It’s more type-safe

This is just not true. In the typical example, a cast is made to the requested type (T in this generic method) – casts in .NET are safe (that is to say, they will throw an exception of they do not work and if they do work they are guaranteed to work properly). The code doesn’t even eliminate the cast – it just moves it to the inside of the method.

Here’s my rationale behind why such a mechanism should not be used:

1. It’s harder to debug.
2. It doesn’t contribute anything.

It’s harder to debug

In the cases where the type of the retrieved cannot be cast, your exception will be thrown inside the inner method which may throw you off when you are trying to figure out what’s wrong. It’s not a big deal, especially if you own the library that is making the call, but it’s just a little harder to understand what went wrong.

It doesn’t contribute anything

Generics are a more advanced language construct that casting and as such is harder to understand. At the end of the day, you are getting the same result and so should be using the simplest mechanism possible.

So when should I use this?

There are cases where this can be useful – for example, when some property of the return type is used to make the method smarter. This is where you go onto the gray line. The above snippet, when presented to me, actually looked a little different:

T result = Convert.ChangeType(objectResult, typeof(T)); // Used to be T result = (T)objectResult;

This is a subtle difference, but an important one. Here we are asking the runtime to force one type unto another. To understand the difference, consider the following call (first one is the generic version and the second the non-generic):

double d = GetRegValue<double>(“path”);
double d = (double)GetRegValue(“path”);

In this case, the fancy version will actually work whereas the non-generic one will fail with an InvalidCastException. The reason behind this is simple – the API method used to fetch the value from the registry returns a boxed integer. A boxed integer can only be cast into two things – an Object or an Integer – it can’t be cast to any other type. For this to work, the caller would have had to do one of the following:

double d = (double)(int)GetRegValue(“path”); // Cast the object into an int and then the int into a double.
double d = Conver.ChangeType(GetRegValue(“path”), typeof(double)); // Coerce the value.

Both these choices ARE indeed somewhat less readable and perhaps less maintainable than the generic alternative – I personally could go both ways on this – not married to either of them. In the case of Registry access, the values are usually known beforehand and thus you probably know exactly what you are looking for so I would probably go with an object retval.

Comments (5)

  1. Jeff Yates says:

    While I mostly agree with your points, I am a little concerned that you advocate other people’s ignorance as a reason not to do things.  Just because others aren’t versed in all areas of the language doesn’t mean we should dumb down our own coding efforts.

    We should use the powerful elements of C# with care, making sure to apply the right solution in the right situation, but we should never shy away from a good solution because other people may not have learned enough to understand it.

  2. MSDNArchive says:

    Jeff’s comment deserves repetition.  There are people who specifically avoid learning new or advanced language features, for whatever poor reason they have.  For my part, I refuse to let them get away with it.  Generics are a valuable skill, and contrary to rumor are *not* difficult to comprehend. [if they are difficult, I recommend a career change, or at least a platform change.  I’m not being snooty about this:  generics as of .NET 3.5 are much more prominent, and will only continue to spread.]

    Incidentally, be careful not to mistake generic usage.  There are a fair number of cases where the type parameter *is* an input argument.  Hardcoded, perhaps, but an argument nonetheless.  For an example, consider the Cast<T> operator in LINQ —  IEnumerable<T> Cast<T>(this IEnumerable) — or the OfType<T> operator.

  3. Shahar says:

    You totally missed my meaning.

    Generics are great and should be used – where it makes sense. I was just pointing out a place where they add nothing but complexity both in understanding and in debugging.

    While Generics are not as powerful as C++ Templates, they are still pretty powerful in what they do and can make code both much more readable, maintainable AND "safer". Not to talk about potential performance improvements (though in a fair percentage of real-world situations, those are not very significant).

    All I was talking about was this SPECIFIC use of Generics.

  4. Jeff Yates says:

    I just wanted to set the record straight.  I was not and am not implying Shahar hates generics, in fact, I said I agree with just about everything he said.  I was just concerned with the point:

    "It’s EXACTLY the same amount of code characters and I am pretty sure more people know how to read/parse casting operators (the (string) bit) than generic type parameters (the <string> bit). So it’s the same amount of code (not easier to write), it’s using a mechanism that less people know (not easier to understand). "

    This implied that it was good to consider ignorance as a reason not to do something and I wanted it point out that this really shouldn’t factor in to programming decisions. Shahar’s point was made without this poor justification because, from the other justifications, it is quite clear that using generics IN THIS INSTANCE is the wrong choice, regardless of whether people understand generics or not.

    Right, enough of everyone getting misunderstood. 🙂  Keep blogging!