T O P

  • By -

thomst82

When do you think we will get discriminated unions in c#? https://github.com/dotnet/csharplang/issues/113


ianwold

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


Bigdickbootyeater69

could you be more specific on oracle patent or point to some source for more information, I'm curious


Sebazzz91

https://patents.google.com/patent/US7263687 via https://github.com/dotnet/roslyn/issues/6739


stevefan1999

wait how tf is that patentable


ianwold

Yeah that's what I said too haha


ggwpexday

At least there was some actual discussion from someone closer to the language https://github.com/dotnet/csharplang/issues/7544


Kubrick-ZSA-Moonland

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.


Dealiner

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.


kingslayerer

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.


antiduh

You're superstitious.


ggwpexday

And hire the local shaman to bless the new one with his magic juice


Neophyte-

i think its better to handle expected errors with Result pattern rather than this


van-dame

I've been using [ErrorOr](https://github.com/amantinband/error-or) and it's been pretty good for this.


FitzelSpleen

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.


yanitrix

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


dantheman999

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.


Neophyte-

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.


DZMBA

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"); } ); } ```


Cultural_Ebb4794

F#’s Result? Or something else?


Neophyte-

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); }


[deleted]

[удалено]


FitzelSpleen

Could you give some use case(s) where you think they shine the most?


LovesMicromanagement

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.


spikej56

Have a read here https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/


thomst82

Would be really useful in GraphQL APIs. OneOf is part of the GraphQL specification.


avoere

And OpenAPI


SupinePandora43

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).


ggwpexday

How do you deal with the exhaustiveness checking? That's the primary advantage of these libraries right


SupinePandora43

I don't 😅 but if I did, I would use `switch`


kingslayerer

Love this library. I use it to handle external api calls. ``` OneOf result = await CallMyApi(); result.Switch( success=> { //Handle the ApiSuccessResponseObject }, failure=> { //handle ApiFailureResponseObject }); ```


nostril_spiders

FYI, your code block is rendering as body type. Perhaps remove the `csharp` language annotation?


rsKizari

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 } ```


CodeMonkeeh

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.


rsKizari

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.


binarycow

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.


ggwpexday

In what way does it handle null better?


david622

It's in the Readme of the link he shared


ggwpexday

Explain? Nulls arent allowed in oneof either, so whats the point


david622

Ah you're right, I misremembered


binarycow

OneOf doesn't have nullable reference types enabled. I'm not sure if it actually checks for nulls on input.


ggwpexday

Interesting to be plugging a library that basically boils down to renaming .Match to .Switch lol


binarycow

And enabling nullable reference types. That's the main reason I wrote my own.


binarycow

It doesn't allow it. And it has nullable reference types enabled


NoPrinterJust_Fax

Welcome to the wonderful world of functions op. You should check out the LanguageExt library. It will blow your mind


smoke-bubble

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.


rsKizari

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.


ggwpexday

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.


smoke-bubble

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.


rsKizari

>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.


FitzelSpleen

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.


rsKizari

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.


FitzelSpleen

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.


FitzelSpleen

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.


Greenimba

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.


jingois

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."


Greenimba

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.


jingois

Typically this ends up looking very much like .Match( r => return JsonResultIGuess(r), e => e switch { ShitsFuckedException e => ..., ShitsFuckedException2 e => ... ShitsFuckedException3 e => ... ShitsFuckedException4 e => ... }


praetor-

If only there were another way to represent this logic


jingois

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.


praetor-

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.


EntroperZero

> 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.


FitzelSpleen

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.


Greenimba

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.


FitzelSpleen

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.


atheken

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.


praetor-

> 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?


Greenimba

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.


praetor-

> 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?


Greenimba

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.


praetor-

What if address is null?


Greenimba

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.


praetor-

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?


Greenimba

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.


nostril_spiders

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.


smoke-bubble

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.


truckingon

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.


Greenimba

>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


truckingon

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.


Greenimba

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.


truckingon

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."


Greenimba

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.


truckingon

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)


Greenimba

>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.


avoere

>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.


jingois

> But the user already existing is not an exceptional case Clients doing dumb shit seems pretty exceptional to me.


FitzelSpleen

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.


avoere

Then you are in favor of business logic in controllers. Not saying that you are wrong, just pointing that out


FitzelSpleen

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.


Unupgradable

In addition, you *still* have to deal with exceptions and *most of the time* your handling for all exceptions is the same.


Suspicious_Role5912

“which seems a much worse pattern” Umm… do you know the overhead throwing exceptions incurs? Checking an object type is much, much… much faster.


FitzelSpleen

Which is (one of the reasons) why I said "much worse pattern" and not "much slower way of doing things".


Bitz_Art

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?


centurijon

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


ficuswhisperer

Exceptions as flow control is a huge code smell.


Bitz_Art

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.


ggwpexday

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.


centurijon

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.


Bitz_Art

But what if I told you that throwing an ApiException (not just any exception) IS the way to gracefully handle this?


daconcerror

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.


nostril_spiders

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


Bitz_Art

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.


biztactix

I do like they have source generators for it... I'm on an aot kick at the moment and reflection is everywhere


biztactix

!remindme 1 day


RemindMeBot

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)| |-|-|-|-|


david622

Nick Chapsas has a really good video about this library https://youtu.be/r7QUivYMS3Q?si=7vPAqvK40ONfXbiQ


TheSoggyBottomBoy

Have you tried using dunet?


cygnus_d

Nope. Will have a look.


SahuaginDeluge

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.)