Home > Enterprise >  Setter not running when assigning to the same reference
Setter not running when assigning to the same reference

Time:01-10

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

CodePudding user response:

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 very 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.

CodePudding user response:

Not clear what exactly you're doing here, but I think your comments are telling:

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

but then you have:

settings.ReferenceVaraible = a; // Setter does run

Obviously the lines of code are exactly the same here, so my guess would be that you're expecting to link a to your Database, such that a would be a kind of a handle/portal to your database and you can modify a and get those changes telegraphed into your database.

This isn't going to work. The setter only runs when you set the value of settings, not when you set the value of a. It might be that you are updating a after the fact, but updating a doesn't force the call to SetSettingValueToDatabase.

How you handle this depends on how you want to restructure your code. I would wait to write a until you're done doing whatever operations you need to do with a, but you could also add a kind of a listener mechanic to a.

I have no idea what's in a, but you could do something like the following. This is a bit more code than I meant to write lol, but I'll put some closing comments after the code block.

public interface IChanged
{
    void Subscribe(System.EventHandler subscriber);
    void Unsubscribe(System.EventHandler subscriber);
}
public class MyClass : IChanged
{
    private System.EventHandler subscribers;
    private int myInt;
    public int MyInt
    {
        get => myInt;
        set
        {
            myInt = value;
            subscribers?.Invoke(this, null);
        }
    }

    private string myString;
    public string MyString
    {
        get => myString;
        set
        {
            myString = value;
            subscribers?.Invoke(this, null);
        }
    }

    public void Subscribe(System.EventHandler subscriber)
    {
        subscribers  = subscriber;
    }

    public void Unsubscribe(System.EventHandler subscriber)
    {
        subscribers -= subscriber;
    }
}

public class Settings
{
    private MyClass myClass;
    public MyClass ReferenceVariable
    {
        get => GetSettingValueFromDatabase();
        set
        {
            if (myClass != null)
            {
                if (myClass != value)
                {
                    myClass.Unsubscribe(OnReferenceVariableChanged);
                }
            }
            myClass = value;
            SetSettingValueToDatabase(value);
            value.Subscribe(OnReferenceVariableChanged);
        }
    }

    private void OnReferenceVariableChanged(object sender, System.EventArgs e)
    {
        SetSettingValueToDatabase(ReferenceVariable);
    }

    private MyClass GetSettingValueFromDatabase()
    {
        // You would get this from a Database
        return new MyClass();
    }

    private void SetSettingValueToDatabase(MyClass myClass)
    {
        // do stuff
    }
}

Here there's an IChanged interface that sets up a mechanism to subscribe to changes. You don't need any information here, you just need a heads up that a changed. You can slap the IChanged interface on whatever you want and use it for a variety of classes.

The trick then is to add the subscribers?.Invoke(this, null); line to each property in MyClass. If you don't use properties then you don't have a way to add this line and thus you won't get notifications if/when the fields are changed.

Then, in Settings, you keep track of a private MyClass myClass to know when you're getting a new instance of MyClass, so you can unsubscribe from the old one. Fire off your SetSettings methods, and then Settings adds itself as a subscriber to the MyClass's property changes.

Now, anytime a property changes, the MyClass class alerts all its subscribers, and the Settings subscriber in particular can use that as a trigger to re/write the settings to the database.

There's nothing special there in the Settings getter, so you might want to consider unsubscribing myClass there, setting it to whatever you pulled from the database, and hooking up the subscriber to that new instance, but I don't know anything about your code so I don't want to push that as "the" answer.

  • Related