-
-
Notifications
You must be signed in to change notification settings - Fork 10
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
Generate IEquatable.Equals, object.Equals, object.GetHashCode implementations #56
Conversation
The whole branch needs some refactoring. @amis92 |
Ad. So, in regards to Honestly, I've spent a few hours today researching equality subject (again!). My findings:
A random summary of my thoughts:
|
Equals implementations performance testI did a small performance test comparing different I interpret the results like this: All implementations take about the same time to complete, with the exception of the My suggestion is that we head for the tuple implementation. Inheritance in equalsImplementing IMO we have two ways of finishing up the implementation:
What are your thoughts on this? |
I think this says it all; better to not do it than do it wrong.
These are records after all, which are tuples with named elements, and as such, it's understandable if they are sealed by default. One can always provide an option to not have them sealed so it's not a restriction (for hacking) but it shouldn't be encouraged by default. I would even go as far as not providing that option initially just in case YAGNI. From a design perspective (less technical argumentation), a sealed record is not surprising but an open record that doesn't deliver on some expected behaviour across an inheritance hierarchy will lead to disappointment and pain.
I think this is a good starting point that can always be improved on. My 2¢ even though you never asked for them. 😉 |
First of all - I agree with @atifaziz. We really shouldn't ignore the possible implications of inheritance on Equals. First and foremost, because if we do it wrong, it'll be inexplicably hard to find what's wrong when someone uses them in dictionaries, or simply List.Contains and it doesn't work. So, either we generate it by default but only if the record is sealed, or we require an opt-in, for which we can raise a warning/error if the class is not sealed. It's much better to start with a highly restricted feature and widen the possible applications, than produce code which doesn't adhere to .NET's equality rules. And for that reason, tuple-equality-shortcut should be used only for implementations of |
Regarding the benchmark, actually the int, string and bool (and all the integer types) together with their nullable counterparts, can always use All the other types should use |
Regarding parametrizing the I'd imagine something like that: public sealed class RecordAttribute : Attribute
{
public RecordAttribute()
{
}
public RecordAttribute(Parts parts)
{
Parts = parts;
}
public Parts Parts { get; } = Parts.Common;
}
[Flags]
public enum Parts
{
None = 0,
Constructor = 0b1,
Withers = 0b10,
Builder = 0b100,
Deconstructor = 0b1000,
Common = Constructor | Withers | Builder | Deconstructor,
Equals = 0b10000,
EqualityOperator = 0b100000,
GetHashCode = 0b1000000,
Equality = Equals | EqualityOperator | GetHashCode
} What do you think? |
src/Amadevus.RecordGenerator.Generators/RecordGeneratorOptions.cs
Outdated
Show resolved
Hide resolved
👍
I would call it Nice idea with In C#, a
Consequently, I would rename The equality members ( I would also recommend inverting some of the flags as an overall design. You want the generator to produce the most sensible and commonly expected features (albeit opinionated?) of a record and opting out some and opting in other nice-to-haves or esoteric cases. In short, I'm thinking this: [Flags]
public enum Features
{
Basic = 0, // all !excluded + !included
// opt-out
ExcludeConstructor = 0b0001,
ExcludeWithers = 0b0010,
ExcludeEquality = 0b0100,
ExcludeEqualityOperator = 0b1000,
// opt-in
IncludeBuilder = 0b0001_0000,
IncludeDeconstruct = 0b0010_0000,
IncludeUpdate = 0b0100_0000,
} As an aside, shouldn't this design be discussed and iterated in a separate issue as opposed to as part of this PR? |
Yes, there's actually an issue for that since long time: #45 Let's move over that discussion there. |
I am not sure wether or not we should leave the equality features as an opt-in. In the end they can make a siginicant difference when working with collections and most people will probably be using the types in collections. I can agree that the class should be sealed when implementing equality. It is a more restricted approach but it closes a risky area. |
Let's implement this feature semi-agnostically of that [opt-in/out]. Let's pop a warning into |
- Added SyntaxSnippetGenerators - Added MethodBuilder
Done some refactorings. More to come. I would like to write some UnitTests. Can I create a UnitTest project, would you like to create a UnitTest project or would you like to stick to the IntegrationTests? A quick summary of the things discussed above regarding the equality implementation in this first iteration:
Please correct me if I misunderstood something. |
I'll create a UnitTest project.
We'll use
Yes. Although I've been reading though https://github.com/dotnet/csharplang/blob/master/proposals/records.md and below the record struct example there's another implementation that makes sense:
This uses I don't know if it'll be faster than EqualityComparer, and this'll box structs could be used only for reference classes. Just a thought.
I don't agree, we shouldn't seal classes ourselves when needed. Rather, if the customer requests equality (maybe by default), on a non-sealed class, we'll warn that the given class must be sealed for equality to be generated.
Probably. I'll do the unit tests and flags today or tomorrow. |
src/Amadevus.RecordGenerator.Generators/GeneratedCodeAttributeGenerator.cs
Outdated
Show resolved
Hide resolved
src/Amadevus.RecordGenerator.Generators/SyntaxSnippetGenerators.cs
Outdated
Show resolved
Hide resolved
Note: this PR is still a work in progress. I will do more refactorings, write tests, increase functionality etc. Currently I'm implementing the sealed analyzer, altough I won't be able to finish it before options are available. best regards |
Sealed-analysis can be done not as an Analyzer per se, but rather as a Warning Diagnostic returned via IProgress passed to the GenerateAsync method in the main RecordGenerator. We just need to define the DiagnosticDescriptor and if Equality is requested to be generated, we send the warning into the |
And to keep your motivation up, I'm really glad you took up this challenge. It's a complicated feature with a lot of pesky details to work out. I'm very happy we have this discussion and you're still on track to break that wall! 🚀 |
Thanks, looking good. |
- Adjusted EqualityUsageAnalyzer
src/Amadevus.RecordGenerator.Generators/EqualityUsageAnalyzer.cs
Outdated
Show resolved
Hide resolved
We need to finish off this PR ASAP, I feel it's getting too long. Let's touch up cosmetics, test it manually and merge it. Then we can work on fixing up the smaller stuff, because the longer I browse through the more issues I see. We'll also be able to parallelize the work on those additional issues and/or writing tests. Can you complete this PR soon? |
Hi @amis92, I agree with you that the PR should be finished soon. Unfortunately there are, even when ignoring refactorings and tests, 2 important changes left:
I think these two points must certainly be in a working state before releasing. Unfortunately, today is the swiss national day so I'll be busy and I'm flying into vacation next week. I will put the PR out of Draft, so everybody is welcome to help in order to finish this asap. Another option would be to wait another 2 weeks before releasing, until then I'd be able to finish everything in a certain quality. best regards |
Implemented standalone |
Implemented operator equals |
The operator equals implementation uses (as VS does) the Fortunately, Roslyn already throws a warning CS0661 so we don't have to. |
@amis92, |
How do you feel about creating more interfaces? I get the feeling that I'm not able to create clean UnitTests because everything is coupled so tightly. A starting point could be a |
Implementation of Issue #19
Work in progress