Union Types are Finally in .NET, but who cares?
Read full transcript 9 segments
-
Hello everybody. I'm Nick. Yep, I remember my YouTube account password. And I'm back here to talk to you about one of the most anticipated features of .NET 11 for the longest of times, what 10 years, probably more. Uh, and that is union types. We used to think it's going to be discriminated unions. It's not quite that. It's finally here. I have the full .NET 11 installation, the one that will be released officially in the end of November. And let's see what we got. Let's see what is this feature, why you might want to use it. And I know, do you even care? Because now agents write all the code. I think you should care. And I'm also going to show you how you can sort of train your agents to know how to use every C# 15 feature by the end of the video. So keep watching to see how we can do that. So straight away, let me show you what I have here. I have a simple console application. It is running in .NET 11. And I'm going to start with sort of the the most basic demo I can because fundamentally what we got in this release compared to what we sort of thought we might be getting um is a bit different. It's a bit more of a simplified version if you if you want.
-
Uh but let's see what we have. So I have a simple method over here where we have a card object here and I'm casting it as an object and then I'm switching on it to say that if this is a card then do this but if it is a bank transfer then do this. So this type can be one of the two basically and there's no inheritance or anything in this just individual objects but based on what this thing could be we're treating it differently. So this is how we would be doing things in the in the olden days. Now if I go and I delete this catch all arm of the equation then as you see I get a warning that this expression does not handle all possible outputs. Um so it's not exhaustive and this would be a problem because well what if this type is something else you'd have to sort of handle it otherwise this will return nothing in this case because uh null in a right line will be printed as an empty string. But this in other situations, for example, in a response, if someone is a user being created and if you want to say that the account was created or that the account already exists with this email or some any other response, then you'd have to handle sort of every known case. But because you don't know this object can be anything basically, well, you'd have to code it defensively to make sure you cover all the bases, which was really annoying. Not only because of that, but because even if you had inheritance and even if this card object wasn't a card object, but it was like a payment method and every payment
-
method inherited from that type, unless that type wasn't able to be extended anymore, you wouldn't possibly know how to handle it. And then if it was in a library and someone else adds their own code, it just gets more and more complicated with handle scenarios like that. So what are union types? What was added? And and what solves this issue? Well, now what you are able to do in net 11 is you can have let's say a a public union which is this new keyword we're going to be using and this union is sort of a type in itself. So the same way we have records, tracks, classes and so on. Now this thing is a union and it is a payment method union and we want to say that a payment method can either be a card payment or a bank transfer. So, I'm going to say that this is either a card or a bank transfer. And that's it. That's the syntax, the most basic thing you can possibly have. I will show you what this is behind the scenes because it is interesting and it's something you should keep in mind because this isn't magic. This is basically syntactic sugar. We're going to see at the code after and how this code is lowered. But now that we have this, what we can do is we can say that this is a payment method. It can be either a card or a bank transfer. And to better visualize this, if I said here random dotshared dot uh next and I said that this is from I don't know 0 to 10 and I said that if
-
this is more than five um then this is that otherwise this is a new bank transfer with a reference. let's say I don't know um Nick is the reference then this can be either one or the other. So if I was to run this and I can go over here and just comment this out um and run this code, we're going to get either the card before um ending in that random number or if I run it again enough times, it will say bank transferred. So 50/50 around 50/50 on getting that response back, which is really useful for a very simple reason because this can only be one of the possible two situations over here. either a card or a bank transfer. I do not need this default arm because it cannot possibly be anything else. Anything that it can be, it's part of this declaration. Either a card or a bank transfer. If you want to think of this more as I don't know, maybe someone is being created in your system and you have a create endpoint. Well, when you create a user, what can you get? you might get and I'm going to use pseudo types here because I don't want to make all of them but if I say uh user created result then what you would you have you would have the created you would have the email exists you would have the I don't know it could
-
be bad email it could be a bunch of different things so instead of dealing with null and dealing with all of these situations separately and edge cases and all that you can just define a type as a union. So that union type and then you can exhaustively guarantee handle all of these situations without having to sort of code as defensively because it can't possibly be anything else if you don't handle it. So if I go here and I say switch on the card then I'm getting a warning which by the way I recommend you treat as an error. So I would say that hey compiler warning I want to turn this actually into a compilation error instead because you should be handling every possible scenario. That's the whole benefit of adding a feature like this. But if you cover everything, it's not something you have to worry about. Now, let's see how this actually looks behind the scenes. So, if I pull over here the IL code and I turn it into low-level C, I know it looks a bit gibberish, but if I click on the union here, you see it's super super simple.
-
It is basically a strct. So the union by default is a strct sequential layout kind and then it has this value object that will have a backing field carrying the value that whatever this thing is representing. And what you have here is a constructor with the card to cast the backing object cast it into an object and then carry it around and then return it. And otherwise you have the bank transfer depending on whatever this thing is. So basically that is the structure. You could very much take this entire thing, copy it, go here and say this. So the union type, the nullable context, all that. I mean, I'm going to remove this for simplicity, but you could very much say payment method to uh this backing field. I could just name, I don't know, backing field and replace it everywhere here. And this code is not gated in any way. So I could just rewrite all of this. This should be payment method to this nullable. But just to give you an idea, there's no magic here. It is just a little bit of syntactic sugar that the compiler will write for us to basically write this.
-
You could write your own unions as long as it's marked as a union with the attribute. So the compiler knows how to deal with it. And we can remove all this as well by the way. So these are not needed. Then immediately you see it's not really that complicated. And then this I union interface if we take a look at here all it does is it adds that object value that will be carried around by the union which will carry the value of the current union state. That is it. It is not fancy. It is super super simple. And basically this will guarantee that the exhaustiveness will be there. So your code will be safer. Now I hear many people screaming about this Nick. I think that's a bit too little too late because now Claude Codex and all the other agents will write all this code and even though I was thinking like that in the beginning like why does it matter? Well, I think it does because the code still matters and being able to use all of these features and the ability of Microsoft in the future potentially optimizing them. You do want to be on the latest features for your codebase and migrating to them would be very very easy. The problem is the agents don't really know how to use these features because the training data is always behind. There's not enough of a critical mass of code using that feature. So it will probably never use it or it won't use it until the next training data set with enough of that code. So how do you deal with this?
-
Well, you write a skill about it. You write a skill that covers all the C 15 features. And in my case, it looks like this. So I've already written a skill that covers every single C# 15 feature. And then if the agent needs to know uh how to use union types, then you explain how the feature works. And the agent will be able to use it and produce code. And because the description of that skill says immediately, hey, whenever you see code that basically is on where is it on C 15 or .NET 11 and it uses the target net 11.0 or language version 15 or preview immediately go in and start using it. So this will automatically modernize your code if you add this skill. If you want to get this skill, I'm going to put the link in the description. You can just download it. I recommend you install it and the moment C 15 and 11 is out, you automatically include through all your projects because it will know how to fix all the previous code that is not using these features and you will get a better output from your agent. So again, link description, try it out, let me know what you think. I guarantee Microsoft will expand on this and there will be more things and more goodness in this.
-
But fundamentally what we're going to get now and what you should know to start implementing your project is this. And now I want to know from you what do you think about this feature? Are you going to use it? Do you care? Leave a comment down below and let me know. Well, that's all I had for you first video. Thank you very much for watching and as always, keep coding.
Summary
This transcript introduces union types in .NET 11, detailing how this long-awaited feature simplifies handling multiple distinct types without relying on traditional inheritance. Key subjects covered include syntax for declaring unions, exhaustive pattern matching in switch statements, compile-time warnings for unhandled cases, and how the underlying syntactic sugar works behind the scenes. The practical takeaway is that developers can now define strict, multi-state types to eliminate defensive null checks and guarantee that all possible scenarios are safely handled.