Giter Site home page Giter Site logo

diegofrata / generator.equals Goto Github PK

View Code? Open in Web Editor NEW
122.0 4.0 18.0 272 KB

A source code generator for automatically implementing IEquatable<T> using only attributes.

License: MIT License

C# 100.00%
source-generators equality csharp roslyn-analyzer csharp-sourcegenerator

generator.equals's Introduction

Nuget

Generator.Equals

A source code generator for automatically implementing IEquatable<T> using only attributes.


Requirements

In order to use this library, you must:

  • Use a target framework that supports .NET Standard >= 2.0
  • Set your project's C# LangVersion property to 9.0 or higher.

Installation

Simply add the package Generator.Equals to your project. Keep reading to learn how to add the attributes to your types.

Migrating from version 2

Migrating to version 3 is very straightforward.

  1. Ensure projects are targeting C# 9.0 or latter using the MSBuild property LangVersion.
  2. Be aware that IEquatable<T> for classes is now implemented explicitly in order to support deep equality. As a result, the method Equals(T) method is no longer marked as public. Most code should still work, requiring only to be recompiled as the ABI has changed.

If you have an existing project using Generator.Equals and don't need any of the new features, you can still use version 2.x. The differences are minimal between both major versions.

Usage

The below sample shows how to use Generator.Equals to override the default equality implementation for a C# record, enhancing it with the ability to determine the equality between the array contents of the record.

using Generator.Equals;

[Equatable]
partial record MyRecord(
    [property: OrderedEquality] string[] Fruits
);

class Program
{
    static void Main(string[] args)
    {
        var record1 = new MyRecord(new[] {"banana", "apple"});
        var record2 = new MyRecord(new[] {"banana", "apple"});

        Console.WriteLine(record1 == record2);
    }
}

Need more than records? Generator.Equals supports properties (and fields) also across classes, structs and record structs.

using Generator.Equals;

[Equatable]
partial class MyClass
{
    [DefaultEquality] 
    private int _secretNumber = 42;

    [OrderedEquality] 
    public string[] Fruits { get; set; }
}

[Equatable]
partial struct MyStruct
{
    [OrderedEquality] 
    public string[] Fruits { get; set; }
}

[Equatable]
partial record struct MyRecordStruct(
    [property: OrderedEquality] string[] Fruits
);

Supported Comparers

Below is a list of all supported comparers. Would you like something else added? Let me know by raising an issue or sending a PR!

Default

This is the comparer that's used when a property has no attributes indicating otherwise. The generated code will use EqualityComparer<T>.Default for both equals and hashing operation.

Fields are not used in comparison unless explicitly annotated. To enable the default comparison for a field, annotate it with the DefaultEquality attribute.

IgnoreEquality

[IgnoreEquality] 
public string Name { get; set; }

As the name implies, the property is ignored during Equals and GetHashCode calls!

OrderedEquality

[OrderedEquality] 
public string[] Fruits { get; set; } // Fruits have to be in the same order for the array to be considered equal.

This equality comparer will compare properties as a sequence instead of a reference. This works just like Enumerable.SequenceEqual, which assumes both lists are of the same size and same sort.

Bear in mind that the property has to implement IEnumerable and the that the items themselves implement equality (you can use Generator.Equals in the items too!).

UnorderedEquality

[UnorderedEquality] 
public string[] Fruits { get; set; } // Does not care about the order of the fruits!

[UnorderedEquality] 
public IDictionary<string, object> Properties { get; set; } // Works with dictionaries too!

This equality comparer will compare properties as an unordered sequence instead of a reference. This works just like Enumerable.SequenceEqual, but it does not care about the order as long as the all values (including the repetitions) are present.

As with OrderedEquality, bear in mind that the property (or key and values if using a dictionary) has to implement IEnumerable and the that the items themselves implement equality (you can use Generator.Equals in the items too!).

SetEquality

[SetEquality] 
public HashSet<string> Fruits { get; set; } // Fruits can be in any order and it can be repeated

This equality comparer will do a set comparison, using SetEquals whenever the underlying collection implements ISet<T>, otherwise falling back to manually comparing both collections, which can be expensive for large collections.

Hashing always returns 0 for this type of equality,

ReferenceEquality

[ReferenceEquality] 
public string Name { get; set; } // Will only return true if strings are the same reference (eg. when used with string.Intern)

This will ignore whatever equality is implemented for a particular object and compare references instead.

StringEquality

[StringEquality(StringComparison.CurrentCulture | CurrentCultureIgnoreCase | InvariantCulture | InvariantCultureIgnoreCase | Ordinal | OrdinalIgnoreCase)]
public string Title { get; set; } // Will use the StringComparison set in constructor when comparing strings

CustomEquality

class LengthEqualityComparer : IEqualityComparer<string>
{
    public static readonly LengthEqualityComparer Default = new();

    public bool Equals(string? x, string? y) => x?.Length == y?.Length;

    public int GetHashCode(string obj) => obj.Length.GetHashCode();
}

class NameEqualityComparer 
{
    public static readonly IEqualityComparer<string> Default = new SomeCustomComparer();
}


[CustomEquality(typeof(LengthEqualityComparer))] 
public string Name1 { get; set; } // Will use LengthEqualityComparer to compare the values of Name1.

[CustomEquality(typeof(NameEqualityComparer))] 
public string Name2 { get; set; } // Will use NameEqualityComparer.Default to compare values of Name2.

[CustomEquality(typeof(StringComparer), nameof(StringComparer.OrdinalIgnoreCase))] 
public string Name2 { get; set; } // Will use StringComparer.OrdinalIgnoreCase to compare values of Name2.

This attribute allows you to specify a custom comparer for a particular property. For it to work, the type passed as an argument to CustomEqualityAttribute should fulfill AT LEAST one of the following:

  • Have a static field/property named Default returning a valid IEqualityComparer instance for the target type;
  • Have a static field/property with the same name passed to the CustomComparerAttribute returning a valid IEqualityComparer instance for the target type;
  • Implement IEqualityComparer and expose a parameterless constructor.

Advanced Options

Explicit Mode

The generator allows you to explicitly specify which properties are used to generate the IEquatable.

To do this, set the Explicit property of EquatableAttribute to true and specify the required properties using DefaultEqualityAttribute or other attributes.

using Generator.Equals;

[Equatable(Explicit = true)]
partial class MyClass
{
    // Only this property will be used for equality!
    [DefaultEquality] 
    public string Name { get; set; } = "Konstantin"; 
    
    public string Description { get; set; } = "";
}

Ignore Inherited Members

You can also choose to ignore members from parent classes/record by setting IgnoreInheritedMembers to true.

using Generator.Equals;

class Person 
{
    public string Name { get; set; }
}

[Equatable(IgnoreInheritedMembers = true)]
partial class Doctor : Person
{
    // Only members in the Doctor class will be used for comparison.
    public string Id { get; set; }
    public string Specialization { get; set; }
}

generator.equals's People

Contributors

danielegbers avatar diegofrata avatar fjmorel avatar havendv avatar hayer avatar nickstrupat avatar ssttgg avatar tom-englert avatar wainwrightmark avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

generator.equals's Issues

Could not load file or assembly 'netstandard, Version=2.1.0.0,

When I run the example from README, I get printed 'False'.

Probably it has something to do with the weird warning I'm getting:

An instance of analyzer Generator.Equals.EqualsGenerator cannot be created from C:\Users\Patrik\.nuget\packages\generator.equals\0.3.0\analyzers\dotnet\cs\Generator.Equals.dll: Could not load file or assembly 'netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified..

Here's the code: https://github.com/PatrikBak/Bug.Generator.Equals

Visual Studio 2019 Version 16.8.1

What if properties of a class are also generated by another code generator?

Hi! Thank you for this awesome library.

Suppose I have a ViewModel class like this:

using System.IO;
using System.Windows.Media.Imaging;

using CommunityToolkit.Mvvm.ComponentModel;
using Dapper.Contrib.Extensions;
using Generator.Equals;

namespace CoilQueryTool.ViewModels
{
    [Equatable(IgnoreInheritedMembers = true)]
    [Table("CoilMap")]
    internal partial class CoilRecordViewModel : ObservableObject
    {
        public CoilRecordViewModel ShallowCopy()
        {
            return (CoilRecordViewModel)MemberwiseClone();
        }

        [IgnoreEquality]
        public int Id { get; set; }

        [ObservableProperty]
        private string coilId = null!;

        [ObservableProperty]
        private string? name;

        [ObservableProperty]
        private string? connector;

        [ObservableProperty]
        private string? pN;

        [ObservableProperty]
        private string? description;

        [ObservableProperty]
        private string? machine;

        [ObservableProperty]
        private string? mode;
    }
}

Since most of the properties of this class is generated by another code generator - CommunityToolkit.Mvvm's code generator, Generator.Equals will not generate code for these properties.

I am wondering if [DefaultEquality] can be used like this:

class MyViewModel
{
    [DefaultEquality]
    [ObservableProperty]
    private string title;
}

DictionaryEquality is redundant

We can juse use UnorderedSequenceEquality, since IDictionary<TKey, TValue> extends IEnumerable<KeyValuePair<TKey, TValue>>, and KeyValuePair<TKey, TValue> implements Equals and HashCode correctly.

using Generator.Equals;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Test
{
    [Equatable]
    partial record MyRecord(
        [property: UnorderedSequenceEquality] Dictionary<string, int> Mutable,
        [property: UnorderedSequenceEquality] ImmutableDictionary<string, int> Immutable
    );

    class Program
    {
        static void Main()
        {
            var mutable1 = new Dictionary<string, int> { { "A", 0 }, { "B", 42 } };
            var mutable2 = new Dictionary<string, int> { { "A", 0 }, { "B", 42 } };

            var record1 = new MyRecord(mutable1, mutable1.ToImmutableDictionary());
            var record2 = new MyRecord(mutable2, mutable2.ToImmutableDictionary());

            Console.WriteLine(record1 == record2);
        }
    }
}

Remove the runtime dependency

Another idea is to remove the runtime dependency, especially for those who don't use custom Comparers. They can be delivered explicitly (with global:: full typepaths to avoid possible collisions).

Originally posted by @HavenDV in #22 (comment)

Support for Reference Equality

There might be some niche cases where you want this:

    public class ObjectReferenceEqualityComparer<T> : IEqualityComparer<T>
        where T : class
    {
        private static IEqualityComparer<T> _defaultComparer;
        public static IEqualityComparer<T> Default => _defaultComparer ?? (_defaultComparer = new ObjectReferenceEqualityComparer<T>());

        bool IEqualityComparer<T>.Equals(T x, T y)
        {
            return ReferenceEquals(x, y);
        }

        int IEqualityComparer<T>.GetHashCode(T obj)
        {
            return RuntimeHelpers.GetHashCode(obj);
        }
    }

README contains an uncompilable code

using Generator.Equals;

[Equatable]
partial class MyClass
{
    [SequenceEquality] 
    public string[] Fruits { get; set; }
);

The last line should be };.

Support StringEqualityAttribute on collections of strings

While experimenting with Generator.Equals, I found that if I try to customize string equality to be case insensitive for a property that is for example an array of strings, the code generated by Generator.Equals will currently incorrectly assume the property is a string. GetHashCode code produces a compilation error:

CS0411: The type arguments for method 'HashCode.Add(T, IEqualityComparer?)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

I assume the only way Generator.Equals can handle this case is via a custom equality, but I believe the case to be common enough to consider supporting it directly. Making StringEqualityAttribute handle it seems like it would be nice. Other than that, in general it would be nice if generation failed with a better error when an attribute is used with a member of an unsupported type.

Moreover, if I try to combine StringEqualityAttribute and UnorderedEqualityAttribute on the same property, generation will obey unordered equality and the intent to treat the string elements as case insensitive will be ignored. Personally I think this is also a compelling scenario to support.

Repro

using Generator.Equals;

Console.WriteLine("Hello, World!");

[Equatable]
public partial class Resource
{
    [StringEquality(StringComparison.OrdinalIgnoreCase)]
    public string[] Tags { get; set; } = Array.Empty<string>();

}

Generator.Equals will detect the attribute and generate code that assumes that the property is a string, for both the code generated for equality and the code generated for hash code:

    /// <inheritdoc/>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Generator.Equals", "1.0.0.0")]
    protected bool Equals(global::Resource? other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        
        return other.GetType() == this.GetType()
            && global::System.StringComparer.OrdinalIgnoreCase.Equals(this.Tags!, other.Tags!)
            ;
    }
    
    /// <inheritdoc/>
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Generator.Equals", "1.0.0.0")]
    public override int GetHashCode()
    {
        var hashCode = new global::System.HashCode();
        
        hashCode.Add(this.GetType());
        hashCode.Add(
            this.Tags!,
            global::System.StringComparer.OrdinalIgnoreCase
        );
        
        return hashCode.ToHashCode();
    }

Do not require runtime reference to Generator.Equals.Runtime.dll

Even for a simple usage like this

    [Equatable]
    internal partial class Class1
    {
        public string Text { get; set; } = string.Empty;
    }

a runtime reference to Generator.Equals.Runtime.dll is required, just because of the usage of the attribute.

Proposed solution:

Decorate the attributes with e.g. [Conditional("GENERATOR_EQUALS")] (like e.g. JetBrains does for their annotation attributes: https://www.jetbrains.com/help/resharper/Code_Analysis__Annotations_in_Source_Code.html)

This way a runtime reference should only be needed when using one of the special comparators.

Cleanup IEquatable<T> implementation for classes

This would require a breaking change, so I am adding this to the 3.0 milestone.

The idea would be to make the method IEquatable<T>.Equals(T) virtual and have a similar implementation to records, where the immediate base is overridden and sealed, calling Equals(object?). Below is the decompiled source of a derived record -- that's what we want for classes.

    [NullableContext(2)]
    [CompilerGenerated]
    public override bool Equals(object obj)
    {
      return this.Equals(obj as Derived);
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public override sealed bool Equals(Base other)
    {
      return this.Equals((object) other);
    }

    [NullableContext(2)]
    [CompilerGenerated]
    public virtual bool Equals(Derived other)
    {
      if ((object) this == (object) other)
        return true;
      return base.Equals((Base) other) && EqualityComparer<string>.Default.Equals(this.\u003CName\u003Ek__BackingField, other.\u003CName\u003Ek__BackingField);
    }

NullReferenceExceptions starting with v2.7.2

Iโ€™ve been using your lib for a while in 0install-dotnet. Great project, thanks!

Starting with v2.7.2 up to and including v2.7.5 my builds targeting .NET Framework 4.7.2 (with <Nullable>annotations</Nullable>) are throwing exceptions for null values:

System.NullReferenceException with message "Object reference not set to an instance of an object."
   at Generator.Equals.DefaultEqualityComparer`1.ObjectEqualityComparer.GetHashCode(T obj)
   at System.HashCode.Add[T](T value, IEqualityComparer`1 comparer)
   at ZeroInstall.Model.Command.GetHashCode() in /_/src/Model/Generator.Equals/Generator.Equals.EqualsGenerator/ZeroInstall.Model.Command.Generator.Equals.g.cs:line 58

The same code targeting .NET 6.0 (with <Nullable>enable</Nullable>) still works fine.

Was this an unintentional change or do I need to change something in my usage of the lib?

Explicit mode

Hi. Cool generator.
I am migrating to your generator from this - https://github.com/tom-englert/Equatable.Fody. It uses Fody and explicitly defined properties to implement IEquatable. I would like to see the same mode in this generator, in which you need to explicitly specify the necessary properties.
It would also be great to think about supporting IIncrementalGenerator

Inherited classes Equals ignored when using OrderedEquality on an array of the base class

This is actually a follow up to #38 but in another situation.

Problem

The ObjectEqualityComparer<T> class is used, which actually calls x.Equals(y) where y is of the expected time, resulting in only the base class Equals implementation being called.

I'm wondering if marking the typed Equals method as public might make things harder? If it was protected instead, it would be much harder to fall in that trap. The method checks for

Reproduction

public class GeneratorBaseClassBugTests
{
  [Equatable]
  public partial class MyContainer
  {
    [OrderedEquality]
    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 TestEqual()
  {
    var v1 = new MyContainer { Content = new MyBase[] { new MyImpl { Value = 1 } } };
    var v2 = new MyContainer { Content = new MyBase[] { new MyImpl { Value = 1 } } };

    Assert.That(v1.Equals(v2), Is.True);
  }

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

    Assert.That(v1.Equals(v2), Is.False);
  }
}

Solution

The code ends up calling SequenceEqual, which again calls GenericEqualityComparer<BaseType>, ignoring inherited Equals implementations. This is actually the behavior of SequenceEqual, which makes me think that even outside of your comparison code, the generated equality checks may fail in other circumstances. Let me know if you believe going the protected route for the typed Equals implementation sounds like the way to go.

Version

.NET 6.0, Generator.Equals 2.7.2

Sorry to be a pain, hopefully that'll avoid some nasty surprises for other people :D Thanks again for your consideration and the great library!

No Licence?

Hey there,

I was just wondering if you intended this library to not be licenced since that implies that all rights are reserved and it can't redistribute your code without explict permissions.

Indexers on classes lead to syntactically invalid generated code

First of: Very useful generator. Thanks for building this! :)

I ran into a small issue with classes that have indexers:

[Equatable]
partial class Sample
{
    public string Property { get; set; }
    
    public string this[int index] => index.ToString();
}

The generated code looks like this: (shortened for readability)

partial class Sample : IEquatable<Sample> {

// ...

public bool Equals(Sample? other) {
return !ReferenceEquals(other, null) && this.GetType() == other.GetType()
&& EqualityComparer<String>.Default.Equals(Property!, other.Property!)
&& EqualityComparer<String>.Default.Equals(this!, other.this!) // <- invalid
;
}

// ...

public override int GetHashCode() {
                var hashCode = new global::System.HashCode();
            
hashCode.Add(this.GetType());
hashCode.Add(this.Property!, EqualityComparer<String>.Default);
hashCode.Add(this.this!, EqualityComparer<String>.Default); // <- invalid
return hashCode.ToHashCode();
}

which is then rejected by the compiler with CS1001 and CS1003.

It seems that Generator.Equals thinks that this is the name of a property here, rather than interpreting it as the keyword indicating an indexer.

As a workaround, I can mark the indexer with [IgnoreEquality]. However, I think it would be better if Generator.Equals simply ignored indexers by default.

Inherited classes Equals ignored when called from the base class

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!

Compiler-error with static properties

Classes with static properties, like:

[Equatable]
partial class Sample
{
    public string Property { get; set; }
    
    public static string StaticProperty { get; set; }
}

lead to a compiler error when used with Generator.Equals:

Member 'Sample.StaticProperty' cannot be accessed with an instance reference; qualify it with a type name instead

As a workaround, I can mark static properties with [IgnoreEquality]. However, I think it would be better if Generator.Equals simply ignored them by default.

Support for Record Struct

[Equatable]
public partial record struct MyStruct(int Data);

Generates

partial record MyStruct
{
    /// <inheritdoc/>
    [GeneratedCode("Generator.Equals", "1.0.0.0")]
    public bool Equals(MyStruct? other)
    {
        return
            base.Equals(other)
         && EqualityComparer<Int32>.Default.Equals(Data!, other.Data!)
            ;
    }
        
    /// <inheritdoc/>
    [GeneratedCode("Generator.Equals", "1.0.0.0")]
    public override int GetHashCode()
    {
        var hashCode = new HashCode();
            
        hashCode.Add(base.GetHashCode());
        hashCode.Add(
            Data!,
            EqualityComparer<Int32>.Default);
            
        return hashCode.ToHashCode();
    }
}

This is correct except it should say partial record struct MyStruct and the Equals method should be

/// <inheritdoc />
    [GeneratedCode("Generator.Equals", "1.0.0.0")]
    public bool Equals(MyStruct? other)
    {
        return
            other.HasValue
         && EqualityComparer<Int32>.Default.Equals(Data, other.Value.Data)
            ;
    }

also the GetHashCode method does not need to do hashCode.Add(base.GetHashCode());

I am willing to have a go at this myself.

Missing XML comments on generated code

Hey,

If you're using XML doc comments and have warnings enabled when they are missing the source generator gets picked up (even though you've correctly added GeneratedCodeAttribute which you'd expect to be skipped but isn't).

Would you be interested in either:

  • Generate inheritdoc comments for overridden methods and default comments for operators
  • Add #pragma warning disable 1591 to the file to avoid the warnings entirely?

I'd be happy to contribute a PR for either if it's welcomed ๐Ÿ‘

Support for struct

Hello,

Would it be possible to also generate equality members for struct ?

`IEnumerable<T>` property marked by `[UnorderedEquality]` causes exception during generation

I've recently found this package and wanted to replace own reflection-based equality. I marked up classes but got no result. Building project I've got warning:

CSC : warning CS8785: Generator 'EqualsGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'InvalidOperationException' with message 'Nullable object must have a value'

So, error message wasn't explanatory enough. After quite a bit of debugging, I've found out that my class contained IEnumerable<T> property with UnorderedEquality attribute. It caused NullReferenceException during generation.

I believe, it is a bug, isn't it? If so, I can provide some fix.

P.S. Thanks for your work

Feature Request: Would be great to be able to enumerate the properties that are different!

Would be great to have a wrapper class such that one can have a Comparer collect the difference between say two record's or other supported type. This would also walk the nested collection and say use a ., [x] or => notation for the hierarchy, array/set elements or maps.

A Use Case: CDC you have the before and after image from your database of choice and need detect what changes happen between them, so some conditional biz logic can be applied based upon that.

That this generator avoids reflection means it will also work with AoT compilation.

Compatibility with the mvvm community toolkit

This code will generate equality comparisons:
[DefaultEquality] public bool MyProperty{ get; set; }
or
[DefaultEquality] private bool myProperty;

But the following will not generate any equality code:

[ObservableProperty]
[property: DefaultEquality]
private bool myProperty;

and this will generate the code but it also causes warning message MVVMTK0034:

[ObservableProperty]
[DefaultEquality] 
private bool isFromExistingStock;

Reasoning: some libraries (like the MVVM community toolkit) require all attributes to be applied to the backing field, but doing so will introduce warnings whenever you try to access the backing field instead of the property.

I reviewed the source generated by MVVM toolkit, and it appears that (when using [property:DefaultEquality] the auto-generated property will include the DefaultEqualityAttribute, but source code normally generated by Generator.Equals is not included. Maybe attributes generated a source generator cannot be used to generate more code? It would make sense if that was the issue.

/// <inheritdoc cref="myProperty"/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::Generator.Equals.DefaultEqualityAttribute()]
public bool MyProperty
{
    get => myProperty;
    set
    {
        if (!global::System.Collections.Generic.EqualityComparer<bool>.Default.Equals(myProperty, value))
        {
            OnMyPropertyChanging(value);
            OnMyPropertyChanging(default, value);
            OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.MyProperty);
            myProperty = value;
            OnMyPropertyChanged(value);
            OnMyPropertyChanged(default, value);
            OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.MyProperty);
        }
    }
}

Thoughts?

Replace MSTest and NUnit with Xunit.

Xunit works really well with nullable by using ctor and IDisposable for its setup/teardown semantics. Need to refactor tests to use that and also adopt snapshot tests throughout.

Suppress obsolete warnings in generated code

Issue
When I mark a property as obsolete but don't use [IgnoreEquality], then I get build warnings about usages of the obsolete property.

I would expect those to be suppressed because they will go away when the property is removed anyway. In the meantime, I can't use [Obsolete] on properties in projects where I treat warnings as errors.

Solution 1
Use a pragma to suppress build warnings when comparing obsolete things in the generated source.

Solution 2
Add a toggle on [Equatable] to decide whether to use a pragma to suppress obsolete warnings. Something like:

[Equatable(SuppressObsoleteWarnings = true)]
public partial record Something
{
  public string Thing1 { get; set; }
  [Obsolete]
  public string Thing2 { get; set; }
}

If you have a preferred way of doing it, I'd be more than happy to write up a PR.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.