How To Create Generics in C#, Including New Features

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
ever since Microsoft introduced generics into the language they have been used almost everywhere even if you don't understand how they work using generics is very easy in this video we're going to look at why generics are important and then how to create them we'll look at the different types of generics how to limit generics and how to implement some of the more recent generic types such as generic interfaces and numeric generics now this video is a stand loone training video that will teach you a specific coding topic every Monday I provide videos like this on Thursdays I release podcast videos that cover the topics software developers need to know that don't include writing code if you're looking for a sequential training on a topic with a real world Focus you can check out I am timc cory.com where I have dozens of courses meant to prepare you to be a software developer if you're just getting started check out the master courses those courses are designed to start you at the beginning and get you job ready by the end okay let's jump over to visual studio and we're going to create a new console application very generic very quickly and easily um we're we're going to try avoid all of the UI stuff so a console application is the way to go this will allow us to um demo just generics and not anything else so we call this generics demo and generics demo app and I'll put this in my um temp folder next we'll hit um we'll see that it's net 8 yes that's what we want to use we're using visual studio 2022 the latest version so this is all um as of net 8 so if you're using net 9 or Net 10 uh if you're in the future uh cool that'll probably it'll probably still all work the same even if you choose the later versions but we are going to use top level statements and we're not going to worry about aot okay once this gets created we'll get rid of the initial the initial um entry here and I'll put a few empty lines here so what we're going to do is we're going to look at what generics are first and why they're important and probably the most common way of doing this is looking at what we have for generics today so we have a list of what's called t t is the representation of a generic meaning you can put any type you want in here so we could say list of int we could say uh numbers and we could instantiate that and then start using we say we can even instantiate right away this is a newer feature we're going to say you know one two three like so and now there's three numbers in this list and they're all integers and this is different from saying list of string where we might say strings equals and we'll say uh Tim and Corey and Sue okay so there's three strings there and strings as we know are different than integers and you know if we try to say something like like this well that's not going to work get read quickly it says no no no you can't convert um a an integer to a string implicitly so we could in theory do something like two string but what it's saying is I'm expecting you to put a string here but you're giving me a number but yet up here we can put numbers no problem so if I put four that works well why is that because we're using generics and what generics allows us to do is specify the type and then only that type will be used we have what's called type safety meaning if you put the wrong type in there the compiler at design time can yell at you and say no no no you can't do that that's the wrong type even though we can have different lists with different types so that's one of the benefits here of generics but you might say well Tim we could do this before that we could do something like this where we could say um and please don't ever do this but I'm gonna demonstrate why you shouldn't do this so list of object because really everything derives from object right so I could say list of objects called objects and I could then go well Tim and four and that works so why couldn't we just have one list called list of objects instead of list of T why we we have just objects well the reason why is because this is really not efficient this is very much not efficient and it's also not type safe meaning I could put different type in here I could put um 3.6 which now that's a a double or I could do a decimal or I could do you know some an object like a a person model I could put all these different things in this list of object and it would work it's just not great so even if we didn't use generics and we just had one list and we don't have to specify object then but there's a list of objects I'm just simulating that would look like um that wouldn't be good and the reason why is because of the fact that behind the scenes what happens is what's called boxing and so the way this works is this right here four is a value type but objects are a a reference type which means that in order to put a value type into a reference type you have to convert it you have to wrap it in an object so you're saying object of four not just four which means it it changes where it is located in memory and it is an expensive operation to box and unbox this value we can I can do a quick example for you just to show you how this works we could do a stopwatch um which that's under the using system. Diagnostics we'll call it s SW equals new and we're going to do something really basic here we're goingon to start the stopwatch and we're going to have a for Loop and we're say 412 and I'll put the underscore here so you can more easily read it for one to a million and if you're not familiar the underscores um are just there for visuals so I could just put one and then six zeros it be just the same but this way allows you to see that yes this is 1 million and we're going to do is we're going to say objects. add I okay so we're going add a number of integers to this object's list and then what we'll do is say stop the the stopwatch and we'll say console right line and we'll do a string interpolation and say list of object uh elapsed time and then we'll say stop watch do elapsed milliseconds now I'm doing a million just to be very clear of the the differences in time you wouldn't see much of a difference in a few but this is kind of show you underlying that something is different and something has changed so that's using this I'm going to duplicate the again and do the exact same code but this time instead of objects I'm going to add it to numbers and so a list of int and how long is that going to take so let's just run this and and see the difference between these two interactions so again same interaction we're going to create a stopwatch we're GNA start it we're going to add a million objects to our list of objects and we're going to say how long do that take and we're going to do start over again and add a million integers to the numbers list and then say how long did that take just to show why generics is such a good thing so let's bring us over here and it took us 57 milliseconds I should put the MS the end but 57 milliseconds to add to our list of object where four milliseconds to add to our list of integer and that's just adding to a list and we're still using really generics but we're kind of simulating this not being a generic list so it's much more expensive to do it this way with a list of objects that's why the generics were created because we can do things like list of integer and not only do we get the type safety of saying we can only put integers in here we also get the um the compile time checks to make sure that we're obeying that and we get no boxing or unboxing when we put those values in so there's a lot of benefits here of doing this I'll get rid of our stopwatch code you can just recreate it if you really want to but that allows it to be much more efficient to have this generic list and like cool I get it um but why would I ever create a generic and that's you know that's valid there are times when generics are are useful so what we're going to do is we're going to show off how you can create generics and then we'll touch on a few times when they might be useful if you ever watched me work with dap for data access for C I use generics in there to uh send parameters to and from Dapper so that's when I would do a lot but uh we'll show a few different times we could do this so I create a method here called type Checker um and what type Checker will do we'll say type Checker of type T so we're specifying here a generic and saying T value okay so so what are we doing here well first of all we're saying this method uses generics how we know because we put the angle brackets and then put the generic identifier here by default the the name we use or the letter we use for generics is T but you don't have to you can do whatever you want but that's the common part just just like when we do a for Loop and we have an i for the counter it's a similar kind of thing we start with T generally but again you might see something different for example um we have a dictionary you'll see that it's not just T it's t key and T value so they're saying is hey is a key and a value they could have said key and value but that would have been a little confusing because wait is that my value and my key no it's a type of your key and value so instead they said t key and T value you could do that you could also do another letter um typically if you have a second one we'll see in a little bit um you'd use a u but for our case we're just going to use T and then we can use that as a type Pretend This is one type to kind of help visualize this so type Checker of type string we'd say string value type Checker of type int we'd say int value and so on so our type Checker we're going to do some really simple here we're going to say console right line type of t and that's to give us the type that we passed in not the value the type we can also then pass in the the value to print out as well so we could even say something like um type and then do the same thing here for Value I could have done string interpolation but this is just the same pretty much so now we could do is we could call this let's even get rid of these and we're going to say um type Checker and we'll pass in a value of one and then we'll do type Checker and we'll pass in a value of Tim okay so there's a couple of um different type check we're going to run each of them will print out something so we run this this and we get wait for it to come over here it's not cooperating there we go finally so we have the type is system. in32 the value is one the type is system. string and the value is Tim which is interesting because we just passed in the the values we didn't pass in and specify a type it just knows what that type is going to be if we CH passed in 1.1 and run this again we can see that the type is system. double and the value is 1.1 so it knows based upon what we've passed in what that type is and we could be you know more um more specific if you wanted to we could say something like um how about uh record uh record person record string first name String last name right and they can say here um type Checker and new person record Tim Corey okay so now we're doing a type check on a person record and it's going to work in the same way where we have the type person record it knows it's a person record and the value is person record first name equals Tim last name equals Corey because records print off really nice nicely now if we did a class that would be just a class name unless you modify the two string so that allows us to have one type Checker method that takes an any type and tells you what that type is and the value of it so you pass in any value you want and it goes oh that's a double that's a decimal that's a person record whatever the type is and also what the value is that could be useful if you're logging something maybe you log both what the type is as well as the value of that type there's lots of different ways you use this but this allows you to create one method that does this now the comparison would be to have something like void um type Checker uh for string and say string value and do that work and then have another one that you do that is for INT but then when you have something like person record you got create another one but what if you don't know that person record is gonna be created you could put this in a library somewhere and it wouldn't even matter if you knew what the types were when you created it because the caller could pass in a brand new type it's okay it will figure it out and tell you what that type is so there's lots of ways you could use this that allow you to not even care about future usage because it will just handle future future usage just fine now that works for methods we've seen how to use a method now but let's create a class I'm going to create a new file for this just we don't want to clutter things up and we'll call this the um better list class all right and we're clean some stuff up here get rid of all that and make this public okay so this is the better list class and I'm going to make this generic I'm going to say better list of type T and why would a class need a type T well because we're going to use that type T throughout the class and in this case what I'm going to do is I'm going to have a private list of type T called Data equals new and instantiate that and you might say well Tim I've never seen that done before usually you have to put the actual type there and I did I'm saying this type so it's a little confusing because I've used T and we have list of T but if I said something like um uh T value then I'd put T value here okay okay so that's where the you know the using a t all over can be a little confusing but in this case I've passed in a t to the class and I said create me a list of T so I've instantiated a list based upon that type so it's if I pass an integer here I'll have a list of integer if I pass in string I'll have a list of string if I pass an person record I'll have a list of person record in data now I'm going to create a public void add to list and I'll pass in t value now I don't have to say add to list type T because we've already done type T at the class level so I can say data. add value and that will add that value to my newly instantiated list but in this better list I not only want to add the value to a list I also want to do a console right line that says uh value has been added to the list okay so I don't want to just add things to the list I want to know when things have been added to the list by having this this print out and say console right line this has been added to the list okay so now let's actually create this back over here we can we can comment out um just these three so we don't have you know U things printing out we don't need to and we're going to say better list which is going to create using directive at the top for me I'm going to say better list of type int and let's move that all the way to the top there we go I say better list of type int we'll say better numbers equals new so instantiate this class called better numbers I'm going to say better numbers. add to list I could have said add to be kind of consistent with a list but I said add to list to make sure you know it's different I'm G to say add five but I'm also going to say down here better list I'm G to say person record and we'll say people equals new so instantiy another class instance of better list but that's time with a person record as T I'm going to say people. add to list new Tim Cory okay so I've added myself to this list so two different types of classes one's an integer list one's a person record list but either way when I run this code it says five has been added to the list and it says person record Tim Corey has been added to the list so it knows what's been added to the list and it knows that each list is uniquely uh typed so if I were to say people. add the list one it goes no no no no that doesn't work because this is strongly typed and a a number in an INT cannot be a person record so therefore that doesn't work you have to add the correct thing to the list we have design time checking to make sure that we have uh type safety okay so make sure we're we're being safe with our types and not just this is not just a whatever you want to throw in there you can because what happens is when we compile this it's going to create the person record specific better list and create the integer specific better list and it's only going to allow those things this is not some kind of dynamic thing where we can throw whatever you want into that so we have a a generic method we have a generic class well there's another thing we can do and let's actually create a new file for this I call this I importance okay and this is is a nonsensical one in some ways because I'm not going to create great code for it that's okay um but I want to show off an interface we could have an interface called I importance and this will allow us to create something that is generic on an interface level so I can say type T and in here I can say t most important t a TB and what this will be is this will be a method that returns whichever value you pass in A or B that is most important so you're pass in two values of the same type you'll get back one value that's the more important one okay so now I could create a class that implements I importance so we're going to say um evaluate importance sure that's a great name whatever um we whoops semicolon goes there and we get rid of our usings and we make this public and I can say you know what I want to implement I importance but I have to give it a type importance of what well let's start with an INT okay so now I do control dot to implement the interface and it says okay there's the inter the implementation which is integers for the T values and how do I um compare importance uh well let's just do an if a is greater than b uh return a else return B okay I could have made that so much simpler but that's you know that's a a really simple evaluation where this now implements I importance of type int now that's a very specific interface the interface is for I importance of integer which is different than I importance of type string notice it's not implemented I could Implement that one as well let's do that and I can say here um let's actually copy this if statement and we'll just change the the uh a is greater than B I'm going to say a.length is greater than b.length okay that's again nonsensical in the way that I wouldn't see this in next production code because who's really comparing lengths to say which one's more important but this kind of illustrates for you how you can use an interface that's generic and implement it with different actual types so you can write one into interface but have it Implement multiple different types and allow you to do you know really cool things with it now I'm going to show you one of the ways that Microsoft implemented this interface generic interface in a little bit but let's first talk about constraints so I'm going to close down all these things let's create a sample class for now just to kind of illustrate the different types of constraints we're not going to try make it do anything I just want to show you how to limit constraints so we're just going to call a sample class and in here let's still clean this up a bit make it public and I'm going to say the class takes in a a a generic or is a generic but I don't want this generic to be just any generic because maybe there's some things that I do or don't want it to do do so I can give it a a few constraints on here to make sure that it um is limited I can say where T and I put colon I can give it a few different constraints for example I can say new and what this does is it says this T must have an empty Constructor so when you pass in this whatever it's going to be for T it must have an empty Constructor or you could say you know what I want this to be a class so I want to be a class and I want to have an empty Constructor or you can say you know what I want it to be a nullable class so you can also say things and notice the commas mean this and this I could also say that this is a newer one I can say not null which is a a um a a a warning generator that will say hey this is null as saying it shouldn't be null it's not a um an error so we could also specify a base class we could say um maybe we you know better list you know so this is either better list of t or one of its descendence one of it one of its um child children so that could be something we do we can also say this has to be uh this has to implement an interface I importance of type t Okay so this not only is a um H this T has to implement I importance so has to be then a a class or a record or something else that has an interface called I importance of t uh which is the same type as itself which would be weird um but whatever interface you want it to be we could also say um where T equals if we're passing in two values and notice we we can do that say T and U uh which would be this be one type this be another type we can actually say where T and U are the same so now um these two are the same or that um that you derives from T so maybe this T is a base class and you is a child class that could be still valid but you're saying that these two um will be you know have the same Base Class essentially um well actually that you will derive from T so there's ways you can limit these there there's not you don't want to limit them too much in in a lot of ways because otherwise what's the point so if you just say where T is an you know implements this interface well then why don't you just use that interface instead instead of passing in generic there can be reasons but more often it's you know just use the interface instead of trying to make it generic but you could also say where T equals a specific interface and U is a different interface that could be possible as well so there's ways that you can limit these things so that you uh kind of dial in exactly what you want for these generics so it's not just you know if if you say where T is a class and has a blank Constructor well then if I come over here into program.cs and I were to come down here and say uh sample class of type int test it goes no you can't do that um because that doesn't work so it oops I could say int int uh and it goes no you can't do that because this integer must be a reference type in order use the parameter T so I go okay I want this to be person record and it goes no you can't do that because must be a non-abstract type with a public parameterless Constructor okay fine so I'm come down here and say class person model and then I'll in here say prop um ID okay whatever so this has one value in here person model so I pass in a person model it goes yep cool no problem but if that person model has a Constructor where I pass in an integer uh starting ID like so I'm back to an error here because it says no no no must have a parameter list Constructor and I go oh okay so now I create another Constructor that has nothing and we're back to good again with this so this is how you can limit what your generics can do and I'll just get rid of this and in this example we don't need to have it okay so that's how you can limit your generics um at the class level or even at the method level but what we're going to do now is look at how Microsoft implemented something new and look at the I number this kind of a fun one so let's create a new class and we'll call this math operations just to show off how to use this so again clean this up one of these days I will create a template for this to make it easier um but what we're going to do is we're gonna pass in a type okay we're gonna say where T is I number of T okay so when we pass in this T that t has to be an i number all right so it's going to check to make sure we can do that which means now we can say public T add TX t y and we can say return x + y well if we didn't have this we couldn't do that okay notice the quickly why because we can't add two objects together what if they're person record well person record plus person record how does that even work that doesn't what if they are booleans how do you add booleans together well technically you can but that's another thing you know so it doesn't know how to do addition on any generic type or every generic type but if we limit them to say hey this has to be an i number well then we can do things like add subtract multiply divide and a whole lot more numeric operations which means now if we come back over here and we say you know what I want to do math operations on type int and say um int math equals new to instantiate I have I can create this um without with make it static but we're not going to so I can say um console right line int math. add one and four right and that's going to give me the value let's comment these out here so we don't have other values that give me a value of five right so let's let's run this just to just to see that actually does give me a value of five and there we go it says five okay so now what we're going to do is we're create another one where I say math operations of type double and say double math and I can say the same thing let just copy and pastees here only I'll say double math here and we're gonna say that these are doubles and it goes okay fine I I can deal with that and it's still five but just to kind of prove we can say 1.5 and 4.3 and we can run this again and we get 5.8 so that works even though this is a double and this one is an integer and if we were to try and pass 4.3 here it's not going to work it's gonna say no no no I can't take a double I need an integer and we could even change this to say decimal and it say well hey whoa um you can't do that because these are not double these are doubles not decimals so we have to pass in decimals there I'll just undo that for now so we could pass whatever numeric type doubles decimals um integers floats and so many other things because they all implement the I number of type T so this allows us to create more um robust math libraries up into this Point typically what I recommended was that you would create a math library with doubles because that's what Microsoft does for everything and that's where we can do things like power and other things that they take in double so if you were to do um you know math Dot and say look at all the values notice it takes in a double it takes in a double takes in a double uh takes in a decimal for that one um but you know you start looking at all these different things things and they take in double okay decimal so what you can do is you can actually create your own math library that takes in whatever values you're using whatever type you're using without having to do conversions that's where the real uh Power of generics comes in because you're not doing conversions yes if you have two integers you could convert them to double and then add them together but then you have to convert them back to an integer which is expensive to do all of that when you could just add two integers together and if you have decimals adding two decimals together is not the same as adding two doubles together there's a different system there because of the uh significant digits and all the rest so you don't want to do conversions back and forth because you could actually lose significant places after the decimal and so many other things so having this generic allows it to be more specific in how we do things so that's how generics works that's why you might use them I would encourage you like anything else in software development use it don't abuse it okay don't look for every possible case to use generics because often you'll find that a class would have done just fine or an interface or using an interface would have worked just fine but when used correctly generics can make your code much more efficient much more dry and much easier to understand and read so there's a lot of benefits to using generics we see generics a lot with things like list and dictionary other things where these are all generics now because of the fact that it gives us that type safety it gives us the ability not to box and unbox values and compile time checking and so many other things we also see that things like logging where we can just say hey log whatever log this class log this method log this value we don't have to know about specific types and it can still derive information from the caller and so many other things so generics are awesome and when used correctly they're really powerful I encourage you to understand them practice them it's really important that you practice this stuff just seeing it isn't going to be enough actually practicing it trying it out is really valuable to understand how it works and make sure that you know how you can use it in the future all right thanks for watching and as always I am Tim [Music] [Applause] Corey
Info
Channel: IAmTimCorey
Views: 33,185
Rating: undefined out of 5
Keywords: .net, C#, Visual Studio, code, programming, tutorial, training, how to, tim corey, C# course, C# training, C# tutorial, .net core, vs2022, .net 6
Id: Ld5D6B2Ntjg
Channel Id: undefined
Length: 38min 50sec (2330 seconds)
Published: Mon Jan 22 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.