-
Notifications
You must be signed in to change notification settings - Fork 17
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 a new analyzer for checking record equality implementations #94
Conversation
Did you intentionally not check for a custom |
Yes. There is already a built-in C# warning CS0659 that emits a warning if you implement
That's definitely possible. I know some developers like to use record for its concise syntax shortcut (i.e. primary constructor syntax) to quickly create data structures that hold a bunch of variables, e.g: internal record MyRecord(int MyInt, string MyString); In those cases, it's true that equality isn't the main concern. However, starting in .NET 8, C# 12 supports primary constructor syntax for classes and structs too—so using records should now be reserved for truly wanting a value type class where equality works as expected. And in my experience in code reviews, most are unaware of the enumerable equality problem (or forgot about it). For my team's code, equality is critically important, and we've had bugs in the past due to failure to override equality.
The issue you referenced seems to stem from creating a custom enumerable type that performs value equality internally in its That's a different approach than what my team's project has done. My team's project also uses standard immutable collection types in records heavily for json serialization/deserialization, and we've not run into the serialization issues mentioned in stackoverflow. |
...truments.Analyzers/Correctness/RecordWithEnumerablesShouldOverrideDefaultEqualityAnalyzer.cs
Show resolved
Hide resolved
...truments.Analyzers/Correctness/RecordWithEnumerablesShouldOverrideDefaultEqualityAnalyzer.cs
Outdated
Show resolved
Hide resolved
...truments.Analyzers/Correctness/RecordWithEnumerablesShouldOverrideDefaultEqualityAnalyzer.cs
Outdated
Show resolved
Hide resolved
...ments.Analyzers.UnitTests/RecordWithEnumerablesShouldOverrideDefaultEqualityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
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.
Overall great job and I (we) appreciate the contribution - thanks for educating me on this issue!
Justification
For C#
record
types, which are meant to represent a value type, the compiler will automatically generateEquals
andGetHashCode
such that the members of therecord
are compared for equality—this contrasts toclass
where the defaultEquals
behavior is to compare reference equality of theclass
instance.However, if the
record
contains members ofIEnumerable
, the default behavior of the generatedEquals
implementation is to compare the reference equality of the enumerable member, not the more expected value equality of each enumerable element.Thus for any
record
that contains enumerables, it is important to manually implementEquals
andGetHashCode
such that the enumerables are compared viaSequenceEqual
if ordering matters, orUnorderedEquals
—a helper method provided by NationalInstruments.Core—if not.In the past, we've relied on code reviews to enforce this requirement, but it can get tedious and easy to miss. Additionally, it does not allow other teams that are not aware of the default
record
behavior to learn about the requirement.By providing a custom analyzer, we can auto enforce that all
record
types with enumerables must provide a manually implementedEquals
andGetHashCode
, making code reviews easier and educates others about why the defaultrecord
behavior doesn't work for enumerables.Implementation
record
type's properties to see if there are enumerables.NI1019
warning if therecord
has enumerable properties but doesn't implementEquals
.Testing
Added several unit tests to verify the new warning fires or doesn't fire for various scenarios.
Built the analyzer and tested it in ASW codebase, made sure it shows the warning properly:
Encouragingly, the analyzer also found existing
record
violations in ASW that I will work on fixing later.