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

Inherited classes Equals ignored when called from the base class #38

Closed
christianrondeau opened this issue Dec 18, 2022 · 6 comments · Fixed by #39
Closed

Inherited classes Equals ignored when called from the base class #38

christianrondeau opened this issue Dec 18, 2022 · 6 comments · Fixed by #39
Assignees
Labels
bug Something isn't working

Comments

@christianrondeau
Copy link

christianrondeau commented Dec 18, 2022

First of all, great library, thanks for making it!

Problem

When calling .Equals() or System.Collections.Generic.EqualityComparer on a base class that uses [Equatable], the generated Equals implementation will not call inherited Equals implementations.

  • When the base class doesn't use [Equatable], reference equality is used (which is not what we want)
  • When the base class uses [Equatable], the base class only checks it's own fields and ignores inherited types fields.

Reproduction

public class GeneratorBaseClassBugTests
{
  [Equatable]
  public partial class MyContainer
  {
	  public MyBase Content { get; set; }
  }
  
  [Equatable]
  public abstract partial class MyBase
  {
  }
  
  [Equatable]
  public partial class MyImpl : MyBase
  {
	  public int Value { get; set; }
  }

  [Test]
  public void Test()
  {
    var v1 = new MyContainer { Content = new MyImpl { Value = 1 } };
    var v2 = new MyContainer { Content = new MyImpl { Value = 2 } };

    Assert.That(v1, Is.Not.EqualTo(v2));
  }
}

Solution

This code is generated in the container:

global::System.Collections.Generic.EqualityComparer<global::MyNamespace.MyBase>.Default.Equals(Content!, other.Content!)

This shorts-circuits the concrete type by directly checking the base type. This would work:

global::System.Object.Equals(Content!, other.Content!)

Version

.NET 6.0, Generator.Equals 2.6.0

Thanks!

@diegofrata
Copy link
Owner

diegofrata commented Dec 18, 2022

This is definitely a bug, thanks for raising it.

@diegofrata
Copy link
Owner

I think we still want to keep the EqualityComparer for value types as to avoid unnecessary boxing. So use Object.Equals only for reference types.

I might have some time this week to look at this, unless you'd like to have a go at it yourself and send a PR!

Let me know!

@christianrondeau
Copy link
Author

Yes, that sounds right!

I just started playing with generators yesterday (they are great, I should have used them earlier), and I'm swamped right now so I wouldn't be able to make a PR in the next two weeks. However if the issue is still open early next year, I'll definitely try my hand at it :)

Thanks for responding!

@diegofrata
Copy link
Owner

I looked into this issue this morning, I think the fix is a bit more involved. Basically, all the comparers use EqualityComparer.Default for the element comparison -- these need also be fixed, my view is that we create another comparer, let's say VirtualEqualityComparer<T> (probably needs better naming).

In this comparison, at the static ctor, we check whether the type is a reference type && not sealed, if so, then on Equals/Hashcode operations we call the Object methods, else, we call the EqualityComparer methods.

@ssttgg does it sound good to you?

@diegofrata
Copy link
Owner

@christianrondeau this should be fixed now in 2.7.2.

@christianrondeau
Copy link
Author

Wow that's great, before the end of the holidays even! Hope you are having a great time, and thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants