Closed tig closed 3 months ago
There is. It's just hidden. Because the code has only changed by the inclusion or exclusion of one element: [Nullable(0)]
applied to the type parameter.
No execution differences exist.
The warnings you're getting are for a reason - because the execution model isn't what you actually expect and isn't consistent.
There's no trouble if I don't add where T : notnull
clause to the class.
Weird that posted out of order...
Anyway it's a response to
There's no trouble if I don't add
where T : notnull
clause to the class.
Just copying it for convenience:
There is. It's just hidden. Because the code has only changed by the inclusion or exclusion of one element: [Nullable(0)]
applied to the type parameter.
No execution differences exist.
The warnings you're getting are for a reason - because the execution model isn't what you actually expect and isn't consistent.
The only reason they don't appear without it is because the analysis simply isn't being performed.
Weird that posted out of order...
Anyway it's a response to
There's no trouble if I don't add
where T : notnull
clause to the class.Just copying it for convenience:
There is. It's just hidden. Because the code has only changed by the inclusion or exclusion of one element:
[Nullable(0)]
applied to the type parameter.No execution differences exist.
The warnings you're getting are for a reason - because the execution model isn't what you actually expect and isn't consistent.
But yeah, in reference to this...
Aside from the attribute, which isn't executed in any way at run time (and, in fact, won't even exist if the assembly is trimmed), you end up with identical IL with or without that type param constraint. Thus, it telling you there's something odd is legit.
Those generic method calls are resolved at compile time, even though the concrete types are created at JIT-time upon first encountering a new value type (reference types share one implementation in most cases).
To be run-time safe, anything we have that is generic that actually does anything with the data given to it with type T must exist either narrowly defined to only accept what will always work, for all types accepted, or has to have separate implementations for the cases that don't behave the same implicitly.
It's annoying and makes it understandable why the option to not use a generic is still sometimes chosen in new BCL features to this day.
I guess I need you to provide a PR to my PR showing me exactly how to do this right because I have not been able to follow most of what you've explained. I will sleep on it and see if that helps, but I'm just not getting all the nuances. I struggle to understand some of your vague references and translate them into actual code:
Well... The problem is it's a tough decision.
I, like you, really want and appreciate generics when possible.
But, they have some really irksome caveats like this, which require specific considerations and decisions to be made.
If you wanna do it that way, though, where you put it in as-is but minus the constraint, and the caveats can be addressed later, that's cool as far as I'm concerned.
But I'd make the events themselves accept the non-generic base class CancelEventArgs, instead, if you haven't already, both to avoid an API surface change and so consumers who don't need to pass the actual values in aren't forced to supply them.
Or actually put it in with the constraint, so we have the warning as a reminder to fix it... That might be smarter.
But still with the event delegates accepting the immediate base class.
In any event-handlers for them, you simply do a type check to see if it's the thing you expect.
Because remember - if it got overridden and it's not what you expected, that's a run-time error.
(Remember, I'm just whiteboarding here. This all isn't explicit answers/recommendations, and that's intentional - just options)
One of the issues that makes this annoying AF on our end is there are a ton of ways to approach it, but all have pros and cons. There's no perfect solution. There are even entire libraries out there that try to deal with this exact set of issues.
To get actually consistent behavior between struct and class, which is the bigger issue at play, here, I'd typically try to favor reference semantics OR value semantics, but not both (note I didn't say types). It's easier to support and easier to use. Reference semantics are a lot easier to implement. Value is harder both for us AND the consumer (even though C# is already pass/return by value...reference types muddy that a lot).
If you want a longer explanation with my general and preliminary opinions, I can certainly give you one.
But suffice it to say that the syntactic issues and the very legitimate warnings from analysis due to nullability (or lack thereof, for structs) are symptoms, and the disease is a clash of expectations with what the language and CLR actually do or can do, on its own, without extra work, which avoiding/reducing is part of the reason for wanting and using generics in the first place, right?. Just kinda moves where the work has to be done. 🤷♂️
Or we just throw up our hands and let consumers deal with it, which is the lightest touch approach.
But then we also shouldn't do anything internally in the library that assumes one or the other without a graceful fallback, if that's the chosen path. And I'd also suggest you explicitly annotate the type parameter as nullable, if you do that (so, <T?>
), to let the analyzer know null is allowed. Otherwise it is assuming it isn't.
if you do that (so, <T?>
You can't do that. Gives a syntax error.
BTW, after sleeping on it and writing a bunch of code, I now get what you've bee putting down. Mostly.
Coolio. Yeah I saw the PR and it looks like you're going the right way.
Still some stuff we can shore up, but not important enough to hold it up IMO.
Maybe drop an appropriate [ComponentGuarantees] on that event, as well, for now, in case those tweaks are breaking?
I mis-named this. I originally thought it was just for representing the property that represented the primary state of a View, but that's way to restrictive. This class is useful for any event that is tracking the state of a property.