-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Split Microsoft.Toolkit.Diagnostics package #3726
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
Split Microsoft.Toolkit.Diagnostics package #3726
Conversation
Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌 |
Alright, CI is passing now and the smoke test is running fine too! 🥳
|
I am seeing some errors when I run the tests in VisualStudio but nothing when I run the CI scripts. 🤔 |
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.
This is awesome, thanks @Sergio0694! 🎉🎉🎉
Amazing to see the impact up the chain in the Toolkit, and excited to explore some of the Source stuff in the future and understand that world, but this is a great first step.
It should also make us feel a bit more comfortable at putting some basic helpers in the .NET world for the Toolkit that we use upwards in the Toolkit, as I think splitting any of those chains up is pretty hard.
Hello @michael-hawker! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
@RosarioPulella if this gets merged before we're done |
@msftbot merge when @RosarioPulella approves |
Hello @michael-hawker! Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:
If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you". |
I'm seeing those two in the I think the reason why the CI doesn't detect them is because those tests are under an This PR shouldn't have affected the behavior there though, so I think it's possible they might've been failing before too? FYI @michael-hawker |
Hmm... I thought I had run all the Unit Tests locally recently and they were fine. Let me check on the main branch to compare. I'm trying to remember why those were Debug only now though... I think I just did it for perf/initial testing. |
Just tested |
Yup, I had a few issues building main at first, but then was able to run the Unit Tests with no problems. @Sergio0694 ideas? |
Let me try main on my end too and then I'll double check my changes here to make sure I didn't just slip an incorrect refactor in one of those tests - I might've just not noticed it at first due to those paths not being executed by the CI, so that'd explain it 🤔 |
@michael-hawker All good, fixed in d61cd24! @RosarioPulella Thanks for spotting that! |
@@ -193,8 +195,15 @@ private void ReadjustKeys() | |||
private int ToInnerIndex(int outerIndex) | |||
{ | |||
#if DEBUG | |||
Guard.IsInRange(outerIndex, 0, Count, nameof(outerIndex)); | |||
Guard.IsFalse(_interspersedObjects.ContainsKey(outerIndex), nameof(outerIndex)); // We can't map a inserted key to the original collection! | |||
if ((uint)outerIndex >= Count) |
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.
Do we have to worry about the lower-bound check here that was originally done by IsInRange
or is that handled by the (uint) conversion?
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.
The lower bound is handled by the (uint)
cast, it's the same trick I use in the Guard
APIs as well to skip one conditional branch and checks both edges of the range with just one. This works because signed integers are stored in 2-complemement, with the most significant bit being the sign. If you cast to uint
instead, if the value was negative, it'd just underflow and go to int.MaxValue + x - 1
, which will always be greater than the other comparand since that's guaranteed to be a non-negative value in the int
range 🙂
@Sergio0694 awesome, I've done that before too! Though it was converting a C++ statement to C# for the Gaze code... 😋 And that's why we have unit tests, nice catch @RosarioPulella! @Sergio0694 would it be better to use ThrowHelper type logic here to keep the guards at runtime for the CI but not bog down the methods? Though I think part of my concern was that I wasn't entirely sure if I had missed any edge cases and didn't want to take down the control in the event of a problem, but I did also try to test this pretty heavily because it's such a unique collection type... Thoughts? |
Generally speaking I'd say it would depend on where the input is coming from - if it's from a user-provided value it would be a good idea to keep the tests, whereas if the value is coming from some other internal component within the codebase, keeping them in Debug only is fine. As in, so that the checks kept in Release would effectively only be to validate inputs and not to check for correctness in the internal implementation. That said, the overhead of the whole control is so much greater than what those checks could possibly do that I think adding them in Release would never be noticeable anyway, so it's absolutely fine if you'd like to add them there as well. And of course we could still use some local throw helper methods anyway to keep the codegen cleaner. 🙂 |
@Sergio0694 yeah, considering we accidently broke the test, but wouldn't have caught it in CI worried me a bit. Mind you I don't expect us to be touching this code too much. If you'd like to update it for the more internal ThrowHelper approach, please feel free and remove the #debug flags. I know it'd just take you a second compared to me fumbling with it... 😋 |
@michael-hawker Done in 6a33532 😄 |
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.
Looks good, Thanks @Sergio0694 for going the extra bit for this one.
Related to #3594, working towards reducing footprint and increasing modularity
PR Type
What kind of change does this PR introduce?
What is the current behavior?
The
Guard
andThrowHelper
APIs are part of theMicrosoft.Toolkit
package.This has a few downsides with respect to binary size for consuming package and overall API surface for consumers.
The
Guard
andThrowHelper
APIs relied on a number of different NuGet packages that needed to be pulled in:System.Diagnostics.Contracts
System.ValueTuple
System.Memory
System.Runtime.CompilerServices.Unsafe
These packages are only needed by those APIs, and yet with
Microsoft.Toolkit
being referenced by every other package in the whole Toolkit, every other package ended up having a dependency on those as well even when there was no use for them at all. This is not ideal both for final binary size, for the increased number of potentially unwanted APIs being imported by consumers, and due to the fact that many consumers want to minimize the number of NuGet dependencies in general.Furthermore, the
.Diagnostics
APIs can be very useful for all kinds of developers, especially those working in backend scenarios and with a particular focus on performance. It was not ideal to force those developers to also import APIs such as observable collections and other more UI-oriented APIs that were available in the base package (in fact I've spoken with a few devs that didn't like having to include anything other than just theGuard
andThrowHelper
APIs when adding the package).For consumers on .NET 5 that don't use .NET Native, splitting the
Guard
andThrowHelper
APIs away means 80KB less in binary size (when not using IL trimming) when those APIs are not needed, which is effectively a 75% reduction on the base package.What is the new behavior?
The
Guard
andThrowHelper
APIs have now been moved to a newMicrosoft.Toolkit.Diagnostics
package.Splitting this package completely removes all those dependencies from all other packages too 🚀
This work will also make it easier in the future to introduce a
.Diagnostics
source-only package, if we wanted to.Note: marking as breaking change due to the APIs being moved to a different package, but this PR doesn't do any changes to the overall API surface across the various packages - the APIs are still all exactly the same, just modularized differently.
PR Checklist
Please check if your PR fulfills the following requirements:
Pull Request has been submitted to the documentation repository instructions. Link:Sample in sample app has been added / updated (for bug fixes / features)Icon has been created (if new sample) following the Thumbnail Style Guide and templates