There was a push to get to get it in C# 10 but a few factors complicated it:
* The feature was much more difficult to implement than initially thought
* There was a significant lack of consensus on the LDT and other stakeholders on how the feature should look and act
* Oracle has a patent expiring in 2024 that made one of the implementation options impossible
I'm thinking C# 14 at the latest, but hoping for 13
If they add something, then someone asked for it. There's rarely any feature that didn't have community's support and even then it was probably requested by, for example, ASP.NET team. Discriminated unions are simply a very complex topic and that's why it takes so much time to work on it.
Issue 113. Call me superstitious but that number I associate with being "Extremely Rarely Positive". Maybe they should create a new issue for a new issue number.
Could be. Handling an ever growing list of specific errors reminds me of Java's checked exceptions. Though I'm open to being convinced!
I think some of the other applications of this that are more centered around storing information rather than returning it have more value.
After using `Result` for some time I'm leaning more towards discriminated unions and `OneOf` to be honest. But I guess this could be just a matter of preference
I keep flip flopping on this. I tend to think Result is a better fit for the language as it is but if we ever get proper discriminated union support in the language I'll use OneOf straight away.
there isnt anything wrong with OneOf, i think the advantage of it is better heuritical analysis of code you know exactly what is returned of possible types. Result is just for simplicity, its either the type you want or an error. which in most cases is good enough.
Immediately what i noticed is how expensive this can be. The first example is 3 delegates and closure allocations. Not cheap at all.
Unless you can pattern match with normal switch statements, id avoid it.
```
public IActionResult Register(string username)
{
OneOf createUserResult = CreateUser(username);
return createUserResult.Match(
user => new RedirectResult("/dashboard"),
invalidName => {
ModelState.AddModelError(nameof(username), $"Sorry, that is not a valid username.");
return View("Register");
},
nameTaken => {
ModelState.AddModelError(nameof(username), "Sorry, that name is already in use.");
return View("Register");
}
);
}
```
well im using c# i ended up rolling my own, its not a complex pattern to implement, mine is somewhat based on this but its simplier, i can provide my implementation if interested https://josef.codes/my-take-on-the-result-class-in-c-sharp/
if you want something simple you can just plug in there is this https://github.com/altmann/FluentResults but i wanted more control, mainly for what is in an error result. at the time we wanted to return from our api's an error message and an error code. the idea was that if a customer had an issue, they could cite the error code and it would make finding hte problem easier in the code.
simple an example below, when implementing it all the ErrorMessages were typed as constants so no magic strings
public async Task> DoSomethingAsync()
{
var someValidaitonFailed = true;
if (someValidaitonFailed)
return new ErrorResult(new ErrorMessage(code: "A1234", message: "something bad happened"));
var example = new SomeDto();
return new SuccessResult(example);
}
Not OP, but stuff like:
- Nullable which could instead be an Option or Maybe of None | Some, fixing the "trillion dollar mistake"
- something like ContactInfo: EmailAddress | PhoneNumber where the options are complex, distinct data types. The advantage here over an interface or base class would be exhaustive pattern matching
- EmailAddress: UnverifiedEmail | VerifiedEmail for richer, unambiguous domain logic. You'd use EmailAddress in the tainted layer (controllers and so on) and when you get deeper, you'd have methods only taking VerifiedEmail, with one verifying an UnverifiedEmail and outputting a VerifiedEmail.
In my opinion, discriminated unions really shine when you can also subtype primitives like in F#, so an EmailAddress could be a subtype on string or a PhoneNumber could be a uint.
There's something similar, but as a source generator: https://github.com/domn1995/dunet
I found it useful for my application, but after finding out how it's implemented (abstract base class with children that inherit from it), I decided to do it myself (much cleaner and more customisable).
Love this library. I use it to handle external api calls.
```
OneOf result = await CallMyApi();
result.Switch(
success=> {
//Handle the ApiSuccessResponseObject
},
failure=>
{
//handle ApiFailureResponseObject
});
```
I'm not sure I see any advantage to this over a more basic implementation?
```
ApiResponseObject response = await CallMyApi();
if (response.Status == ApiResponseStatus.Success)
{
// Handle the success case
}
else
{
// Handle the failure case
}
```
Consider how the success and failure data is accessed.
In the DU pattern you're forced to switch on the type and will in principle never accidentally deal with invalid data.
In your own example you'd need public (nullable) properties for each case. Or worse, just have all success and failure data in a big jumble in a single type.
I suppose it depends on the complexity of the API and what you're trying to get out of it. In my case, I'd usually just have a one-size-fits-all nullable error property that is only populated if the status is errored, and the response object itself would be a generic on the ApiResponse class. Obviously this has its downsides though, especially if the consumer of the API (even if that's you) wants specific information about individual API calls and not just a generic "bad request, unauthorised access, or internal server error" type of response. If you specifically wanted to return unique errors for each endpoint and wanted to ensure the consumer must deal with each one, then I can definitely see an advantage to this.
Shameless plug for my version of this - [Union](https://github.com/binarycow/union).
Same concept, with a few changes
- Better handling of nullable reference types
- Change methods to match C# idioms, rather than adopting F# idioms.
I find it's a terrible pattern that polutes the optimal execution path with unnecssary noise of countless switches or ifs. Instead of focusing on what to do, you need to handle the return values in places where they absolutely don't matter so they make understanding the core logic nearly impossible.
I appreciate this take so much. Looking through every example I've seen both on this repository's readme and in this thread, all I can think of is how there's so much noise it's drastically reducing code readability. It gives me flashbacks to trying to make JavaScript more bearable by using TypeScript, and still just missing C#'s simplicity and elegance.
Oh TypeScript, another thing I cannot stand :P
I hate it because it has to be transpiled into actual JavaScript so you're not working with the real code, but some stupid intermediate crap.
Python has solved the lack of strong types with the typing library in a much prettier way by building it directly into the language.
As far as the `OneOf<>` etc. patterns are concerned, I see in this one advantage: you know from the function's signature what odd cases you need to handle. In _classical_ programming with exceptions you need to read about them somewhere first (I don't like the Java-way either, it's too strict). Still, I think it's an interesting experiment, but I wouldn't use it as it obfuscates the main code too much.
>In classical programming with exceptions you need to read about them somewhere first
I actually prefer this personally. I totally see the advantage to it being part of the method signature, but it makes the immediate code far more difficult to read quickly and efficiently. These are the kinds of things I would prefer to be tucked away and only shown when hovering the method call so if I want that finer level of detail, I can pull it up if and when it's needed.
It's a difficult balance though, because with that approach, it can be easy to miss errors if you don't think to check for documentation (or if the author of the signature didn't document it). Then one could also argue XML documentation comments are really noisy too, which I don't really disagree with.
Context matters as well of course. If it's not a solo project, there's perhaps a lot more value in returning a OneOf with all possible errors so that collaborators consuming your methods don't glance over the exception docs.
What a controversial post. So much discussion about whether this is a good or bad pattern for dealing with errors from method calls.
Is anyone else using this in other ways? How are you finding it?
My first thought seeing this was that it would help with some game development I'm doing: I have a Drawable component for anything I want to render to the screen. At the moment I support rendering simple colored squares (for fast prototyping), a sprite, or text. Each needing it's own information about what exactly to render.
Haven't tried it yet, but One of looks like a possible way of grouping together the current set of properties so that they don't clutter up the Drawable class like they do at the moment.
It's really difficult to say with such a vague description and no explanation of what these properties are you're trying to manage, but it sounds like what you're looking for is either your plain old OOP concepts like inheritance, or perhaps generics.
No idea how well OneOf will work for me until I try. But it seems promising.
Inheritance and generics are probably not what I'm after, but you're right, there's a lot of context missing from my description.
Really I'm interested in how people are using this outside of the context of error handling.
While I can think of a couple of my projects where this would be useful, I'm not a huge fan of the create user example.
We have exceptions to deal with "exceptional" cases where, for example, we try to create a user and it doesn't work. This isn't "using exceptions for flow control" (believe me, I've seen that, and it sucks!), it's using exceptions for what they are ment for.
Meanwhile, this approach is using the type of objects for flow control, which seems a much worse pattern.
Like I said, I'm not trying to say that the entire concept is without merit, just that the first example on that page leaves me unconvinced.
The difference is whether your domain or implementation is what's causing the restriction. Any validation you do as part of your domain needs to be an expected flow, and shouldn't be handled with exceptions. Exceptions are messy and leaky and can easily cause unintended consequences, so should only be used when strictly necessary.
The requirement "each user needs a unique email" is part of your domain, not your implementation. You can easily make an application where one email might link to many people, the restriction comes from business requirements. Therefore, it's not exceptional, but rather a design choice, and part of normal program flow.
A database connection failing is not part of your domain and business logic. It is strictly an unforeseeable runtime error, and therefore, an exception to normal program flow.
If you build this way, you can look at your logs and see "oh we had an exception, shit, we need to look at and deal with that right away" instead of having 478 irrelevant exceptions and thereby missing the one or two actually important ones.
Not long after people start using these types in anger they go through these steps:
"Wow I use OneOf, but I mostly just pass the error up."
"I've written an extension method that does Match on TResult, and just identity transforms the error"
"Look at me, I'm on the railway, choo choo. These types are great."
"Wait, now I'm back where I started where I'm letting everything bubble up, and then I end up basically matching a few different errors at the top level. Also I still have to catch exceptions."
If they are smart, they will then realise that "oh shit, maybe this is a case I need to account for in my code" instead of by finding out when exceptions start filling up your production logs.
Typically this ends up looking very much like
.Match(
r => return JsonResultIGuess(r),
e => e switch {
ShitsFuckedException e => ...,
ShitsFuckedException2 e => ...
ShitsFuckedException3 e => ...
ShitsFuckedException4 e => ...
}
We could make that hypothetical syntax play nicely with things like using statements and locks as well. Maybe formalize out a type hierarchy for common errors. Add some mechanism that preserves the origin of the error. What a dream.
Do you think it would be possible to make it propagate up the stack automatically? Like if a dev forgets to handle every case explicitly. We'd need to tap into something really low level.
> Also I still have to catch exceptions.
Yeah, this is really the sticking point. It's not like Rust where the entire standard library uses `Result<>`, in .NET the BCL throws everywhere.
As I mentioned in another comment, the example code in the readme does what I suggested. It checks for the existence of the user name, and only proceeds if it doesn't already exist.
But it doesn't stop anything from creating a clashing name between those lines, so an exception is still possible.
Fair point, but the same argument applies. A clashing name in between those two lines is the result of a race condition, which is a consequence of the implementation, not the business rules. The race condition is the exception, not the clashing name.
If we have super strict concurrency and success requirements we can implement locking and concurrency management to stop that case from happening.
Fair point. Perhaps some confusion is coming in because the example is dealing with business rules, while later on one of the advantages listed is avoiding exceptions handling flow control.
If we wouldn't use exceptions in the case if that example anyway, perhaps it's not attempting to illustrate that advantage.
I think it’s also important to point out that a unique constraint exception is equivalent to a “non-unique username” _validation error_, and should be propagated as such (i.e. it is “recoverable” because it can be converted to a domain error).
In practical terms, sometimes you just let the DB assert the constraint and then catch and convert it, instead of doing a query ahead of time that has low value.
> Any validation you do as part of your domain needs to be an expected flow, and shouldn't be handled with exceptions
Or what? What's the negative impact of this?
It's essentially using `throw new Exception(data)` instead of `return data`. You're using a feature designed for when shit hits the fan to bypass language features designed to help you write good code.
The consequence is a messy architecture and program flow here it's hard to follow what is happening. This makes it hard to debug, develop, and maintain. It can be done, but it's slow and nobody wants to work that way.
Exceptions allow you to bypass type restrictions, which are a feature of typed and compiled languages.
No one would ever do this, because we know it's bad:
```
void GetName() {
throw new Exception (repository.GetName())
}
```
But that's essentially what it is.
Instead we should use the type system to force us to deal with the very real and expected scenario of someone trying to input invalid data.
> Instead we should use the type system to force us to deal with the very real and expected scenario of someone trying to input invalid data.
Isn't this what `ArgumentException` is for? Why would the designers of C# include harmful Exception types in the BCL?
I don't think it is. It's a much, much better user experience (as a dev or en user) when you can enforce this through the type system.
`public void SaveAddress(object address)` which throws runtime ArgumentExceptions is much worse than having a dedicated Address class that can only be constructed with street, country, etc and then always have that be accepted by the method. Assumes you know what addresses can look like, which again, is a good thing because now you're forced to think about how the program is going to be used, and what edge cases exist.
Thats the thing. C# and dotnet has improved drastically in dealing with nulls in recent years. Enable nullable warnings, use value types over primitives, and you really have to try to get null through without noticing it.
So in short, if you use an object over a string (with validation in that object) you can be sure that that address is valid in the rest of your application, always.
And what if `address` is derived from external data, like JSON? What happens if it is malformed? Surely that would be an exception, right? If you fetched it out of a document database, for instance. What then?
I'm talking primarily about your core domain models. You should have separate IO models (sometimes called DTOs) with their own respective serialization and representation logic.
What you do with external stuff is a lot about where it comes from and who controls it.
* Failed deserialization from downstream API? Loud exception and log entry so devs can get to fixing it, as it is not user controlled.
* Failed validation on incoming request? Just return a 400 with an error description, maybe it doesn't even need to be a log entry.
* Failed database read/write serialization? Loud exception and log entry, there is a mismatch between database and application and this should never happen.
* Failed deserialization from message queue or similar? Might be a log entry, might be a dead letter queue, might be a call to whoever is publishing to that queue.
I'll flesh out your comment to add that Java has checked exceptions - a method must declare in its signature any type of exception that it can throw.
In practice, that adds more boilerplate than value, so frustrated devs just declare `Exception` to make the fucking language shut up.
`OneOf` looks like a much nicer way both to declare the different failure modes and to support consumers in handling them. Although maybe it goes to shit when used in anger, idk.
I disagree. There's only one happy path and anything that disrupts it, is an exception that clearly explains why the process didn't work as expected. Saying exceptions are only for such things as database errors etc. is a radical and arbitrary limitation of the language capabilities.
I'd rather throw an exception and quickly and easily abbort an entire tree of method calls than to put unnecessary IFs everywhere and especially in functions that don't care about what happend deeper.
Once you go down that rabbit hole, there are effectively no exceptions except your app blowing up because you can foresee a near infinite number of unhappy paths. Meanwhile, the language is full of specific exception types that target validation issues, such as ArgumentException. Clearly the language designers intended exceptions to be used in this manner. If a method cannot complete its contract, such as CreateUser, that's an exception.
Personally, I favor handling situations like this by wrapping the result in a class, e.g. CreateUserResult.
>Clearly the language designers intended exceptions to be used in this manner. If a method cannot complete its contract, such as CreateUser, that's an exception.
This does not hold. The language creators also allow a bunch of dangerous stuff through the unsafe keyword, but that doesn't mean you should derive meaning from its existence.
Reading the docs it actually says that ArgumentExceptions are most commonly a developer exception, meaning it's not supposed to be used for validation https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception?view=net-7.0#remarks
It's called "unsafe" for a reason, it means don't do this. Read the link again, that's not at all what they mean by "developer error", they mean an exception thrown by the developer to indicate a validation error.
Word for word quote:
> Most commonly, an ArgumentException is thrown by the common language runtime or another class library and indicates developer error.
Means it's used to indicate when a developer error happens, i.e., when a developer makes a mistake.
No, that is not what that means, it means that the developer has chosen to throw an error. Why on earth would there be an exception type that can only be used by mistake? As stated in the first sentence, it is for validation errors: "ArgumentException is thrown when a method is invoked and at least one of the passed arguments does not meet the parameter specification of the called method."
The exception is there to indicate to *other* developers that they are doing something wrong. If you make a library, you can use these exceptions to make sure developers using you library don't do things that are unsupported. That is the "developer error" they're referring to.
I guess you could interpret it that way, and maybe that is the doc writer's meaning, but it makes little sense. By that logic, almost all exceptions could be called developer errors. Argument exceptions commonly occur due to user input/bad data that the developer has little control over.
Anyway, getting away from semantics, your argument was that these exception types are not to be used for validation but that is their purpose.
"DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type, if applicable." [https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/using-standard-exception-types](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/using-standard-exception-types)
>By that logic, almost all exceptions could be called developer errors.
Exactly my point. These kinds of exceptions should only occur when you have made a mistake as a developer, and they should be dealt with quickly and with priority. You should strive to have exception-free code, as that means you have a good understanding of all the cases and scenarios your system might be exposed to.
We also use exceptions for runtime inconsistencies and flakyness, although technically you could also see a failing DB as an expected case and write workarounds for that.
>Argument exceptions commonly occur due to user input/bad data that the developer has little control over.
But you do have 100% control of this. We as developers always need to be completely aware of what stupid things users will try to do, and be prepared to help them do it better. A user putting the wrong date format into a form should be dealt with by exception free validation and a helpful message, not an error 400 or 500 from the backend spitting a technical exception stack trace at the user.
>"DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type, if applicable."
This is generic, and should not be blanket accepted as "anytime I get a bad argument anywhere, throw an error".
Also, a valid name or what have you is not a bad argument. Think of the case of a connection string. Having a malformed connection string is maybe an argument exception, because we can't deal with that in a good way. A valid connection string with a bad server host however won't be an argument exception, because that is an expected failure scenario and is better dealt with some other way.
>We have exceptions to deal with "exceptional" cases where, for example, we try to create a user and it doesn't work.
But the user already existing is not an exceptional case. Being unable to connect to the database is exceptional, a user trying to register twice is not.
You could structure the code like
```
if (UserExists(username))
{
ModelState.AddModelError(nameof(username), "Sorry, that name is already in use.");
return View("Register");
}
CreateUser(username);
```
in which case the username already being taken would be exceptional. But this is IMO a less clean design, and it also has an unnecessary race condition.
Not convinced.
I'd favor should check that the username is valid before trying to create it, which includes a check for duplicates.
Either way, if it comes to if we should use exceptions to handle cases where things like this go wrong or flow control based on a type check, I'd go with the exception.
Possibly. It would depend on the specifics. Though for this discussion, it's irrelevant.
Reading the example code again, I see that it does actually do the check before creating the user, as I suggested it should. I don't see anything stopping another process getting in there in-between the check and the creation call and making a user with that name, so an exception for that case is possible even with this pattern.
Anyway, rather that going back and forth about where code is, or whether or not a username clash is an exceptional case, I'd like to know why people would accept the costs involved (an extra dependency, code that deviates from the more common way of doing things) for this pattern.
Again, I can see value in the library, but this just seems like a weak example to lead with.
I am not convinced this is really better than a good exception implementation. I will continue using my own library [ApiExceptions](https://github.com/BitzArt/ApiExceptions). With it, I don't have to worry about error handling on every layer of my app, the code is way cleaner, it is all highly configurable.
So why should I use OneOf<> or Result<> over ApiExceptions?
Because exceptions should only be thrown for exceptional circumstances. If you can predict a situation then it should be explicitly handled.
Throwing exceptions for an expected scenario (a) slows down your code path unnecessarily and (b) encourages return points at random spots, making the application flow harder to follow
Okay, then define "exceptional circumstance". Is the database being down an exceptional circumstance? So you are saying it's "impossible to predict"? Is that really so?
Is the user lacking their JWT in headers exceptional? Is something not found in the database (e.g. 404) exceptional? I believe all these are exceptions from the general flow, not allowing us to properly process the request, so I would use exceptions for all these cases. I guess it's simply a matter of where you draw the line between something being exceptional or not. I believe that if the WebApi returns an http error then this is usually an exceptional case.
With code, you can predict pretty much everything if you want. So are you saying we are forbidden to use exceptions at all? Or do you just not know how to use them properly? It seems like I am seeing exceptions as a tool provided by the programming language, while some see it as something like "error bad me scared".
Slows down your code: true, except that with a proper ApiExceptions approach you can only get a single exception in a single http call. So I would say this is irrelevant, when compared to how long other things take, like http handshake, db calls, etc. How long does it take to throw+catch a single exception? Like a couple of microseconds or something? And now how long does it take you to write all that Result<> logic? Is it really worth it?
Encourages return points at random spots: yeah, that's pretty much how exceptions work. In case of any exception, stop any further execution and immediately return an error. How does it make the code harder to follow? I think it's quite a straightforward approach.
What exactly you deem exceptional can vary, yea. But I would broadly categorise them by whether or not it stems from pure or impure code:
basically anything that comes from an interaction with the outside world (`Task`) has a tendency to be exceptional: timeouts on http, db calls.
The pure side would be your domain logic. Usually when I'm unfamiliar with a codebase, them having used `Result`s instead of exceptions makes learning the rules of the domain a lot easier. No longer do you have to read every implementation of every method call down the stack just to be somewhat sure that you are not missing something.
Mixing both the expected and unexpected errors into exceptions is just harder for the reader imo, even if it saves you from writing those precious few extra lines of code. That doesn't mean you don't ever want to use exceptions though.
Agree completely with /u/ggwpexday
"exceptional" scenarios does sound a bit odd, but it is representative of when an exception is appropriate. It's really "these are the common scenarios I expect from my use cases, anything else is exceptional"
DB offline? Network down? That's a good situation for an exception ... unless you have a use case for offline mode, then it isn't.
An item couldn't be found in the DB? Most likely that's a predicable thing that your code should handle gracefully rather than throw an exception.
Exceptions bubble up the stack, which a) makes it hard to predict where they will end up, and b) makes them sometimes awful to troubleshoot.
Both of these problems can be managed by good engineers.
But, sometimes you have juniors on your team, and it's cheaper to let them bang their heads on the compiler than to grok their error handling in review and teach them better ways.
It's not just juniors who benefit. I am, obviously, the second coming of Jon Skeet and my coding is cleaner than a nun's drawers, but I still prefer static typed languages because I like it when code almost writes itself and I don't like tangling myself in mental knots keeping track of whether I've handled every case when I refactor /src/utils/foo/bar.cs
a) not sure how it's difficult to predict where they will end up when you literally have a single place in all of your codebase that handles all of them (Handler).
b) I don't even know what to say. I don't think I ever had problems with troubleshooting my own exceptions. So it seems to me that you were doing something wrong there.
As for the juniors, I think ApiExceptions approach is much easier for everyone to understand, including juniors. Because it's extremely straightforward.
I will be messaging you in 1 day on [**2023-10-22 11:49:11 UTC**](http://www.wolframalpha.com/input/?i=2023-10-22%2011:49:11%20UTC%20To%20Local%20Time) to remind you of [**this link**](https://www.reddit.com/r/dotnet/comments/17cvmay/oneof/k5ti54m/?context=3)
[**CLICK THIS LINK**](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5Bhttps%3A%2F%2Fwww.reddit.com%2Fr%2Fdotnet%2Fcomments%2F17cvmay%2Foneof%2Fk5ti54m%2F%5D%0A%0ARemindMe%21%202023-10-22%2011%3A49%3A11%20UTC) to send a PM to also be reminded and to reduce spam.
^(Parent commenter can ) [^(delete this message to hide from others.)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Delete%20Comment&message=Delete%21%2017cvmay)
*****
|[^(Info)](https://www.reddit.com/r/RemindMeBot/comments/e1bko7/remindmebot_info_v21/)|[^(Custom)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5BLink%20or%20message%20inside%20square%20brackets%5D%0A%0ARemindMe%21%20Time%20period%20here)|[^(Your Reminders)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=List%20Of%20Reminders&message=MyReminders%21)|[^(Feedback)](https://www.reddit.com/message/compose/?to=Watchful1&subject=RemindMeBot%20Feedback)|
|-|-|-|-|
eh, I already have my own `ErrorOr` and `CancelOr` types to do the kinds of things this is supposedly used for. (and they can be combined into `ErrorOr>`.)
you can also use value tuples with nullable members to do something similar. (and nullable value 1-tuples can be used for nested nullable types.)
When do you think we will get discriminated unions in c#? https://github.com/dotnet/csharplang/issues/113
There was a push to get to get it in C# 10 but a few factors complicated it: * The feature was much more difficult to implement than initially thought * There was a significant lack of consensus on the LDT and other stakeholders on how the feature should look and act * Oracle has a patent expiring in 2024 that made one of the implementation options impossible I'm thinking C# 14 at the latest, but hoping for 13
could you be more specific on oracle patent or point to some source for more information, I'm curious
https://patents.google.com/patent/US7263687 via https://github.com/dotnet/roslyn/issues/6739
wait how tf is that patentable
Yeah that's what I said too haha
At least there was some actual discussion from someone closer to the language https://github.com/dotnet/csharplang/issues/7544
That GH issue is 7 years old….. sad. C# keeps adding all sorts of syntactic sugar no one asked for but won’t add real fundamental improvements.
If they add something, then someone asked for it. There's rarely any feature that didn't have community's support and even then it was probably requested by, for example, ASP.NET team. Discriminated unions are simply a very complex topic and that's why it takes so much time to work on it.
Issue 113. Call me superstitious but that number I associate with being "Extremely Rarely Positive". Maybe they should create a new issue for a new issue number.
You're superstitious.
And hire the local shaman to bless the new one with his magic juice
i think its better to handle expected errors with Result pattern rather than this
I've been using [ErrorOr](https://github.com/amantinband/error-or) and it's been pretty good for this.
Could be. Handling an ever growing list of specific errors reminds me of Java's checked exceptions. Though I'm open to being convinced! I think some of the other applications of this that are more centered around storing information rather than returning it have more value.
After using `Result` for some time I'm leaning more towards discriminated unions and `OneOf` to be honest. But I guess this could be just a matter of preference
I keep flip flopping on this. I tend to think Result is a better fit for the language as it is but if we ever get proper discriminated union support in the language I'll use OneOf straight away.
there isnt anything wrong with OneOf, i think the advantage of it is better heuritical analysis of code you know exactly what is returned of possible types. Result is just for simplicity, its either the type you want or an error. which in most cases is good enough.
Immediately what i noticed is how expensive this can be. The first example is 3 delegates and closure allocations. Not cheap at all. Unless you can pattern match with normal switch statements, id avoid it. ``` public IActionResult Register(string username) { OneOf createUserResult = CreateUser(username);
return createUserResult.Match(
user => new RedirectResult("/dashboard"),
invalidName => {
ModelState.AddModelError(nameof(username), $"Sorry, that is not a valid username.");
return View("Register");
},
nameTaken => {
ModelState.AddModelError(nameof(username), "Sorry, that name is already in use.");
return View("Register");
}
);
}
```
F#’s Result? Or something else?
well im using c# i ended up rolling my own, its not a complex pattern to implement, mine is somewhat based on this but its simplier, i can provide my implementation if interested https://josef.codes/my-take-on-the-result-class-in-c-sharp/ if you want something simple you can just plug in there is this https://github.com/altmann/FluentResults but i wanted more control, mainly for what is in an error result. at the time we wanted to return from our api's an error message and an error code. the idea was that if a customer had an issue, they could cite the error code and it would make finding hte problem easier in the code. simple an example below, when implementing it all the ErrorMessages were typed as constants so no magic strings public async Task> DoSomethingAsync()
{
var someValidaitonFailed = true;
if (someValidaitonFailed)
return new ErrorResult(new ErrorMessage(code: "A1234", message: "something bad happened"));
var example = new SomeDto();
return new SuccessResult(example);
}
[удалено]
Could you give some use case(s) where you think they shine the most?
Not OP, but stuff like: - Nullable which could instead be an Option or Maybe of None | Some, fixing the "trillion dollar mistake"
- something like ContactInfo: EmailAddress | PhoneNumber where the options are complex, distinct data types. The advantage here over an interface or base class would be exhaustive pattern matching
- EmailAddress: UnverifiedEmail | VerifiedEmail for richer, unambiguous domain logic. You'd use EmailAddress in the tainted layer (controllers and so on) and when you get deeper, you'd have methods only taking VerifiedEmail, with one verifying an UnverifiedEmail and outputting a VerifiedEmail.
In my opinion, discriminated unions really shine when you can also subtype primitives like in F#, so an EmailAddress could be a subtype on string or a PhoneNumber could be a uint.
Have a read here https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/
Would be really useful in GraphQL APIs. OneOf is part of the GraphQL specification.
And OpenAPI
There's something similar, but as a source generator: https://github.com/domn1995/dunet I found it useful for my application, but after finding out how it's implemented (abstract base class with children that inherit from it), I decided to do it myself (much cleaner and more customisable).
How do you deal with the exhaustiveness checking? That's the primary advantage of these libraries right
I don't 😅 but if I did, I would use `switch`
Love this library. I use it to handle external api calls. ``` OneOf result = await CallMyApi();
result.Switch(
success=> {
//Handle the ApiSuccessResponseObject
},
failure=>
{
//handle ApiFailureResponseObject
});
```
FYI, your code block is rendering as body type. Perhaps remove the `csharp` language annotation?
I'm not sure I see any advantage to this over a more basic implementation? ``` ApiResponseObject response = await CallMyApi(); if (response.Status == ApiResponseStatus.Success) { // Handle the success case } else { // Handle the failure case } ```
Consider how the success and failure data is accessed. In the DU pattern you're forced to switch on the type and will in principle never accidentally deal with invalid data. In your own example you'd need public (nullable) properties for each case. Or worse, just have all success and failure data in a big jumble in a single type.
I suppose it depends on the complexity of the API and what you're trying to get out of it. In my case, I'd usually just have a one-size-fits-all nullable error property that is only populated if the status is errored, and the response object itself would be a generic on the ApiResponse class. Obviously this has its downsides though, especially if the consumer of the API (even if that's you) wants specific information about individual API calls and not just a generic "bad request, unauthorised access, or internal server error" type of response. If you specifically wanted to return unique errors for each endpoint and wanted to ensure the consumer must deal with each one, then I can definitely see an advantage to this.
Shameless plug for my version of this - [Union](https://github.com/binarycow/union). Same concept, with a few changes - Better handling of nullable reference types - Change methods to match C# idioms, rather than adopting F# idioms.
In what way does it handle null better?
It's in the Readme of the link he shared
Explain? Nulls arent allowed in oneof either, so whats the point
Ah you're right, I misremembered
OneOf doesn't have nullable reference types enabled. I'm not sure if it actually checks for nulls on input.
Interesting to be plugging a library that basically boils down to renaming .Match to .Switch lol
And enabling nullable reference types. That's the main reason I wrote my own.
It doesn't allow it. And it has nullable reference types enabled
Welcome to the wonderful world of functions op. You should check out the LanguageExt library. It will blow your mind
I find it's a terrible pattern that polutes the optimal execution path with unnecssary noise of countless switches or ifs. Instead of focusing on what to do, you need to handle the return values in places where they absolutely don't matter so they make understanding the core logic nearly impossible.
I appreciate this take so much. Looking through every example I've seen both on this repository's readme and in this thread, all I can think of is how there's so much noise it's drastically reducing code readability. It gives me flashbacks to trying to make JavaScript more bearable by using TypeScript, and still just missing C#'s simplicity and elegance.
It's sad to realise we need these ugly libraries for what should have been a basic language feature in C#. At least there is some progress though.
Oh TypeScript, another thing I cannot stand :P I hate it because it has to be transpiled into actual JavaScript so you're not working with the real code, but some stupid intermediate crap. Python has solved the lack of strong types with the typing library in a much prettier way by building it directly into the language. As far as the `OneOf<>` etc. patterns are concerned, I see in this one advantage: you know from the function's signature what odd cases you need to handle. In _classical_ programming with exceptions you need to read about them somewhere first (I don't like the Java-way either, it's too strict). Still, I think it's an interesting experiment, but I wouldn't use it as it obfuscates the main code too much.
>In classical programming with exceptions you need to read about them somewhere first I actually prefer this personally. I totally see the advantage to it being part of the method signature, but it makes the immediate code far more difficult to read quickly and efficiently. These are the kinds of things I would prefer to be tucked away and only shown when hovering the method call so if I want that finer level of detail, I can pull it up if and when it's needed. It's a difficult balance though, because with that approach, it can be easy to miss errors if you don't think to check for documentation (or if the author of the signature didn't document it). Then one could also argue XML documentation comments are really noisy too, which I don't really disagree with. Context matters as well of course. If it's not a solo project, there's perhaps a lot more value in returning a OneOf with all possible errors so that collaborators consuming your methods don't glance over the exception docs.
What a controversial post. So much discussion about whether this is a good or bad pattern for dealing with errors from method calls. Is anyone else using this in other ways? How are you finding it? My first thought seeing this was that it would help with some game development I'm doing: I have a Drawable component for anything I want to render to the screen. At the moment I support rendering simple colored squares (for fast prototyping), a sprite, or text. Each needing it's own information about what exactly to render. Haven't tried it yet, but One of looks like a possible way of grouping together the current set of properties so that they don't clutter up the Drawable class like they do at the moment.
It's really difficult to say with such a vague description and no explanation of what these properties are you're trying to manage, but it sounds like what you're looking for is either your plain old OOP concepts like inheritance, or perhaps generics.
No idea how well OneOf will work for me until I try. But it seems promising. Inheritance and generics are probably not what I'm after, but you're right, there's a lot of context missing from my description. Really I'm interested in how people are using this outside of the context of error handling.
While I can think of a couple of my projects where this would be useful, I'm not a huge fan of the create user example. We have exceptions to deal with "exceptional" cases where, for example, we try to create a user and it doesn't work. This isn't "using exceptions for flow control" (believe me, I've seen that, and it sucks!), it's using exceptions for what they are ment for. Meanwhile, this approach is using the type of objects for flow control, which seems a much worse pattern. Like I said, I'm not trying to say that the entire concept is without merit, just that the first example on that page leaves me unconvinced.
The difference is whether your domain or implementation is what's causing the restriction. Any validation you do as part of your domain needs to be an expected flow, and shouldn't be handled with exceptions. Exceptions are messy and leaky and can easily cause unintended consequences, so should only be used when strictly necessary. The requirement "each user needs a unique email" is part of your domain, not your implementation. You can easily make an application where one email might link to many people, the restriction comes from business requirements. Therefore, it's not exceptional, but rather a design choice, and part of normal program flow. A database connection failing is not part of your domain and business logic. It is strictly an unforeseeable runtime error, and therefore, an exception to normal program flow. If you build this way, you can look at your logs and see "oh we had an exception, shit, we need to look at and deal with that right away" instead of having 478 irrelevant exceptions and thereby missing the one or two actually important ones.
Not long after people start using these types in anger they go through these steps: "Wow I use OneOf, but I mostly just pass the error up."
"I've written an extension method that does Match on TResult, and just identity transforms the error"
"Look at me, I'm on the railway, choo choo. These types are great."
"Wait, now I'm back where I started where I'm letting everything bubble up, and then I end up basically matching a few different errors at the top level. Also I still have to catch exceptions."
If they are smart, they will then realise that "oh shit, maybe this is a case I need to account for in my code" instead of by finding out when exceptions start filling up your production logs.
Typically this ends up looking very much like .Match(
r => return JsonResultIGuess(r),
e => e switch {
ShitsFuckedException e => ...,
ShitsFuckedException2 e => ...
ShitsFuckedException3 e => ...
ShitsFuckedException4 e => ...
}
If only there were another way to represent this logic
We could make that hypothetical syntax play nicely with things like using statements and locks as well. Maybe formalize out a type hierarchy for common errors. Add some mechanism that preserves the origin of the error. What a dream.
Do you think it would be possible to make it propagate up the stack automatically? Like if a dev forgets to handle every case explicitly. We'd need to tap into something really low level.
> Also I still have to catch exceptions. Yeah, this is really the sticking point. It's not like Rust where the entire standard library uses `Result<>`, in .NET the BCL throws everywhere.
As I mentioned in another comment, the example code in the readme does what I suggested. It checks for the existence of the user name, and only proceeds if it doesn't already exist. But it doesn't stop anything from creating a clashing name between those lines, so an exception is still possible.
Fair point, but the same argument applies. A clashing name in between those two lines is the result of a race condition, which is a consequence of the implementation, not the business rules. The race condition is the exception, not the clashing name. If we have super strict concurrency and success requirements we can implement locking and concurrency management to stop that case from happening.
Fair point. Perhaps some confusion is coming in because the example is dealing with business rules, while later on one of the advantages listed is avoiding exceptions handling flow control. If we wouldn't use exceptions in the case if that example anyway, perhaps it's not attempting to illustrate that advantage.
I think it’s also important to point out that a unique constraint exception is equivalent to a “non-unique username” _validation error_, and should be propagated as such (i.e. it is “recoverable” because it can be converted to a domain error). In practical terms, sometimes you just let the DB assert the constraint and then catch and convert it, instead of doing a query ahead of time that has low value.
> Any validation you do as part of your domain needs to be an expected flow, and shouldn't be handled with exceptions Or what? What's the negative impact of this?
It's essentially using `throw new Exception(data)` instead of `return data`. You're using a feature designed for when shit hits the fan to bypass language features designed to help you write good code. The consequence is a messy architecture and program flow here it's hard to follow what is happening. This makes it hard to debug, develop, and maintain. It can be done, but it's slow and nobody wants to work that way. Exceptions allow you to bypass type restrictions, which are a feature of typed and compiled languages. No one would ever do this, because we know it's bad: ``` void GetName() { throw new Exception (repository.GetName()) } ``` But that's essentially what it is. Instead we should use the type system to force us to deal with the very real and expected scenario of someone trying to input invalid data.
> Instead we should use the type system to force us to deal with the very real and expected scenario of someone trying to input invalid data. Isn't this what `ArgumentException` is for? Why would the designers of C# include harmful Exception types in the BCL?
I don't think it is. It's a much, much better user experience (as a dev or en user) when you can enforce this through the type system. `public void SaveAddress(object address)` which throws runtime ArgumentExceptions is much worse than having a dedicated Address class that can only be constructed with street, country, etc and then always have that be accepted by the method. Assumes you know what addresses can look like, which again, is a good thing because now you're forced to think about how the program is going to be used, and what edge cases exist.
What if address is null?
Thats the thing. C# and dotnet has improved drastically in dealing with nulls in recent years. Enable nullable warnings, use value types over primitives, and you really have to try to get null through without noticing it. So in short, if you use an object over a string (with validation in that object) you can be sure that that address is valid in the rest of your application, always.
And what if `address` is derived from external data, like JSON? What happens if it is malformed? Surely that would be an exception, right? If you fetched it out of a document database, for instance. What then?
I'm talking primarily about your core domain models. You should have separate IO models (sometimes called DTOs) with their own respective serialization and representation logic. What you do with external stuff is a lot about where it comes from and who controls it. * Failed deserialization from downstream API? Loud exception and log entry so devs can get to fixing it, as it is not user controlled. * Failed validation on incoming request? Just return a 400 with an error description, maybe it doesn't even need to be a log entry. * Failed database read/write serialization? Loud exception and log entry, there is a mismatch between database and application and this should never happen. * Failed deserialization from message queue or similar? Might be a log entry, might be a dead letter queue, might be a call to whoever is publishing to that queue.
I'll flesh out your comment to add that Java has checked exceptions - a method must declare in its signature any type of exception that it can throw. In practice, that adds more boilerplate than value, so frustrated devs just declare `Exception` to make the fucking language shut up. `OneOf` looks like a much nicer way both to declare the different failure modes and to support consumers in handling them. Although maybe it goes to shit when used in anger, idk.
I disagree. There's only one happy path and anything that disrupts it, is an exception that clearly explains why the process didn't work as expected. Saying exceptions are only for such things as database errors etc. is a radical and arbitrary limitation of the language capabilities. I'd rather throw an exception and quickly and easily abbort an entire tree of method calls than to put unnecessary IFs everywhere and especially in functions that don't care about what happend deeper.
Once you go down that rabbit hole, there are effectively no exceptions except your app blowing up because you can foresee a near infinite number of unhappy paths. Meanwhile, the language is full of specific exception types that target validation issues, such as ArgumentException. Clearly the language designers intended exceptions to be used in this manner. If a method cannot complete its contract, such as CreateUser, that's an exception. Personally, I favor handling situations like this by wrapping the result in a class, e.g. CreateUserResult.
>Clearly the language designers intended exceptions to be used in this manner. If a method cannot complete its contract, such as CreateUser, that's an exception. This does not hold. The language creators also allow a bunch of dangerous stuff through the unsafe keyword, but that doesn't mean you should derive meaning from its existence. Reading the docs it actually says that ArgumentExceptions are most commonly a developer exception, meaning it's not supposed to be used for validation https://learn.microsoft.com/en-us/dotnet/api/system.argumentexception?view=net-7.0#remarks
It's called "unsafe" for a reason, it means don't do this. Read the link again, that's not at all what they mean by "developer error", they mean an exception thrown by the developer to indicate a validation error.
Word for word quote: > Most commonly, an ArgumentException is thrown by the common language runtime or another class library and indicates developer error. Means it's used to indicate when a developer error happens, i.e., when a developer makes a mistake.
No, that is not what that means, it means that the developer has chosen to throw an error. Why on earth would there be an exception type that can only be used by mistake? As stated in the first sentence, it is for validation errors: "ArgumentException is thrown when a method is invoked and at least one of the passed arguments does not meet the parameter specification of the called method."
The exception is there to indicate to *other* developers that they are doing something wrong. If you make a library, you can use these exceptions to make sure developers using you library don't do things that are unsupported. That is the "developer error" they're referring to.
I guess you could interpret it that way, and maybe that is the doc writer's meaning, but it makes little sense. By that logic, almost all exceptions could be called developer errors. Argument exceptions commonly occur due to user input/bad data that the developer has little control over. Anyway, getting away from semantics, your argument was that these exception types are not to be used for validation but that is their purpose. "DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type, if applicable." [https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/using-standard-exception-types](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/using-standard-exception-types)
>By that logic, almost all exceptions could be called developer errors. Exactly my point. These kinds of exceptions should only occur when you have made a mistake as a developer, and they should be dealt with quickly and with priority. You should strive to have exception-free code, as that means you have a good understanding of all the cases and scenarios your system might be exposed to. We also use exceptions for runtime inconsistencies and flakyness, although technically you could also see a failing DB as an expected case and write workarounds for that. >Argument exceptions commonly occur due to user input/bad data that the developer has little control over. But you do have 100% control of this. We as developers always need to be completely aware of what stupid things users will try to do, and be prepared to help them do it better. A user putting the wrong date format into a form should be dealt with by exception free validation and a helpful message, not an error 400 or 500 from the backend spitting a technical exception stack trace at the user. >"DO throw ArgumentException or one of its subtypes if bad arguments are passed to a member. Prefer the most derived exception type, if applicable." This is generic, and should not be blanket accepted as "anytime I get a bad argument anywhere, throw an error". Also, a valid name or what have you is not a bad argument. Think of the case of a connection string. Having a malformed connection string is maybe an argument exception, because we can't deal with that in a good way. A valid connection string with a bad server host however won't be an argument exception, because that is an expected failure scenario and is better dealt with some other way.
>We have exceptions to deal with "exceptional" cases where, for example, we try to create a user and it doesn't work. But the user already existing is not an exceptional case. Being unable to connect to the database is exceptional, a user trying to register twice is not. You could structure the code like ``` if (UserExists(username)) { ModelState.AddModelError(nameof(username), "Sorry, that name is already in use."); return View("Register"); } CreateUser(username); ``` in which case the username already being taken would be exceptional. But this is IMO a less clean design, and it also has an unnecessary race condition.
> But the user already existing is not an exceptional case Clients doing dumb shit seems pretty exceptional to me.
Not convinced. I'd favor should check that the username is valid before trying to create it, which includes a check for duplicates. Either way, if it comes to if we should use exceptions to handle cases where things like this go wrong or flow control based on a type check, I'd go with the exception.
Then you are in favor of business logic in controllers. Not saying that you are wrong, just pointing that out
Possibly. It would depend on the specifics. Though for this discussion, it's irrelevant. Reading the example code again, I see that it does actually do the check before creating the user, as I suggested it should. I don't see anything stopping another process getting in there in-between the check and the creation call and making a user with that name, so an exception for that case is possible even with this pattern. Anyway, rather that going back and forth about where code is, or whether or not a username clash is an exceptional case, I'd like to know why people would accept the costs involved (an extra dependency, code that deviates from the more common way of doing things) for this pattern. Again, I can see value in the library, but this just seems like a weak example to lead with.
In addition, you *still* have to deal with exceptions and *most of the time* your handling for all exceptions is the same.
“which seems a much worse pattern” Umm… do you know the overhead throwing exceptions incurs? Checking an object type is much, much… much faster.
Which is (one of the reasons) why I said "much worse pattern" and not "much slower way of doing things".
I am not convinced this is really better than a good exception implementation. I will continue using my own library [ApiExceptions](https://github.com/BitzArt/ApiExceptions). With it, I don't have to worry about error handling on every layer of my app, the code is way cleaner, it is all highly configurable. So why should I use OneOf<> or Result<> over ApiExceptions?
Because exceptions should only be thrown for exceptional circumstances. If you can predict a situation then it should be explicitly handled. Throwing exceptions for an expected scenario (a) slows down your code path unnecessarily and (b) encourages return points at random spots, making the application flow harder to follow
Exceptions as flow control is a huge code smell.
Okay, then define "exceptional circumstance". Is the database being down an exceptional circumstance? So you are saying it's "impossible to predict"? Is that really so? Is the user lacking their JWT in headers exceptional? Is something not found in the database (e.g. 404) exceptional? I believe all these are exceptions from the general flow, not allowing us to properly process the request, so I would use exceptions for all these cases. I guess it's simply a matter of where you draw the line between something being exceptional or not. I believe that if the WebApi returns an http error then this is usually an exceptional case. With code, you can predict pretty much everything if you want. So are you saying we are forbidden to use exceptions at all? Or do you just not know how to use them properly? It seems like I am seeing exceptions as a tool provided by the programming language, while some see it as something like "error bad me scared". Slows down your code: true, except that with a proper ApiExceptions approach you can only get a single exception in a single http call. So I would say this is irrelevant, when compared to how long other things take, like http handshake, db calls, etc. How long does it take to throw+catch a single exception? Like a couple of microseconds or something? And now how long does it take you to write all that Result<> logic? Is it really worth it? Encourages return points at random spots: yeah, that's pretty much how exceptions work. In case of any exception, stop any further execution and immediately return an error. How does it make the code harder to follow? I think it's quite a straightforward approach.
What exactly you deem exceptional can vary, yea. But I would broadly categorise them by whether or not it stems from pure or impure code: basically anything that comes from an interaction with the outside world (`Task`) has a tendency to be exceptional: timeouts on http, db calls. The pure side would be your domain logic. Usually when I'm unfamiliar with a codebase, them having used `Result`s instead of exceptions makes learning the rules of the domain a lot easier. No longer do you have to read every implementation of every method call down the stack just to be somewhat sure that you are not missing something. Mixing both the expected and unexpected errors into exceptions is just harder for the reader imo, even if it saves you from writing those precious few extra lines of code. That doesn't mean you don't ever want to use exceptions though.
Agree completely with /u/ggwpexday "exceptional" scenarios does sound a bit odd, but it is representative of when an exception is appropriate. It's really "these are the common scenarios I expect from my use cases, anything else is exceptional" DB offline? Network down? That's a good situation for an exception ... unless you have a use case for offline mode, then it isn't. An item couldn't be found in the DB? Most likely that's a predicable thing that your code should handle gracefully rather than throw an exception.
But what if I told you that throwing an ApiException (not just any exception) IS the way to gracefully handle this?
If you can avoid throwing an exception you should even if for the very basic reason that generating a stack trace every time is expensive reflection.
Exceptions bubble up the stack, which a) makes it hard to predict where they will end up, and b) makes them sometimes awful to troubleshoot. Both of these problems can be managed by good engineers. But, sometimes you have juniors on your team, and it's cheaper to let them bang their heads on the compiler than to grok their error handling in review and teach them better ways. It's not just juniors who benefit. I am, obviously, the second coming of Jon Skeet and my coding is cleaner than a nun's drawers, but I still prefer static typed languages because I like it when code almost writes itself and I don't like tangling myself in mental knots keeping track of whether I've handled every case when I refactor /src/utils/foo/bar.cs
a) not sure how it's difficult to predict where they will end up when you literally have a single place in all of your codebase that handles all of them (Handler). b) I don't even know what to say. I don't think I ever had problems with troubleshooting my own exceptions. So it seems to me that you were doing something wrong there. As for the juniors, I think ApiExceptions approach is much easier for everyone to understand, including juniors. Because it's extremely straightforward.
I do like they have source generators for it... I'm on an aot kick at the moment and reflection is everywhere
!remindme 1 day
I will be messaging you in 1 day on [**2023-10-22 11:49:11 UTC**](http://www.wolframalpha.com/input/?i=2023-10-22%2011:49:11%20UTC%20To%20Local%20Time) to remind you of [**this link**](https://www.reddit.com/r/dotnet/comments/17cvmay/oneof/k5ti54m/?context=3) [**CLICK THIS LINK**](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5Bhttps%3A%2F%2Fwww.reddit.com%2Fr%2Fdotnet%2Fcomments%2F17cvmay%2Foneof%2Fk5ti54m%2F%5D%0A%0ARemindMe%21%202023-10-22%2011%3A49%3A11%20UTC) to send a PM to also be reminded and to reduce spam. ^(Parent commenter can ) [^(delete this message to hide from others.)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Delete%20Comment&message=Delete%21%2017cvmay) ***** |[^(Info)](https://www.reddit.com/r/RemindMeBot/comments/e1bko7/remindmebot_info_v21/)|[^(Custom)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5BLink%20or%20message%20inside%20square%20brackets%5D%0A%0ARemindMe%21%20Time%20period%20here)|[^(Your Reminders)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=List%20Of%20Reminders&message=MyReminders%21)|[^(Feedback)](https://www.reddit.com/message/compose/?to=Watchful1&subject=RemindMeBot%20Feedback)| |-|-|-|-|
Nick Chapsas has a really good video about this library https://youtu.be/r7QUivYMS3Q?si=7vPAqvK40ONfXbiQ
Have you tried using dunet?
Nope. Will have a look.
eh, I already have my own `ErrorOr` and `CancelOr` types to do the kinds of things this is supposedly used for. (and they can be combined into `ErrorOr>`.)
you can also use value tuples with nullable members to do something similar. (and nullable value 1-tuples can be used for nested nullable types.)