Skip to content

PropertyChanging.Fody weaver interfering with PropertyChanged.Fody equality check option #1100

@xeniorn

Description

@xeniorn

Describe the issue

PropertyChanging.Fody interferes with PropertyChanged.Fody option "CheckForEquality".

Assuming that the two modules are intended to be compatible:
#169

Basic behavior seems to be correct. But PropertyChanged.Fody's CheckForEquality = false will be ignored for classes also implementing INotifyPropertyChanging, I assume due to weavers conflicting in some way.

For a class like:

[AddINotifyPropertyChangedInterface]
public class TestClassImplicitNoCheckEquality
{
    [DoNotCheckEquality]
    public object Value { get; set; }
}

The relevant part of IL code with PropertyChanged.Fody alone will look like:

  .method public hidebysig specialname instance void
    set_Value(
      object 'value'
    ) cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 2

    // [12 36 - 12 40]
    IL_0000: ldarg.0      // this
    IL_0001: ldarg.1      // 'value'
    IL_0002: stfld        object FodyTest.TestClassImplicit::'<Value>k__BackingField'
    IL_0007: ldarg.0      // this
    IL_0008: ldsfld       class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs 'FodyCDoubleNet.<>PropertyChangedEventArgs'::Value
    IL_000d: callvirt     instance void FodyTest.TestClassImplicit::'<>OnPropertyChanged'(class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs)
    IL_0012: nop
    IL_0013: ret

  } // end of method TestClassImplicit::set_Value

With added INotifyPropertyChanging and PropertyChanging.Fody:

.method public hidebysig specialname instance void
    set_Value(
      object 'value'
    ) cil managed
  {
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 2

    IL_0000: ldarg.0      // this
    IL_0001: ldfld        object FodyTest.TestClassImplicit::'<Value>k__BackingField'
    IL_0006: ldarg.1      // 'value'
    IL_0007: call         bool [System.Runtime]System.Object::Equals(object, object)
    IL_000c: brfalse.s    IL_000f
    IL_000e: ret
    IL_000f: nop

    // [18 36 - 18 40]
    IL_0010: ldarg.0      // this
    IL_0011: ldarg.1      // 'value'
    IL_0012: stfld        object FodyTest.TestClassImplicit::'<Value>k__BackingField'
    IL_0017: ldarg.0      // this
    IL_0018: ldsfld       class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs 'FodyCDoubleNet.<>PropertyChangedEventArgs'::Value
    IL_001d: callvirt     instance void FodyTest.TestClassImplicit::'<>OnPropertyChanged'(class [System.ObjectModel]System.ComponentModel.PropertyChangedEventArgs)
    IL_0022: nop
    IL_0023: ret

  } // end of method TestClassImplicit::set_Value

Diff of IL codes:

Image

The first class will fire an event on assignment of same value; the second class will ignore it despite the DoNotCheckEquality attribute.

The same happens if the option is configured via weavers.xml

The same happens if the interface is explicitly implemented without the AddINotifyPropertyChangedInterface attribute.

Minimal Repro

Used the following test on a set of test classes:

public class UnitTestReflection
{
    [Theory]
    [InlineData(typeof(TestClassExplicit), 1, 1, 1)]
    [InlineData(typeof(TestClassExplicit), 1, 2, 2)]
    [InlineData(typeof(TestClassImplicit), 1, 1, 1)]
    [InlineData(typeof(TestClassImplicit), 1, 2, 2)]
    [InlineData(typeof(TestClassImplicitNoCheckEquality), 1, 1, 2)]
    [InlineData(typeof(TestClassImplicitNoCheckEquality), 1, 2, 2)]

    public void CorrectFireAll(Type type, object firstValue, object secondValue, int expectedCount)
    {
        var a = Activator.CreateInstance(type);

        var props = type.GetProperties();

        var valueProp = props.Single(x => x.PropertyType == typeof(object) && x.Name == "Value");

        var ap = (INotifyPropertyChanged)a;
        var count = 0;

        ap.PropertyChanged += (p, e) => count++;

        valueProp.SetValue(a, firstValue);
        valueProp.SetValue(a, secondValue);

        Assert.Equal(expectedCount, count);
    }
}

The class & test projects (VS2022 Community 17.12, NET9.0, Fody 6.9.1, PropertyChanged.Fody 4.1.0, PropertyChanging.Fody 1.30.3) :

FodyTest.zip

I can add a PR with the tests if desired, just wasn't sure which repo it should go to - here makes more sense I think.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions