Search code examples
c#getter-settersetter

Setter not running when assigning to the same reference


My setter code is not running here, I think by design because I am setting the same reference.

Is there syntax I can use to ensure the setter runs?

var settings = new Settings();

var a = settings.ReferenceVariable;

a.Value1++;

settings.ReferenceVariable = a; // Setter is not running here, so changes to 'a' are not persisted in database

// One workaround is to set to a different value, then set back to my value.  This isn't a good solution for me
settings.ReferenceVariable = null; // Setter does run
settings.ReferenceVaraible = a; // Setter does run

public class Settings
{
    public MyClass ReferenceVariable
    {
        get => GetSettingValueFromDatabase();
        set => SetSettingValueToDatabase(value);
    }
}

Edit: Thanks everyone for your help, I found the issue, I'm using Fody/PropertyChanged package, which does modify property setters, and checks for changes. Their changes aren't visible to me while debugging, so it was confusing to track down


Solution

  • When you say "the setter is not running" - are you saying the set => SetSettingValueToDatabase(value) line is never reached, or are you infering this only by the fact that the expected side effects from SetSettingValueToDatabase are not observed?

    Because my gut feeling would be that the setter and the function SetSettingValueToDatabase itself are actually called, but MyClass has an internal optimization to skip the actual database operation if the value "hasn't changed", implemented like so:

    private MyClass _cachedValue;
    private bool _isLoaded = false;
    
    private MyClass GetSettingValueFromDatabase() {
      if (!_isLoaded) {
        _cachedValue = DoActuallyLoadFromDatabase()
        _isLoaded = true;
      }
      return _cachedValue;
    }
    
    private void SetSettingValueToDatabase(MyClass newValue) {
      if (!_isLoaded || _cachedValue != newValue) {
        DoActuallySaveToDatabase(newValue);
        _cachedValue = newValue;
        _isLoaded = true;
      }
    }
    

    The != would then most likely fall back to object.ReferenceEquals, which would yield true since the reference of newValue and _cachedValue still match - hence no DB write or cache update, hence it looks as if the setter wasn't called, when actually just its side effect weren't triggered.

    You can verify this by changing the property getter/setter to

    get {
      var res = GetSettingValueFromDatabase();
      Debug.WriteLine($"get will return {res}");
      return res;
    }
    set {
      Debug.WriteLine($"set called with {value}");
      SetSettingValueToDatabase(value);
    }
    

    My suspicion is that the debug output will be

    get will return MyNamespace.MyClass
    set called with MyNamespace.MyClass
    set called with null
    set called with MyNamespace.MyClass

    rather than

    get will return MyNamespace.MyClass
    set called with null
    set called with MyNamespace.MyClass

    indicating the setter was indeed called as expected.

    On a side note: a setter that triggers a database write operation is not a good design. Setters should be usually designed to be light-weight operations, not triggering a potentially locking hefty database operation. Rather use a method, that should potentially even be asynchronous.