Skip to content

Categories:

Using generic methods and constraints

Generic methods are a powerful way to provide type safety yet creating flexible methods that support multiple types. The idea behind a generic metode is simple: A generic method accept parameters and returns values of a range of different types. Hence it is generic, opposite to specific. But there is a little more to the story.

Let us first look at a non-generic way to accomplice this: That is making methods that accepts and returns objects of some base class or interface: E.g. many methods accepts a Stream as a parameter - this means that they will work on all the specific streams in .NET (FileSteam, MemoryStream and so on). 

This kind of non-generic, yet type flexible, method works great in many scenarios. But in some scenarios, generic methods offer stronger typesafety and better performance:

An common would be, that you need to return a object of some unknown type, instead of accepting it as a parameter: Where using a  FileStream as a Stream object in a parameter provides flexibility, returning a FileStream as a Stream, like in this example, may mean that the calling code may have to cast the returned Stream back to a FileStream:

public Stream NonGeneric(Stream stream)
{
    //do something
    return stream;
}

So why is this bad? What happens when you supply this method with a FileStream, is that the method returns the same FileStream, but now as a Stream. The returned object is still a FileStream, but for all the calling code knows, the returned object could be any kind of Stream.

So to use some of FileStream methods, the calling code may have to cast the Stream back to a FileStream. Since the returned Stream is in fact a FileStream, this cast goes well. But first the CLR check whether the Stream is in fact a FileStream (or can be cast to one) by examine the objects metadata.

This is done runtime and is in fact time consuming. What also happens, is the if you for some reason change the input parameter to a MemoryStream, the code including the FileStream cast will still compile. But at runtime you will get a InvalidCastException. Hence this makes the code more error-prone.

Generic methods

Generic methods offers a less errorprone and more efficient way do the exact same. The idea is, that you simply supply the specific type (FileStream) as a parameter together with the ordinary parameter. This extra parameter is called a type parameter.

Now the function knows exacty what kind of Stream it should return, and yet you can still use the same method later with some other stream, by supplying some other type os stream.

Inside the method, you still have to threat the stream parameter as a Stream – otherwise the method would not be generic (work on different streams). But outside the method, a specific stream goes in, and the same specific type of stream comes out. In C# syntax, this looks like:

public T GenericMethod<T>(T stream) where T : Stream
{
    // do something
    return stream;
}

The type parameter is conventional called T, but this is just a convention, so in my next example, I will call it TheType. Also notice the where clause concluding the methods signature. This constaints the types accepted to types that inherit from Stream. Otherwise the method would accept all kind of types, which would make the method more generic that we would like. Often constaints are needed, so let’s take a look at another constraint:

public TheType New<TheType>() where TheType : new()
{
    return new TheType();
}

This method is called New, and takes one type parameter, TheType. There is no ordinary parameters. The method returns a object of the type supplied as a parameter. Because of the where clause, this method will only accept types that have a parameterless constructor.

The reason why this constraint is needed, is because the method constructs a new instance of the supplied type, and returns this object. Because this is done with the new keyword, we have to make sure that the supplied type parameter is in fact a type with a parameterless constructor. As before, this constraint is checked at compile time. Your code will simply not compile if the supplied type does not meet the constraint.

Calling generic methods

When you call a generic method, you supply the class name as type parameter. Is method above is wrapped in a class called GenericsExamples, I would call:

GenericsExamples ge = new GenericsExamples();
DateTime dateTime = ge.New<DateTime>();

On a side note, you may find it a little strange that the type parameters are not System.Type instances, but simply class names. This is to make the code easy to read.

The next method demonstrate how to constraint the type parameters to type implementing a specific interface. The is needed because I am using the the Convert.ChangeType, which requires a that the source type implements IConvertible:

public U Convert<T, U>(T value) where T : IConvertible
{
    return (U)Convert.ChangeType(value, typeof(U));
}

Calling this method will convert a supplied instance of the class T to a instance of the class U:

Other kind of constraints

We have now seen three ways to constraint the type parameters accepted: By base call or interface or by having a parameterless constructor. There is three more constraints, where T: struct means that the type parameter T must be a value type, excluding enums. The constraint where T: class means the the type parameter must be an object (same as where T : System.Object). The last constaint is where T : U. This simply means that the type parameter T, must derive from some other type parameter U.

 

Generic classes

Type parameters can also span the entire class. Then the class is called generic. The type parameter is the moved to the declaration of the class. This means that you do not have to supply the type parameter in each method call, but only once, when you declare the class:

public class GenericClass<T> where T :new()
{
    public T New()
    {
        return new T();
    }
}

When declaring this class, you will have to supply a type that meets the new constraint:

GenericClass<DateTime> gc = new GenericClass<DateTime>();

Following, the generic class can be employed without type parameters:

gc.New().AddMonths(1);

kick it on DotNetKicks.com

Posted in C#.

Tagged with , , , , , , , , , , .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.