Generics in C# and Unity - Do More with Less Code

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Generics are useful to reduce the amount of code needed in a project. Generics allow code to be reused over and over with different types. This means less code to write, less to debug and hopefully your project gets finished just a little earlier! Despite looking a bit scary they aren't all that difficult to use.

Hopefully, this is helpful and helps to demystify generics for someone :)

👍︎︎ 1 👤︎︎ u/OneWheelStudio 📅︎︎ Jan 14 2021 🗫︎ replies
Captions
generics and c-sharp may sound scary and look scary but the truth is you're probably already using them even if you don't know it so let's try to talk about them let's make them a little less scary and hopefully add another tool to your programmer toolbox if you're looking for something in particular make sure to check out the timestamps in the description below now if you've been using unity for any period of time you've almost for sure used the get component function i know when i first saw this function i simply accepted the fact that this function required some extra information inside of angle brackets and i didn't think much about it a little later on i learned to use lists and those had a similar requirement again i didn't question why the type that was being stored in the list needed to be added so differently when compared to creating other objects it just worked and i went with it well it turns out that lists and the get component function are taking in what's called a generic argument that's what's inside the angle brackets the generic argument is a type and it helps the object know what to do in terms of the get component function it tells it what type to look for and with a list it tells the list what type of thing to be stored inside of that list generics are just placeholder types they can be any type they can also be constrained in various ways and it turns out this functionality is pretty darn useful but why would you want to use generics beyond the built-in methods provided by unity well essentially they allow us to create code that is more general even generic so that it can be used and reused generics can help prevent the need for duplicate code and the less code that we have the less code that we need to debug and the faster our projects can get finished we can see this with the get component function that it works for any and all types of mono behavior this one function can be used for all built-in types or types created by a programmer it would be a huge pain for every modern behavior that we created we had to write a new custom get component function the same is true of lists they can hold any type of object and when you create a new type you don't have to create a new version of the list class to contain that new type so yeah generics can help reduce how much code needs to get written and that's why we use them so let's take a look at an example of creating our own custom generic function i've created a simple scene with some code that generates a grid of random shapes that's not the important part but i will include a link to the files in the description below if you want to take a closer look at that piece of code the important part is that i have four classes defined the first is a basic shape class the cube sphere and capsule class each inherit from then each of these shapes which happens to be a prefab have the corresponding name class attached to them now all these classes are empty and are primarily used to add a type to a prefab now personally i do this fairly often in my projects it's a way to effectively create tagged objects without the weekly typed tag system built into unity i find that it's an easy and convenient way to get a handle on scene objects in code that feels more robust and reliable than relying on weekly type strings but more to the point these types and these classes allow us to leverage generics when the grid is generated and the shapes are instantiated they are added to a list that holds the type shape this is done as a way of keeping track of what's currently in the scene and this could also be done on demand by using the functions such as find all objects of type which happens to be generic but that isn't particularly performant as find all objects of type would have to be called frequently with that said let's imagine you want to find all the cubes in the scene that's not too hard you could write a function like the one here function iterates through the list of scene objects checking the type shape and then adds all the cubes that it finds to a new list and then returns that list of cubes and then you can imagine that we might want to find all the spheres in the list and to do that we can just copy the cube function and change the type of the list we're returning and the type we're trying to match and again we could do the same thing with capsules copying the function and changing the type but now we have three functions that do almost the exact same thing and that that should be a red flag and worse than that if we want to add a fourth shape to our grid we need to create a fourth function that can find that new shape and this is quickly going to become unsustainable if we add more shapes or types to our project so there's got to be a better way to do that and that way is using generics the only difference between the three functions we've created is the type the type in the list we're returning and the type that we're trying to match when we iterate through the list of scene objects we are doing the same steps for every type and that is the exact problem that generics solve so let's make a generic function that will work for any type of shape we do this by adding a generic argument to the function in the form of an identifier between the angle brackets after the function name and before any input parameters now traditionally the capital letter t has been used for generic arguments but you can use anything that you like the generic argument is the type of thing we're using in our function which for our case is also the type stored in the list and this is the type that we are trying to match so we can simply replace the particular types in our shape find functions with a generic type of t you know that we still have the type of shape in our function this is because the list of all the scene objects is storing types of shape so in this case that type is not generic we will come back to this towards the end of the video and add a second generic argument to make this function even more generic and allow us to search through any list and return a list of sub classes in my example project i have ui buttons wired up to call this generic function so that cubes spheres and capsules can be highlighted in the scene because we now have a generic function each button can call the same function just with a different generic argument and the result we have less code the same functionality and the ability to find new shapes if new types are added to our project without having to create code for each type of shape now you may have noticed that i snuck in a little extra unexplained code at the top of our generic function this extra bit is a constraint by using the keyword where we are telling the compiler that there are some limits to the type that t can be in this case we're constraining t to be a subclass of shape or more specifically we are defining the generic type of t where t will be of type shape we need to do this otherwise the casting of objects in the list to type t would throw an error as the compiler doesn't know if the types can be converted constraints can be very wide or very narrow in my experience primarily with unity constraints to a parent class like we did above or to a monobehaviour or even a component happens to be a parent of model behavior are very common and useful without a constraint the compiler assumes the type is object which while very general there are limits to what can be done with a base object and much of what you'd likely want to do will require more specificity for example maybe you want to destroy objects of a given type throughout your scene this isn't too hard to do but it is another good example of a custom generic function to do that we can use the find all objects of type which returns a unity engine object which is too general for our purposes but if we constrain t to be component or a mono behavior we can then access the attached game object and destroy that game object you can find more about constraints in the c-sharp documentation and i'll include a link in the description below in my personal game project i often need to check and see if the player clicks on a particular type of object i often just need a true or false value returned if the cursor is hovering over a particular object type and no surprise this is a perfect use for a generic function a raycast from the camera to the mouse can return a raycast hit if that hit is not null we can then check to see if that object has a component and this is all pretty normal raycasting type stuff but rather than check for a particular component we can check for a generic type note once again that we need a constraint and that the generic type needs to be constrained to be a component then to use this function we simply call it like any other function but tell it what type to look for by giving it a generic argument at the end of the day it's not too complex and this is definitely reusable now as a somewhat tangential tip many generic functions can also operate as static functions so to maximize their usefulness i often place my generic functions in a static class so they can be easily accessed throughout the project which in turn often means even less code duplication okay so let's return to the first generic function that searched through a list of shapes this function can search through any list of shapes and find any type of shape within that list which is great but it's still specific to shapes so we can go a step further down the rabbit hole of generics and make this function search through a list of any type and return a list of any type of subclass and we can do this by introducing a second generic argument and we can call it t class as it will be a parent class and use it in place of the type shape likewise we can change t to t subclass as it will be a subclass of t class now the renaming doesn't change anything on a functional level it's simply intended to make the code more readable notice how the constraint has also changed and it itself is now more generic to go a step further into our example while this should be a very generic function we might want to only use this function with lists of model behavior to do that we need to constrain t class to model behaviors and we do that by simply adding a second constraint like so now so far we've looked at generic functions which i think are by far the most common and likely use of generics in unity but we can also make generic classes and even generic interfaces and these operate much the same way that a generic function does in the sense of including a generic argument in the definition along with potential constraints on the type for example here's a generic class with a single generic argument it also has a variable of type t and a function that has an input parameter and a return value of type d notice that the type t is defined when the class is created and not with each function the functions make use of the generic type but do not require a generic argument themselves you can introduce additional generic arguments per function but they will need to be new arguments also worth noting is that this class is a mono behavior and as is unity will not be able to attach this to a game object since the type t is not defined however if an additional class is created that inherits from the generic class and defines the type t then this new additional model behavior can be attached to a game object and used as a normal model behavior the uses for generic classes and interfaces are highly dependent on the project and not super common frankly it's difficult to come up with good examples that are reasonably universal so an imperfect example of a generic class might be an object pooling solution where there is a separate pool per type if you're new to object pooling make sure to check out my video on object building as i'm just going to give this as a very quick albeit somewhat complicated example now inside each pool there is a queue of type t a prefab that will get instantiated if there are no objects in the queue plus functions to return and get objects from that pool the clumsy part here comes from assigning the prefab which must be done manually but isn't too high of a price to pay as each pool can be set up in an on enable function on some sort of pool manager type object something like this the pool class is static and is per type which makes it very easy to get an instance of a given prefab during runtime in this case we are equating a type with a prefab which could cause some problems or confusion so just something to be aware of if you decide to use something like this so it turns out that a lot of what can be done with generics can also be done with inheritance with a good amount of typecasting and it might turn out that generics are not the best solution and using inheritance and casting can be a better or simpler solution but in general using generics tends to require less typecasting tends to be more type safe and in some cases that you're admittedly unlikely to see in unity can actually perform better to quote a post from stack overflow you should use generics when you want only the same functionality applied to various types things like add remove and count and it will be implemented in the exact same way inheritance should be used when you need the same functionality such as get response but you want it to be implemented in different ways for each type so there you go i hope that was helpful and maybe demystified generics a little bit and better yet useful for you and your project until next time happy game designing you
Info
Channel: One Wheel Studio
Views: 4,341
Rating: undefined out of 5
Keywords: Unity3d, Indie Game, Game Development, C#, C Sharp
Id: 6ED4Qo0Yi6o
Channel Id: undefined
Length: 12min 40sec (760 seconds)
Published: Thu Jan 14 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.