Skip to content
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

Add an equality comparer for AssemblyName in RazorAnalyzerAssemblyResolver #76752

Closed
wants to merge 4 commits into from

Conversation

chsienki
Copy link
Member

AssemblyName doesn't implement GetHashCode so each instance was unique, leading to duplicates in the hash set. This PR adds an equality comparer that uses the FullName of the AssemblyName as the comparison item for the hash set.

@chsienki chsienki requested a review from a team as a code owner January 15, 2025 00:23
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Jan 15, 2025
@chsienki
Copy link
Member Author

@dotnet/roslyn-compiler for review please :)

{
public bool Equals(AssemblyName? x, AssemblyName? y) => x?.FullName.Equals(y?.FullName) == true;

public int GetHashCode(AssemblyName obj) => Hash.GetFNVHashCode(obj.FullName);
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this direct call to a hash function is preferable to string.GetHashCode()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, so string.GetHashCode is a bit tricky, as it changes between runs. This is purely in memory so it should be fine, but on desktop it can also change between AppDomains. This should only run on core as it ends up in ServiceHub, but this assembly does cross compile, and servicehub supports .NETFX, so could conceivably be loaded on framework. I figured the number of calls to this is going to be small enough that we can live with the hit, but if we're worried about the cost we can just go with string.GetHashCode instead.


private class AssemblyNameEqualityComparer : IEqualityComparer<AssemblyName>
{
public bool Equals(AssemblyName? x, AssemblyName? y) => x?.FullName.Equals(y?.FullName) == true;
Copy link
Contributor

@RikkiGibson RikkiGibson Jan 15, 2025

Choose a reason for hiding this comment

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

It's not obvious to me this does the right thing when one or both AssemblyNames are null. Maybe it would be simpler to branch like x is null ? y is null : x.FullName.Equals(y?.FullName) (if we can rely on FullName always being non-null, when the containing instance is non-null.)

Copy link
Member

Choose a reason for hiding this comment

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

On .NET core only the short name is considered for loading purposes in the majority case. This code is going to differentiate based on the full name though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, so I mentioned this on Aleksey's comment, but we control the parent interface which is annotated as never being null, and the comparer is only ever used here. I've updated it to ! those values to indicate we know it will never be null as opposed to making the test stronger. Happy to switch back to a stronger null check if you prefer though.

Copy link
Member Author

Choose a reason for hiding this comment

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

On .NET core only the short name is considered for loading purposes in the majority case. This code is going to differentiate based on the full name though.

This was deliberate. Full name includes the version string i.e something like Microsoft.CodeAnalysis.Razor.Compiler, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

Whereas .Name (which I assume is what you mean by shortname?) only includes the assembly name itself (Microsoft.CodeAnalysis.Razor.Compiler). Given that this is AssemblyLoadContext code its completely valid to load two different versions of the same assembly.

If Razor specifically asks for version 9.0.0.0 I don't want to consider an 8.0.0.0 version to be equivalent.

{
public bool Equals(AssemblyName? x, AssemblyName? y) => x?.FullName.Equals(y?.FullName) == true;

public int GetHashCode(AssemblyName obj) => Hash.GetFNVHashCode(obj.FullName);
Copy link
Contributor

Choose a reason for hiding this comment

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

obj

Should we be prepared to deal with null as an input?

Copy link
Member Author

Choose a reason for hiding this comment

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

IEqualityComparer<T>.GetHashCode is marked [DisallowNull] which I beleive the intent is that it will never be called with a null value. We also own the parent IAnalyzerAssemblyResolver interface, and this comparer is only used here, so annotations say we can never see a null value for AssemblyName even if the comparer did support calling it with a null value. I'm happy to handle null though if you'd prefer.


private class AssemblyNameEqualityComparer : IEqualityComparer<AssemblyName>
{
public bool Equals(AssemblyName? x, AssemblyName? y) => x?.FullName.Equals(y?.FullName) == true;
Copy link
Member

Choose a reason for hiding this comment

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

On .NET core only the short name is considered for loading purposes in the majority case. This code is going to differentiate based on the full name though.

@chsienki
Copy link
Member Author

@dotnet/roslyn-compiler for second round of feedback. Thanks!

@chsienki
Copy link
Member Author

Closing in favor of #76808

@chsienki chsienki closed this Jan 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants