Skip to content

Conversation

martincostello
Copy link
Contributor

@martincostello martincostello commented Aug 24, 2024

  • Add assertion methods for IReadOnlyDictionary<K, V> using net9.0 and later only so that [OverloadResolutionPriority] can be used to avoid ambiguity caused by types that implement both dictionary interfaces.
  • Update to .NET 9 SDK and C# 13.

Resolves #834.

- Add assertion methods for `IReadOnlyDictionary<K, V>`.
- Add assertion methods for `Dictionary<K, V>`  to resolve overload ambiguity when using a concrete dictionary as it implements both interfaces.

Resolves shouldly#834.
@jnm2
Copy link
Collaborator

jnm2 commented Aug 24, 2024

Add assertion methods for Dictionary<K, V> to resolve overload ambiguity when using a concrete dictionary as it implements both interfaces.

This leaves other dictionary types such as ImmutableDictionary out in the cold, which will be noticed.

I may do a followup to add an internal declaration of .NET 9's OverloadResolutionPriorityAttribute and use it to help with this, but that only comes into play if you set your langversion to 13 (currently, preview). The only docs I know of for this are the spec itself, https://github.com/dotnet/csharplang/blob/main/proposals/overload-resolution-priority.md. This is how it works:

image

We could also consider removing the IDictionary extension method or moving it to a different namespace. I think these days it should be rare to have a dictionary that does not implement IReadOnlyDictionary.

@slang25 @SimonCropp Do you have any thoughts?

@jnm2
Copy link
Collaborator

jnm2 commented Aug 24, 2024

Well, one important issue with moving or removing the extension method is, how do you then assert on something typed as the interface IDictionary<,>?

Is it worth hitting ImmutableDictionary at least, or maybe a few more concrete types, or perhaps just wait for irritated feedback to roll in? :D

@jnm2
Copy link
Collaborator

jnm2 commented Aug 24, 2024

Anyone who does hit this issue could declare their own extension method for the dictionary class that we didn't think of, like ImmutableDictionary/ConcurrentDictionary/ReadOnlyDictionary/etc, and simply call the Shouldly extension method after casting to IReadOnlyDictionary. This may not be an obvious workaround, but we could add this to our docs.

As a workaround, we could also say "use LangVersion 13 or later, regardless of your target framework." That would work too, once .NET 9 and C# 13 release in November.

Another option: We could add the new extension method under #if NET9_0_OR_GREATER. That way it won't cause disruption at all, because people are expected to use langversion 13 with .NET 9 (and to get a worse experience across the board if they don't). And then OverloadResolutionPriorityAttribute will work.

@martincostello Would you be able to benefit from this new extension method if it was only available when the test project targets .NET 9?

@martincostello
Copy link
Contributor Author

This leaves other dictionary types such as ImmutableDictionary out in the cold, which will be noticed.

I hadn't considered things like ImmutableDictionary, FrozenDictionary, ReadOnlyDictionary etc. I could look to add overloads for those too if desired.

Would you be able to benefit from this new extension method if it was only available when the test project targets .NET 9?

For me that wouldn't be a problem, as the app where I found the need for this extension is going to be running .NET 9 rc.1 next month (assuming no last-minute issues).

@jnm2
Copy link
Collaborator

jnm2 commented Aug 26, 2024

Okay, perhaps we start with using OverloadPriorityAttribute and only making this overload available in .NET 9 (need to add a net9.0 framework target for this), instead of adding overloads for concrete types, and then revisit as needed?

@martincostello
Copy link
Contributor Author

Sure, I can do that - how do you want to go about that given that the .NET 9 SDK is still pre-release until November?

@jnm2
Copy link
Collaborator

jnm2 commented Aug 28, 2024

As soon as the CI image has a .NET 9 preview version that contains the compiler update to recognize OverloadResolutionPriorityAttribute, we can update our global.json file with minimal hassle, for example to 9.0.100-preview.7 if that works in CI:

shouldly/global.json

Lines 1 to 7 in 50e0045

{
"sdk": {
"version": "8.0.303",
"allowPrerelease": true,
"rollForward": "latestFeature"
}
}

Then we need to change the langversion to preview, with a comment that we'll change it to 13 when .NET 9 ships:

<LangVersion>11</LangVersion>

Then add an additional net9.0 to TargetFrameworks in Shouldly.csproj and Shouldly.Tests.csproj.

Then wrap this around the added overload, along with applying [OverloadResolutionPriority(1)]:

#if NET9_0_OR_GREATER

@jnm2
Copy link
Collaborator

jnm2 commented Aug 28, 2024

Ideally we'd choose the earliest preview version that is actually required in order to get the compiler recognition of OverloadResolutionPriorityAttribute. That would help with any contributors to the repo who might already have that older preview installed, as well as being the best chance of all three of these images having at least that preview version or later:

os: [macos-latest, ubuntu-latest, windows-latest]

@jnm2
Copy link
Collaborator

jnm2 commented Aug 28, 2024

Okay. Through trial and error, I've confirmed that the compiler only starts recognizing OverloadResolutionPriorityAttribute in .NET 9 Preview 7, in spite of the attribute being added to the core libraries in Preview 5.

https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview7/csharp.md#prioritize-better-overloads-with-overloadresolutionpriority-attribute
Via https://devblogs.microsoft.com/dotnet/dotnet-9-preview-7/

This means that the global.json version should be 9.0.100-preview.7.24407.12, and we'll just have to see if all the CI images have preview 7 yet. We could script them to install preview 7 also, or just be patient.

@martincostello
Copy link
Contributor Author

Why not just use actions/setup-dotnet to install the SDK? IMO that's best practice as it's deterministic and doesn't need to wait on GitHub to install anything. This approach even works for their daily builds from their CI.

My question was more geared towards merging - using a preview is trivial, but do you want main using prerelease software? Because otherwise this is unmergeable until November.

@jnm2
Copy link
Collaborator

jnm2 commented Aug 30, 2024

That sounds good too! Glad to know you're familiar already.

Yes, main using prerelease SDK is good with me. It's usually a safe bet, and there are options in the event of a rare surprise.

- Update to .NET 9 preview 7 SDK.
- Add .NET 9 TFM.
- Update test projects to .NET9.
- Update to LangVersion latest.
- Only support `IReadOnlyDictionary<K, V>` for .NET 9 and later.
- Use `[OverloadResolutionPriority]` to prefer `IReadOnlyDictionary` to `IDictionary`.
- Remove overloads for `Dictionary<K, V>`.
Copy link
Collaborator

@jnm2 jnm2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff! Thanks for the help. LGTM after reversing the langversion edits.

Use C# 13 for shiny new features like `[OverloadResolutionPriority]`.
Revert project-specific LangVersion changes.
@jnm2
Copy link
Collaborator

jnm2 commented Aug 30, 2024

@slang25, do you mind giving this a look and merging?

@jnm2
Copy link
Collaborator

jnm2 commented Sep 20, 2024

@slang25 Next time I remember this PR exists, I'll probably merge it :D

Copy link
Member

@slang25 slang25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. I think we should separately update the net5.0 tfm to net8.0, so that we have a policy of netstandard2.0 + Current LTS + Current STS for the main package

@slang25 slang25 merged commit db16e5a into shouldly:master Sep 22, 2024
3 checks passed
@martincostello martincostello deleted the gh-834-readonlydictionary branch September 22, 2024 06:04
@slang25 slang25 added this to the 4.3.0 milestone Jan 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for IReadOnlyDictionary
3 participants