-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Enable Unsafe.As NotNullIfNotNull on .NET Standard 2.0 #42827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming CI passes, LGTM.
Thank you! |
Happy to help! 🎉 @jkotas small question, since this change is very small do you think there's a chance it will end up being integrated into the public 5.0 release of the |
@jeffhandley, looking over the bar we've set, this almost meets it: it won't introduce new warnings, it is related to new annotations in .NET 5, and it is based on feedback... but it's not a commonly-used API (it's very advanced, and this will only show up for netstandard2.0 consumers). Thoughts on whether to try to port it to release/5.0? It is very low risk. |
@stephentoub I'm using this API quite a bit in the That is to say, personally if this made it into the final 5.0 package it would be appreciated! |
What is the bar we are shooting for with nullability annotations for netstandard2.0? I thought that the nullability annotations in netstandard2.0 are largely non-existent and there is no consistency, so this fix does not really move the needle on the nullability annotations when you are using netstandard2.0. |
Correct. The only APIs from dotnet/runtime that will have annotations for a netstandard2.0 consumer are those we publish in packages available independently of netcoreapp. But it's question of whether this actually hurts. Those unannotated APIs in netstandard2.0 are "oblivious", so the compiler won't warn about them. But if a nullable-enabled library consumes this package, it will end up producing more warnings than it should, because the API is annotated. |
@jkotas Yeah, I see no nullability annotations on .NET Standard 2.0 in general, but those ones from the .NET Standard 2.0 target of the |
Thanks for reporting and fixing this, @Sergio0694. While this fix is very low risk, it doesn't meet the bar we've defined for fixing nullable annotation attributes in 5.0. Here's the bar we're using at this point. This checks off every aspect except the last bullet.
Referring to the API usage data, this API is used in 1.1% of apps/libraries scanned. Sorry for the inconvenience on this. |
Thanks @jeffhandley, is the low usage the only blocker? This API is helping to optimize our new MVVM Toolkit package for the Windows Community Toolkit. We'll be shipping it in the Nov/Dec timeframe. This new .NET Standard based MVVM library is the successor to MVVMLight, so we're expecting it to get picked up quite a bit. |
To be clear, is the impact more significant to you than adding a |
If you are using this API a lot, you are doing something wrong. This API is a very unsafe construct, and it should be used only in a few most performance critical situations. |
Hey @jkotas, I guess I can answer to that 😄 There are a couple places where I'm using that API in the MVVM Toolkit, and they're indeed in a performance oriented type. In particular, there's a usage of the // Assume we have these locals
object handler;
object recipient;
TMessage message;
// Here we perform an unsafe cast to enable covariance for delegate types.
// We know that the input recipient will always respect the type constraints
// of each original input delegate, and doing so allows us to still invoke
// them all from here without worrying about specific generic type arguments.
Unsafe.As<MessageHandler<object, TMessage>>(handler)(recipient, message); Basically I have the following situation:
public delegate void MessageHandler<in TRecipient, in TMessage>(TRecipient recipient, TMessage message)
where TRecipient : class
where TMessage : class;
So doing that unsafe cast basically tricks the runtime into just calling the The alternatives I've considered here would've been:
This is something I've actually talked about quite a bit with @333fred and @tannergooding too in the C# Discord server. Given the exact specifications of this type and the way it's implemented, using this trick should be perfectly valid, as all the various constraints and unsafe casts will always be guaranteed to be valid, so there will never be a type violation in doing so. This is really just a workaround for a lack of proper support for basically doing the equivalent of "try to invoke this delegate with these inputs, and just throw if they are not valid". With the exception that here we already know the inputs are valid, so we don't even check. In an ideal world (as in, with you guys having unlimited time and budget to implement all sorts of proposals even for less requested features), my idea was that On the other hand, as a result of this and a number of other (less unsafe) optimizations this new implementation is both much faster than all other competitor types from other common libraries, and using virtually no memory at all for broadcasts:
Hope this helps to clarify why the usage of that arguably very niche API in that library in particular 😊 |
The specific use of Unsafe.As that you have highlighted sounds reasonable to me. I understand that the generic constrains are not always expressive enough to do what you need. But the other uses of |
I'm really glad to hear that! Thank you for taking the time to read through that and glance over the code, I appreciate it 😊 As for the other point on those bounds checks being removed, I did that after doing some profiling, which seemed to indicate that some amount of time was spent just in those two assignments in the first tight loop in the Removing those checks cut the reported CPU time there in half, and resulted in an overall 10% improvement in my benchmarks:
My rationale here was that a ~10% overall speedup seemed enough to consider this bounds checks removal worth it in this case. The safety here is guaranteed by the fact that there is a dedicated property (here, with comment) to track the total number of items in each mapping, which is used to retrieve the rented array from the pool. Doing that guarantees that the rented array will always be as large or greater than necessary even in case every existing handler in that loop matches the token and is added to the array. |
In dotnet/runtime, we would likely give up this ~10% speedup to avoid this type of unsafe code. Here are some ideas where you can save the cycles instead:
|
What if there is a corner case bug in DictionarySlim where the property returns wrong size? |
Hey @jkotas - thank you so much for the feedback and suggestions! 😊 I made some changes starting from them and made a new PR that completely removes all To reply to your previous points:
Yup, basically ended up applying both those suggestions, I've documented everything in the new PR.
Just for sake of clarify, the As a final note, to get back to @michael-hawker's original point, I was wondering if the expected adoption of the MVVM Toolkit would be enough to have this PR (I mean this one for .NET 5) meet the requirements for the .NET 5 release? Otherwise as Stephen said, I can just sprinkle a bunch of |
It would not. This issue does not affect users of MVVM Toolkit. It just affects internal implementation details of MVVM Toolkit. |
I understand, yeah that's a fair point. Thank you for your time! 😊 |
Working around lack of annotations below .NET Standard 2.1, see dotnet/runtime#42827 for more info
Working around lack of annotations below .NET Standard 2.1, see dotnet/runtime#42827 for more info
Overview
The
Unsafe.As<T>(object)
API was not using the nullability attribute for the return value when not on .NET Standard 2.1:runtime/src/libraries/System.Runtime.CompilerServices.Unsafe/ref/System.Runtime.CompilerServices.Unsafe.cs
Lines 21 to 24 in 37c5a3e
This caused some incorrect null reference warnings when using the library in a .NET Standard 2.0 project, eg.:
This PR simply removes the compiler directive so that the .NET Standard 2.0 target will still have access to that nullability attribute.