The New Option and Result Types of C#
HTML-код
- Опубликовано: 9 сен 2024
- Get our GitHub Actions course and get the Git course for free: dometrain.com/...
Subscribe to my weekly newsletter: nickchapsas.com
Become a Patreon and get special perks: / nickchapsas
Hello, everybody. I'm Nick, and in this video, I will talk about the Result and Option types that C# might be getting in the future when Type Unions eventually release.
Check out the proposal: github.com/dot...
Workshops: bit.ly/nickwor...
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: github.com/Elf...
Follow me on Twitter: / nickchapsas
Connect on LinkedIn: / nick-chapsas
Keep coding merch: keepcoding.shop
#csharp #dotnet
can't wait to return Task
Aliases are helpful in these situations
using Users = ;
Lol I think people are just going to put that into a new interface to short method signatures back to normal levels.
Empty enumerable is enough instead of Option. Do not return null instead of empty collection. Nick also mentioned that few times.
Why return an Option? If there are no results, you can just return an empty IEnumerable, and the calling code will handle accordingly by default.
@@cjrada82You get a point, A Result monad would give more info (about errors). As an IEnumerable can also be considered as a multi value modad. There is no point to elevate it further.
'bout time C# grew a pair of monads
never realized that monads sounds like gonads...
Meh, Rust really kicked C# in its Monads IMO...
C#/.Net paved the way with async/await, .Net's TPL with asynchronous Tasks, but then Rust comes along and just grabs its developers by those Monads.
C# has had monads for a long time... LINQ's built out of them.
lol, I see what you did there...
Me: Nick, can I use Rust?
Nick: We have Rust in C#
Rust in C#:
10/10 meme usage :D
Not a full coincidence. F# and Rust both share a strong OCaml language origin too. And with F# being in the dotnet world, lots of things gradually entered the C# area too.
// Rust: declares integer called "n"
×&÷>%>×&÷>÷[$*&$
@@GameDevNerdlooks very c++ to me
let age: i32 = 0; 😊@@GameDevNerd
All I can say is, I love when features become part of the C# standard rather than a Nuget package. Better visibility, better documentation, guaranteed bug fixes and security fixes. So much good stuff. And I do so agree 100% with everything you said in this video.
Guaranteed bug fixes 😆
I tottally disagree. We are bloating .NET with pointless features. Bigger dlls, slower performance etc.
@@user-ci4yb4zl5e Thankfully you don't need to compile in features you don't use.
@@user-ci4yb4zl5ei don't agree that it results in slower performance
I'm so happy you emphasize so much the importance of exhaustiveness and forcing yourself to handle all cases.
Returning result objects was one of the best things I ever did to my codebase.
Exactly the same experience here, been a game changer - started using LanguageExt.Core which also has other Functional style programming stuff - but having to explicitly handle all the result states just made everything in the codebase much cleaner
5:15 "Exceptions are incredibly, painfully slow"
-> Jon Skeet, "If you ever get to the point where exceptions are significantly hurting your performance, you have problems in terms of your use of exceptions beyond just the performance."
Would argue those cases should often just be TryGet or something along those lines if normal logic of possible that does not exist.
Yes, and then Nick also says "using exceptions like this for flow control is bad practice" ;)
I also like TryGet, but it only returns Boolean and not the "reason" for the failure.
Result pattern is yet another pointless pattern offering nothing. It is becoming like the american quote about opinions. "... everyone has an pattern".
Im tired hear people "exceptions extremly slow bla bla". If youre getting nonstop exceptions then your code has much much bigger problems.
My understanding of programming is that exceptions are for exceptional events, which does not include validation errors, such as a routing id not being found in a database.
This is one of the reasons I always advocate doing more in controller actions rather than pointlessly cutting and pasting everything into a service method, because in the action we have direct access to the http response methods, without needing to awkwardly communicate all those scenarios back from service method.
@@tarquin161234 We just have our repos throw EntityNotFoundException and use middle ware to translate it to a 404. Might not be crazy quick but meets our needs and avoids a bunch of try catch every where.
Convention dictates if it will through Get throws Find will not List will not
The other option I could do is AOP but I really dont want to explain IL weaving to juniors.
@@user-ci4yb4zl5eBeing slow isn’t the main problem. People use it to control execution flow which not what exceptions should be used for. Exceptions should used for…well exceptions not expected behaviour. They are just goto statements with glitter.
I've widely used the C# Functional Extension by Vladimir Khorikov for years, which implemented just that. I am glad it now becoms part of C#.
Me too. This is the great library.
Me too. Validimir is a rockstar!
Nick is gonna milk this proposal for 10 more videos
You have no idea
As he should
As he should.
As he should
As he should.
One tip with LanguageExt options, you can switch on the Case property:
Option.Case switch { User u => …, null/_ => … }
I introduced functional programming in a big company and I must say I really enjoyed working with options result, pattern matching, unions etc. But it takes some time to learn and to read some chaining code when you have a lot of I/O calls. New developers feedback was always this, they enjoy it but they find it hard to understand this new way of thinking.
We only had a single bug in production in 3 years that didn't even affect users. It was on cache serialization of a model.
Option, Result and enforcing developers to only access the value through pattern matching is the way for no bugs, because 99% of it is when someone forgot to handle a exceptional path on code.
We want to achieve that "if it compiles it works" and this helps.
I'm also very happy with the introduction of this and see the feedback of devs and next moves.
I agree. At my company, we fired all the QA teams and replaced them with pattern matching, with no more bugs.
@@2SHARP4UIQ150QA and this are both arrows in the quiver of excellence. Why would you fire them??? That’s insane
Slowing people down and preventing them from pushing code is a way to reduce bugs too
@@LC12345 That's a good one, and I will add that another way to eliminate bugs is to fully embrace functional programming and eliminate object-oriented programming. 😉😂
@@2SHARP4UIQ150 the best way to not introduce bugs is simply to not code anything at all, just fire all your programmers and I guarantee you wont run into any new bugs ever again.
Result type is great, but I don't really see the how Option would be better than Nullable in modern C# projects, where NRT is enabled (and especially if you have warnings as errors). It's essentially solving the same problem. In projects without NRT however, I do see the benefit.
I add a Result with Succeeded, Error, Result properties to almost every project I work on, for this very reason. It works well but doesn’t enforce error path handling, so this is a great feature, very welcome. Will save lots of boilerplate.
I do a lot of automation work, and I grew to love this feature in F#. It is definitely nice knowing you have handled every possible scenario in some way that will never fail.
Love the Fsharp representation, lets go! lets let em know there's more than 12 of us, and that 12 fsharp devs get done what 100 csharp ones can in less time with less bugs.
@@WillEhrendreich It's a fantastic language! I pair it with C# to get a lot of stuff done, and I couldn't be happier.
“exceptions are slow”
meanwhile in production:
million exceptions - 1 second
one sql request to big table that returns nothing - 1 second
yeah… exceptions are problem
They're probably a consideration in low latency performance critical code, which APIs are often not.
Regardless of performance, exceptions are not for normal operating conditions such as validation; they are for halting the flow because of something unexpected .
I've written my own Result, Result and Option classes with Monad support. They work like charm. Plus they do away all null ref exceptions since you cannot write "null" anywhere 😂
Next I'm planning to write like virtual machine upon NET virtual (a bit hard to explain) so I can alter the values like in Unity game engine plus with diagnostic support.
After that I'm going to write my own programming language, compiler and virtual machine 😂
Edit. Ah, yes. I'm planning to write library so that you can use Monads in Controllers out of the box.
Exceptions have one benefit: if you use standardized exceptions, you just create a middleware for handling them and suddenly your controllers become really slim, as they should be.
The result type, while it's nice we're getting it, will quickly lead to massive bloat in controllers, cause each endpoint suddenly needs to check the result type. Another benefit of exceptions is that unless explicitly handled, it'll terminate the execution of the body at the point of exception - which ensures that application/data doesn't end up in an invalid state. Results pattern shifts that responsibility to the caller, which - depending on the implementation of course - can in theory be error prone.
Idk what's the hatred for exceptions. Both result type and exceptions have pros and cons, and both are fine - definitely pick whichever fits your codebase better. I personally am team Exceptions here - the biggest downside is performance, sure - but until benchmarks prove that THIS is the bottleneck IN PRACTICE, then raging at exceptions is just premature optimization.
Exceptions certainly have their place as well I hear ya.
But speaking for myself, I follow the motto of "exceptions should only be used for exceptional behavior", and it's always felt a bit icky whenever I've seen/used exceptions as a way to branch the program's core flow. So Option feels like a more natural way to codify that the program should respond differently based on if the object exists.
Hope that makes sense.
@@Tsunami14 How do you define exceptional situation? Trying to update not existing object isn't exceptional situation? It's not a "normal" path.
On the other hand that "magic" implicit error handling somewhere else in the code reduces readability.
Nobody says that Result is easier than Exceptions. Result is a better way to encode errors than Exceptions but it requires some effort!
The code looks nicer with exceptions but it's harder to reason about. You see only the happy path and have absolutely no idea what errors can happen and how they can be handled because the handling happens behind the scenes in some implicit weird way.
If you introduce a new error in Result, your code will stop compiling because you don't handle the new error. Most of the time it's what you want. With exceptions no matter how many new exceptions you add to the code base, the compiler won't be able to help you with anything.
With Result you can map errors to the response directly in the endpoint logic. C# Minimal Apis have very nice TypedResults feature to help you with that.
With TypedResults every possible case and the response body ends up in the return type of your endpoint logic. And since it's a type, a lot of things can be infered from it like Swagger schema and other things.
I'm not sure if I'm team exception, I prefer using a Result type for doing my logic, but I confess that if I'm to use this Result where I have to instance an exception and then put inside an object and returning I will definitely go with the throw method.
For me it makes no sense to mix the 2 things, you should use exception or result, not both. Unless you are in a specific scenario of course.
At this point you C# has enough things to recreate the entire Earth from scratch.
C# is basically that LotR meme of Bilbo going "After all, why not? Why shouldn't I keep it?" to any useful feature in other languages.
It had from C# 2, but now there's 5 ways to do it!
At this point, C# is pretty much turning more and more into F#. :P
The programming language equivalent of carcinisation is that languages keep evolving towards LISP.
@@sleeper-cassieLISP’s sin was always the those parentheses
as it should.
Thats a good thing - loved F#. Minus the missing "protected" level access
The main issue I have with using Result right now is that I still having to handle exceptions when calling external code (like when I'm calling into ef core to SaveChanges for example) because they throw exceptions on error.
If a native Result type is added I hope we'll get versions that return an error result on error which I'll then be able to just return back from my service functions as is.
When Java got Optional, in JPA they added, for example "find" functions, which return an Optional as opposed to "get" functions that throw NotFound.
Current I have extension methods that wrap a catch over external code calls and return Result.Error(ex). You can handle different exceptions here and return more descriptive Result errors so you don't pollute your code with random exception classes from other peoples code, but it's still a hassle not to be able to propagate directly and handle in one place.
Man C# slowly becomes the new C++, it combines every possible paradigm and as a result, gets more and more convoluted.
Yeah, I would like to see Microsoft create a more refined language based on C#, kind of like what Kotlin is to Java.
@@evancombs5159 why not just use C#6-C#7 - when it hadn't yet become a syntactic diabetic.
I thought Kotlin was the love child of a time traveling Java and a future version of C# 😂
As always when talking about this pattern, only simple examples with 1 lvl nesting. What about nesting methods in methods in methods? If there's a problem on 3rd lvl, how do you transmit result through all levels? Using a lot of bloating code I suppose?
For now yes, but that's up to the language designers to fix. In Rust, you can propagate these very simply without always having to check them, and there are libraries to make things very ergonomic. No doubt that people will start doing the same for c#. But I argue that, even if you do need to add some boiler plate, the added explicitness and clarity is worth it.
Result and Option are monads. Monads must have map and bind methods.
But using these methods directly when combining multiple monads sometimes isn't very nice because of the nesting they introduce.
That's why FP languages usually have some kind of syntax sugar for these methods. In Scala it's called for comprehension and I believe in Haskell it's called do notation (not really familiar with it).
C# already has this syntax too and it's LINQ. Select is map and SelectMany is bind.
LINQ can be used to combine multiple monads together and even asynchnous versions of them like Task. You just have to write generic Select and SelectMany methods so that LINQ can use them.
Not really.. this should be addressed by a proper Bind() method. The purpose of the Monad is to act like a "shell" so it should be pretty easy to change the "shell" because the Invariant (T) remains the same, so it would be just one function call away. I had the exact same problem you say in the past and I solved it like that. Furthermore you can transform it in an extension method and it will simply become something like result.Bind() or something like this
You mean the developer needs to decide how each function should return a particular result? How horrible!
Try not to nest your methods so much, it results in confusing to follow code. Instead try to create orchestration methods that defines each step in the process, and aggregates the results to produce a new result.
It's nice to finally see that Microsoft wants to add Discriminated Unions officially in C#. I found them very neat last year when trying them in Rust for the first time, and I have been very tempted to use them with third-party libraries before, but I never took the plunge. Now that I know it will (hopefully) come in a future C# release, I can't wait to use them.
In any case, I think it's a step in the right direction because it will force developers to think ahead in order to handle all possibilities that their program could encounter.
I'd recommend taking the plunge anyway, there are some good ones out there, or you can roll your own. I've used it in production for years now.
What will happen to nesting methods? I have a method call tree with depth of 5 methods, each can return multiple errors. So how do I pass the original error up to the caller?
Also will caller be forced to handle ~30 possible errors? Or can it just handle all them in once, like catch(Exception) did?
The spec isn't complete in the proposal, but it should be something like this:
Result result = doSomething();
if (result.IsFailure)
return result.Failure;
//Else Continue Processing
As well, I would recommend moving away from so much nesting of methods. Instead use an orchestration pattern. It will result in a much easier to understand codebase.
This is perfect timing on this video for me! I am scheduled to give a Tech Talk on this topic next Thursday and had already watched your previous video from a year ago covering the Result Type. Needless to say I'm excited about this feature and have been enjoying using a home rolled version based on your example from the first video. Thanks Nick and keep up the great work!
So excited foe the Result and Option - Now if we can get immutable variables by default!
Immutable by default will never happen, too fundamental of a change for it to happen. You'll need a new language.
@@evancombs5159 I have long been thinking about this subject, and my conclusion is that it's related to something much larger.
Important difference with a language like Rust is that C# manages certain characteristics of on the level of types instead of individual (local) variables and instances. Examples of such things are are indeed immutability (think about records and readonly structs), but also the distinguishment between value type or a reference type.
In Rust it's a lot more obvious to manage all those things on for individual variables and values. You customize mutability, references and ownership all locally based on usage.
Remember that C# is a GC managed language while Rust is a low level language like C and C++, and therefore has a stronger orientation on performance, while C# tends to keep more things on simpler to use side.
@@jongeduard I don't understand what you are trying to say? I've never used Rust so I'm not sure how these things are handled over there.
@@evancombs5159 Ok, no problem. It's just my thoughts on how programming languages evolve differently with different focus. Just intended in addition to what you said, that you would need a different language. I do agree with that.
Instead of Result you can use Either Type with Right and Left. Handling is same as Result with .Match and so on, but is in my opinion more the correct type for returning a result or the exception.
Result type has a built-in "success or error" semantics unlike Either which is just a sum without any additional meaning
I'm probably gonna use Result very seldom. I'm scratching my head how this type became so overhyped where its usefulness is really limited in OO code. I'm especially confused when I hear people say that they replace exceptions with Result (bad idea unless exceptions were used for flow control) or return Result from methods which have side-effects (bad idea because it violates CQS).
Yep. i’d skip this pattern until I have a really good reason to do so.
C# is slowly getting all the features that a functional code need in language itself
I'm looking forward to it. I'm always fighting with myself, should I return a null here or should I just throw an exception? The constant decision making really saps the productivity out of me.
So, if I understand correctly, I'll sort of be forced to handle my cases in a standardized way and won't have to keep making decisions between throwing exceptions or returning nulls.
Cool that it's part of C# implementation, still not that much of a fan unless you're planning to handle it one level higher, performance overhead for exceptions are minimal no matter how you argue it as it's an exception. Prefer having a proper stack trace for errors and let the layer that's responsible for it handle it, not permeate layers and layers of Result returns, just obscures code and every single layer you keep having to write stupid if checks.
Jon Skeet: "If you ever get to the point where exceptions are significantly hurting your performance, you have problems in terms of your use of exceptions beyond just the performance."
I think this is a very useful addition. It's also very useful to standardize other methods of error returns such as methods that return -1 in case of error for positive integer results or methods that return and array or list and reserve one field for an error. I hope it will also support the case when there is a result and a warning at the same time.
This may help exceptions have their places of dealing with something that is u n e x p e c t e d, together with eliminating the need for returning nulls/nullable types.
IMO, Result looks like a nice temporary fix while we wait for this feature. More elegant and as you said it is nice that it explicitly forces one to handle both outcomes.
Compare http calls with db calls.
If the db doesn't work, it's usually a catastrophic failure. For example, master goes down and replica for some reason doesn't become available. It's extremely rare situtation which shoudn't happen. But if it does happen one day, Result won't help me here because my whole application will stop working.
That's why I don't try catch my db calls and don't wrap them in Result.
But I do so with http calls because they are unreliable and fail often. I can gracefully handle failures with Result pattern. I can explicitly fail fast in my logic on bad requests or continue if I get success.
That depends on the use cases and on the business side, how they allow you to handle a catastrophic failure. Some are ok with retrying later, others need compensation. Then this is a question of timing, in some scenarios, it is not ok to wait for 5 minutes, while others are ok with 30 minutes. For how long code should wait and retry is not up to developers to decide. Another example would be when failed requests stay in a queue and get processed as soon as the DB is back.
Anyway, I think it is still a good idea to try-catch, log, and then rethrow.
I think async Result should automatically map to async Task
Option should map to Nullable since its the same.. you should be able to code with option exactly the same as nullable
I would prefer if they would reuse tuple and tuple syntax for this.. even for either/or type should be defined similar to a tuple like [string opt, string opt2]? the way they are planing of doing this show the low IQ level involve..
I never really understood the “exceptions are bad for controlling flow” argument. If something doesn’t make sense then it is an exception to the logic, when thrown it can be caught and handled explicitly or allowed to bubble up and return in some manner to the user. Returning options of good and bad results just makes me have to handle it directly when in reality if I can’t find a user I want a NotFojndException to bubble up to the user. I don’t want to have to manually return it through the chain
"Will the Option and Result" type also include the monadic behaviors?"
"No, C# will not include any monadic behaviors in the language for these types at this time."
Sounds like they're going to be difficult to use, even with DUs.
So does an exception has to be passed into the Result object in the invalid case or can it be anything?
If it is an exception, it is not actually thrown and thus has no callstack?
You do not use exceptions. Exceptions are for errors in your code or errors outside your control (like losing network connectivity). Instead you would return some kind of error object that details what is wrong with the data being processed.
I think these are best for expected errors, like trying to add a duplicate user. No need for a call stack, it only slows things down.
If an exceptional error happens, throw an exception. The callstack is useful for fixing a potential bug that caused it.
Hmm. But: the existing code using exceptions will not profit from this. Why not fix the performance issues of exceptions as well? Also: IMHO readability does decrease. The catch block has a clear well know purpose. In the new Map/Match it is not visible that the second lambda is error handling. You can and should add the parameter name to make it clear which part does what. Also the second part can become "ugly" is case you want to to distinguish for certain error types which is perfectly well defined with exceptions. And: now we again have multiple ways to achieve the same objective, which doubles the learning effort for beginners. Finally: what will happen if one of the underlying APIs throws an exception, e.g. the db.SaveChanges()?. Will that be converted into a Result as well automatically? I guess not. You either have to do it in the routine (not shown in the example!) or you will still have to create a catch block.
The one thing I REALLY wish for is letting us put multiple errors in a single result. Domain validation could then be handled by tje result type and not needing an external library or custom class.
I think in most cases inheriting from an IError or abstract Error type would be sufficient to switch on any subtype. Unfortunately the proposal does lack a little bit in the details, but that is how I always handle errors when I use a result class. It is how it should work if implemented correctly.
I'd be curious to see what the performance difference is between this and a library that avoids exceptions all together (like ErrorOr). It looks like you'd still need to create exceptions here but I thought the slowest part of excpetions was generating the stacktrace? Maybe I'm wrong...
Very much looking forward to this being part of the runtime
Love either monad, a bit like option and match in rust. Wich still feels cleaner tho
I literally learned and created my own implementation of result pattern 3 days ago...
I'm in the middle of "that big rewrite" of a pretty big monolith at the moment.
Basically, the legacy code had so many issues in terms of maintainability, testability, etc. so we decided to rewrite it.
It's on track for release to customers later this year.
We've basically written our own Result and used it all over the .NET Library Code, the HTTP APIs, etc.
We're talking _several_ tens of thousands LoCs..
Great timing for that to come out. Not.
Whats frustrating is that our implementation is so damn close to that proposal that it wouldnt be that big of a deal to refactor it to use the built in types. But we'd be forcing our .NET library users (Enterprise developers mostly) to then refactor their business layer too.
I wonder what other developers that have been using existing Result packages or in-house implementations in their code base are planning on doing now.
It's a shame that we don't have the 'static implicit operator Result(Exception e)'
Could you some day make a video about the library C# Functional Extensions ?
Does the new Result type render libraries like FluentResult obsolete?
Wouldn't `Result` be `Result` instead of `Result`??
You'd think that if you're comparing it to fsharp's one. however, the csharp one seems to be assuming you're using exceptions as errors, so it's implicit. this is ... both frustrating and understandable. I can see wanting the streamlined approach of having exceptions be the way it's handled, but custom error types are very nice in fsharp, because you get the oportunity to use a single Error DU.. *shrug*
While you can do Console.WriteLine inside the Match method, I'd return a string (or Option if you want to stay in monad-land) and move the side-effect to the side (a bit):
var msg = user.Match(u => $"The name is {u.Name}", "The user did not exist");
Console.WriteLine(msg);
It's pretty close to what I'd normally write with nullable:
var msg = user is User ? "The name.." : "Y U no exist!?";
Console.etc.
It seems that the Language-Ext match function and even the switch style that the language would support, could end up re-producing a similar problem to callback hell. Where everything just continually nests, indenting etc.
Think of a multi-step operation where multiple things can go wrong based on prior Result/Option objects.
Are there features or patterns that can be applied to mitigate that?
I think Language-Ext does this with additional functions, that sort of push you down a Functional approach.
Result and Option objects are built for functional use so to use them without callback hell does necessitate a functional approach
LINQ syntax to the rescue
@@JollyGiant19 That is fair, I just wondered if there was an approach that didn't use it.
What i have made in my codebase is a Roslyn Transpiler for the Result.
Whenever it finds a method throwing an exception it wraps it in a Result, replacing all throws with the new Result(...), and into a new concrete class, (code stays the same, just wraps the throws)
When i create an instance of the UserService, i create it with ServiceCreator.Create() which by default will return a UserService, but if the transpiler picked it up, it will have created a ExceptionWrappedUserService
The ServiceCreator.Create is a static class which has a basic Create method and whenever a new type is added, there is now the new Create method
Can we just have this in C#'s compilation pipeline and skip the boilerplate?
Imho, we should write code as we would normally (with exceptions), and there should be a step before the compiler to manage syntax sugar, if we enable a flag :)
Hey Nick thanks for this great one, actually I faced an issue with a null check, will implement this new way of handling these 2 cases, I think the option and result are somewhat similar to how we handle the multiple call-back promises in JS by then and catch I might be completely wrong here.
Java's Optional anyone? And Golang return convention?
Not a fan of the method calls for this though. It just makes way more indentation in my code but then again I guess its the same as if statement indentation🤷♀. But i'd rather use those primitive syntax.
Hi nick, it would be awesome if we could use the result in a basic if/else statement, because going lambda in a suit of operations will lead to nested lambdas... a pain in the ass to read...
The most important part of this is that the option is enumerable, meaning you can use linq on them. So select, firstordefault, etc... This is where the magic happens.
Maybe you can add the Switch feature to the option with extensions until its officially implemented?
I use extensions quite a bit, but I have not tried adding a switch to something so I am not sure if its possible but I am pretty sure something could be done that would make it nicer looking and explains itself better. The match methods main problem for me is that its not especially obvious that the second option happens when "not matched". The switch would make it cleaner I think while it probably also helps describe the different cases.
I agree that its great that it is added. I myself do not enable the "enforce nullable types" in my projects, and I do use nulls a lot, but I also know that I really should avoid nullables and I do often end up having crashes and other strange problems simply because I still use classes, properties and so on that can be null. So when making packages for others to use, I can see why it would help ensure that they run into fewer problems by enforcing this, and also exceptions as a common way of dealing with certain events that you know are common is something I would think best to avoid. As I think you mentioned, exceptions adds a performance hit. If that was not the case I would be less against using exceptions so commonly.
But I still use exceptions myself and try catch, and many times its the null properties that cause the exceptions in my code... so... I really should work more on my code ethics.
result types are realy good, and they opens opportunity to go step further and use railway programming, which allows to write code like it is always on happy path
I hope Option will have a .Map function just like the Optional in Java.
Good to see, that things that I'd introduced like 10 years ago, now go into language ;-)
One question : is result (or a variant) serializable ? Because i often use a custom ResultDto to return a result from a web service call, with an error message instead of an exception. It could be interesting to have a standardized result object for this usage.
I've always done this myself with my own system rather than using exceptions anyway.
so it is basically the same as ErrorOr?
Hi, newbie in C#. The Option demonstrated in this video is native part of C# language, or it is only available via third party library? If Option is not part of C#, does microsoft has any plan to include that natively?
If the c# devs are anything like java, this will be met with pitchforks and torches.
Luckily, they are not (that's me hoping)
as a previous full-time F# developer, Result was the first thing I added to C# in our codebase.
@@shadowsir lol same, I think many people did that over the years, and the number of nuget packages that do this are getting more and more numerous, so I think that triggered Microsoft to think about adding it natively.
Yeah, I've seen a lot of requests for it. So I don't expect the pitches and torchforks
It's a very funny comment because Java has had Option for years and has recently added type unions with switch pattern matching as well.
You can create Result type and use switch to match over Success or Failure in Java today already.
The syntax sucks as usual but Java type unions (called sealed interfaces in docs) are exactly what C# class type unions will be.
I can already feel how painfully the Option type is going to interoperate with nullable reference types and Nullable
What about the exception logging?
Now if some code base has its own Result class, usually you can't see a stack trace of the error/exception. On a big project, it's a pain, to look for a place where error/exception has occurred.
It'll be a mistake to use the existing Exception type and its derivatives for the "there was a problem" value of a Result. I get that doing so allows a quicker conversion (return new Exception rather than throw) but I'd rather see a separate Problem type to indicate business logic problems.
Love the Result, not thrilled with Option. Still feels like T?.
What I don't like about this `Result` type is that the non-success case must be an `Exception`, and the idea of returning an `Exception` that was never thrown doesn't feel right to me. I have been using and enjoying the LanguageExt library recently, but I prefer to use `Either` where the "left" type is LanguageExt's `Error` type, which may wrap an `Exception`, or may just represent a "non-exceptional" custom error. Small downside is that I have to explicitly specify `Error` every time, which does result in verbose return types like `Task`. I kind of wish that `Result` would just swap out `Exception` for `Error` as the non-success case.
Just a proposal still... But we can hope we get more functional goodness in C#
AFAIK Rust's Tokio Crate started these Types.
Maybe a bit earlier. Discriminated unions were in algol back in the 1960's according to Wikipedia
Later in ML, ocaml and therefore they are in F# since forever :)
you save the user and find that another user was created with the same email, thereby failing the unique index invariant. boom! you still have to deal with exceptions.
As always Microsoft is cherry picking some already existing stuff (OneOf library for the type Option case and all the managed result types that are already in every company codebase in order to fulfill the lack of error management in C#), placing its watermark on it and claiming to have achieved a great job
Hope it will be low level compatible with f#, so that the clr can evolve to much more robust apis, and so that interoo between c# and f# gets easier. Result, option and other DU interop is very tedious today.
Very nice, now I just wish I wasn't stuck working in Framework with strict rules on third party libs I can use
How does it differ from this ? OneOf
it doesn't
Will they migrate the standard library to union types? That seems quite unlikely, considering the amount of code that already depends on exceptions.
Hey, I hear you like rust so much that we added some into c#, in which you actually work and get paid for. Better still wrap it in try-catch though - it's still c#
This is a minor nitpick, but I'm not a huge fan of the name "Option" as we use the same word to describe other elements in the code. A lot of static code analysis will also throw errors if you have code that uses a library name like Attribute so I could see this causing issues. Just seems like there could be some nomenclature collision with the options pattern. I would have preferred Optional or HandledResult or something more descriptive.
It's an already established name.
@@jonasbarka I get that. It’s just annoying as there’s possible name collision caused by this. It’s also not super descriptive as to what it’s doing. Generally an option is a configuration
We like it , we need this feature in C#
Technically those two new types are not going to be monads OOB because they lack Map and Flatten (maybe Pure as well). Extension methods can help with that though.
Using exceptions in common flows / hot paths for common stuff is incredibly flawed. Not many people realize just how slow they are and their huge negative performance impact when they are being spammed, which is very easily done by a bad actor spamming invalid data at an endpoint. Exceptions are just that, exceptions. They should be used for things that really should NOT happen and you don't expect to happen. So I'm really glad for these new types that will introduce MUCH BETTER error handling - it's the one thing I always loved using in Rust and I'm glad they work similarily as well
People have had the "exceptions vs results" debate for years. One of the main problems of result types that I see people mention is that they require a ton of additional code. The people developing C# have to write a good implementation that solves this.
And that is beside everything else. It's like the "ORM vs No ORM" debate: for every argument, the other side has two counter-arguments, and vice versa.
@@deeplerg7913 No, "ORM vs No ORM" debate is orders of magnitude different. With No-ORM you are not just forced to write a TON more code, it's also more error prone and you are (usually) tying yourself to a single database. But with "Results" the only extra code is a SWITCH statement that just replaces TRY/CATCH - it's LESS error prone, not more because it forces the user to handle every possible scenario. But most importantly, this is not an EITHER/OR situation like "ORM or NoORM" --- Exceptions and Results are meant to be used together. Results for common errors, exceptions for... well, exceptions.
@@deeplerg7913the real problem with that debate is that sides are thinking too pure. Pragmatically, you just use what is more appropriate. Hot loop where you can expect 1000 traverses of the unhappy paths? Result type. You are in a thread and you suddenly lose the connection which only happens due to rare unforeseen events like an outage? Exceptions are the only reasonable choice here.
@@deeplerg7913 This is not a good comparison. Unlike "ORM vs No ORM" - Results and Exceptions are meant to be used together, Results for common errors, Exceptions for exceptions. Results don't add as much extra code as "No ORM" does - instead they just replace (or extend) the existing TRY/CATCH with a SWITCH or similar. With "No ORM" the development is more error prone because you can easily make mistakes writing everything - with Results you are LESS error prone because you are forced to handle all results. With "No ORM" there is also other downsides like possibly tying yourself to a certain database. But I digress.
Results are meant to complement Exceptions, is my point.
@@sohn7767 @deeplerg7913 Unlike "ORM vs No ORM" - Results and Exceptions are meant to be used together, Results for common errors, Exceptions for exceptions. And they don't require that much additional code either - as long as you're not too extreme with it, they realistically just replace TRY/CATCH
I've been using language-ext since 2017 when I started working for the guy who wrote it. It's a very nice way of coding but once you start working with monadic types, they do propagate throughout your code base and if that's not acceptable to your team, IMO the benefits become limited. I'm excited to see how the C# team implement this in the BCL!
I think that at the runtime level, nullable types and option should be equal if a strict mode is enabled to allow existing libraries to produce option types for consumption without them having to change their api signatures, then with strict mode enabled, do not allow anything null, everything has to be wrapped in option types. Same concept for exceptions with result, only problem is this isn't Java where exceptions are known in the api signature. So if that's the case, first start tracking what kind of exceptions a function/method can throw and then apply this auto-conversation to result type once if opted in and library supports it.
The biggest problem with null is that the runtime can’t tell the difference between new Class and new Class for Class.
This information is erased at the compile time.
Class and Class are on the other hand different and they should be different.
Option is a full-fledged type, not some compiler workaround.
Nope, having Option is required in some cases. Obviously Nullable and ref types do not support that
The new Result/Option types I think (at least from my point of view) are being added now to facilitate better code for both AI models and servers/web. C#/.NET is finally becoming a 1st class language for AI development and made huge strides in the past few months, and one area that's a bit problematic for strongly typed languages is all the disparate shapes and types of structs and tensors. It can force you to have to use managed code and reflection and things that aren't optimal for inference performance. The traditional way around that is try to hack together special interfaces with `unmanaged` constraints and use pointers, but that gets rather complicated.
Been waiting for Microsoft to add this to c# - glad it’s finally going to happen
Option is good for Database access. You can now represent NONE (missing). Before you only had NULL or NON-NULL. If I understand how it works correctly.
seems like more work for nothing
how is that better than throwing the exception (in asp context) and handling any and all of them with the exception handler once
instead of handling every case in almost every controller action
13:35 Why do you skip the next question about monadic behaviour? There's no sense in using these types without monadic binding support, because you would need to match every result you get, and it gets worse especially with nested ones.
11:28 "Just the happy part" haha
So.. We are back to returning error codes and switch statements for all function calls? Perhaps exceptions are slower, but they do make the code a lot easier to read.
Skill issue.
How do they do then when I have to look into the entire code base to find where an exception might be caught?
Except that error codes don't type check compared to typed error return path
I would say it's the other way around, cause you can have functions calling functions calling functions calling... you get me. And many people do the whole "o well it bubbles up hihi" thing. So if you're working anywhere, you'll have to dig down and up to see where exceptions are thrown and caught. With Results and Options returned from every function, everything is extremely explicit. And that is always superior in my book. Little bit more typing at times, but we're big boys that can type fast enough, and every function becomes very understandable and clear.
@@nickchapsas Also in web world you have to write exception middle ware. so throwing an error is not bad. you may reduce propagation at one or two places but it will increase code complexity and readability extensively. Not sure this performance worth it?
Java: All this to replicate a fraction of the power of my checked exceptions!
Is ir possible to switch on the Result same as on the Option?
I am on project with homegrown Option, Result and it is nightmare...
It uses lambdas everywhere, and it is very uncomfortable to understand tens of indentations and nested lambdas. Refactoring cannot be done with tools, only by hands.
Usually when I need to understand and modify (more or less significantly) some part of such code - I rewrite it to be more linear, understand logic and do modifications.
But possibly Option/Result from video will be is more convenient, as it allow to use switch...
From en.wikipedia.org/wiki/Tony_Hoare:
«Speaking at a software conference in 2009, Tony Hoare hyperbolically apologized for "inventing" the null reference:
I call it my billion-dollar mistake. It was the invention of the null reference in 1965.»
when building my API i came across this problem the returning result from API, the only simple way to handle it was to create a middleware exception to catch thrown exceptions if user doesnt exist or user already exists and so on... (unsucessfull flows), on the middleware exception i would create a object containing the StatusCode and the ErrorMessage and send it.
when calling the endpoint i would check if successfull and wrap the response in ApiResponse, containing a property: T? Data
if not successfull then i would know that there was error and based on the statuscode from the middleware response i could switch on the code and do whatever i want.
FUCKING FINALLY, after using rust for a few years now I honestly cant stand using non rust like error handling
I feel your pain. I'll still keep using Rust though, but at least for the c# projects I wouldn't have to roll out my own Result types anymore.
@@peculiar-coding-endeavours yes agreed
It would be interesting to check how much performance boost we will have in the dotnet 9
It will be minimal.
There are so many languages out there nowadays, and it seems most of them are adopting the best features of each other. Quite exiting days...although still a bit of a jungle. And I feel bad for C and C++, they served well and deserve our respect.
How do you chain calls when the function returns Task of something?
await and use parentheses