Quickie Dev #6: Using generics in C#

Because abstraction can save you lots of code repetitions!

This article is also available on Medium.

C# generics are a way of defining data type-specific behavior without repeating code over and over again. When you define a generic method or class, you essentially define an empty model, a blueprint than can then be instantiated and that doesn’t know (or knows only partially) what data type it is working on. From a programmer’s point of view, the unknown data type(s) are labelled and booked in memory, just like a variable:

As you can see, this type placeholder, written between “less-than” and “more-than” signs in the class or the method’s prototype, can be used in the fields and methods of the generic class, or inside the generic method.

Because they are just abstract blueprints, generics cannot be used as-is; when you use a generic class or method, it has to be instantiated by specifying the data type that should be substituted to the matching placeholder:

Generics are very interesting because, once instantiated with the particular data type for the task at hand, the whole type and flow checking can be applied – in other words, if I use a string instance of my generic method or class, the rest of the code will know that I actually have a string and it will proceed to do all the checks as usual with this in mind. This is because C# generics don’t do runtime type casting but rather they do the type conversion at compile-time: this provides automatic type safety.

Also, as we’ll see very soon in small examples, generics reduce the amount of code you need to write and avoid inconsistency. They are also more efficient than boxing/unboxing value types (i.e. converting to and from the “lazy” object type) and, thanks to constraints, you can implement a well-defined interface even though you don’t precisely know what data you act upon.

Note: by the way, remember that a generic can have as many type placeholders as you want! The most common cases use only one or two (for example for hash tables and other dictionary-like containers) but you may in fact have plenty more 😉

Generic methods

A generic method is a C# function that has one or more type parameters. These placeholder types may be used for the input argument types and/or for the return value.

I myself use generic methods in two ways:

  • either to implement a shared behavior on multiple “alike” data types, like a list of ints and a list of strings
  • or, to the contrary, to distinguish between data types and specify the implementation depending on this type

That might seem counter-intuitive – it looks like those are antithetical things to do, right? But let’s look at both those use cases with a simple example.

Example 1: Getting a random object in a container

As a big fan of procedural generation, something I do very frequently in my Unity/C# projects is setup engines to auto-create my levels, filled with cool textures and props! But one issue with this process is that you might have repetitive objects in your scene.

Say you are making a cow-boy game – you’ll definitely need to put some wooden barrels in there to make it more lifelike! At first, you’ll probably have that one and only 3D model, so you’ll copy it around no questions asked. The problem is that the player will quickly see the trick: this little crack at the top that is just repeated on every barrel sort of breaks the illusion. To avoid this, a common trick is to have variations on your 3D objects. Basically, instead of saying: “this is my wooden barrel scene prop”, you’ll decompose the instantiation mental process in two steps:

  • firstly, I know I want to instantiate a wooden barrel
  • secondly, let’s try and mix it up so we use different variations of my wooden barrel object every time

Similarly, you might also want to pick the texture of the barrel at random so that it sometimes uses the “light wood” texture, and at other times it uses the “dark wood” one.

Now, suppose both your model variations and textures are stored as lists (one list of 3D objects, and one list of 2D images). As C# is a typed language, you know you can’t simply create a common plain “get random item” function that will take 3D objects or textures. So your first instinct may be to create 2 functions: GetRandom3DObject() and GetRandom2DTexture().

Although it would work, it poses two big problems:

  • it is not easily scalable: if you want to do the same for your 2D sprites, your animation clips and your game sounds, you’ll need to implement 3 more functions! This will soon clutter your script with lots of almost identical functions – plus you’ll have to remember the name of each one…
  • it is highly inconsistent-prone: if you change the way one function behaves, you’ll either need to update all the others or accept (and remember!) that one behaves differently – this is usually a very bad idea because it opens the door to lots of “silly” mistakes that you take a long time tracking down (because of course – who could assume logically that two GetRandomX() methods don’t behave the same?)

To avoid all of this, we can take advantage of generic methods. Instead of our multiple almost-identical functions, we can make just one called GetRandomItem():

And voilà! Now we’ll simply pass in the data type between “less-than” and “more-than” signs to instantiate our generic method, and the program will be able to pass the data to the method properly:

Example 2: Printing a variable differently depending on its type

On the other hand, sometimes it’s important to know what type of data you’re working on. Generic methods are often used to create specific behaviors depending on what data type it eventually receives.

But how can we know the data type since it’s only passed in as a placeholder with the type parameter? Thanks to another amazing C# feature: reflection. I won’t go into all the details of this other tool because it would require several articles to even get started; but in short, reflection is about dynamically interacting with your object types. Be it to create an object with a dynamically computed type, or to get the type of variable that is not knowable beforehand by the programmer, as is the case here, it is a very powerful feature that lets you write highly abstract code.

So let’s write a little generic function that prints a variable in different ways depending on its type; we are just going to change the log before the actual variable value print to specify its type, and we’re also going to add a default case because we won’t be handling all of the possible data types:

As expected, this prints out the following (the double type is not handled by an if-branch and therefore falls in the default case):

[ String]: hello
[Boolean]: True
[Integer]: 42
[  Float]: -3,4
-Default-: 2

Of course, this is a simple example – but I’ve actually used this exact process to make a custom Unity editor that displays some graph nodes: because the nodes are generic classes and contain generic data, I couldn’t know in advance what types the node fields would have – so I used this technique to get the variable type at the moment the editor requires the display, and it allowed me to show a custom UI element for each data type 🙂

Generic classes

Similarly, we can create generic classes to have more complex behaviors that depend on a not-yet-specified data type. In the example I showed above of my Unity node editor, you actually derive custom nodes (with their specific properties) from a basic class, the BasicNode. Then, you can create your custom tree as an instance of the generic class, BasicTreeGeneric:

This let me define common tree functions in this generic class but then insert whatever type of node and edge I want in the final compute.

In my generic class definition, I used the where keyword to enforce constraints on my type parameters: the node and edge types cannot be just any object, they have to implement the same interface as the BasicNode and BasicEdge classes. This is useful to maintain type safety (and, additionally, help the IDE with auto-completion 😉 ).

You can also notice that generics can be “nested”: here, my edge type is partially defined with my node type, and then my tree type is defined with both my node and my edge types.

Note: methods in a generic class are not necessarily generic methods! As recalled in the Microsoft docs: “a method is generic only if it has its own list of type parameters”.

Conclusion

This was just a quick overview of the C# generics feature – there is plenty more to say! If you’re interested, I definitely recommend you check out the Microsoft docs that give more details on the terminology, the advantages and the limitations of this tool.

What about you: do you have ideas of nice things we could make using generics? Don’t hesitate to post a comment below! 😉

Leave a Reply

Your email address will not be published.