Skip to content

Avoid calling setter for bound properties for same value #3129

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

Merged
merged 5 commits into from
Dec 13, 2022

Conversation

papafe
Copy link
Contributor

@papafe papafe commented Dec 12, 2022

Avoids calling the setter for bound properties in case the new value is the same as the old one. This avoids a problem in MAUI in which the UI is calling the setter unnecessarily, and causing an issue in case objects outside of the user's write permissions are modified

Fixes #3128

TODO

  • Changelog entry
  • Tests

Sorry, something went wrong.

@papafe
Copy link
Contributor Author

papafe commented Dec 12, 2022

Two things:

  • I'm not sure if it would make sense to add more tests and verify that this works with all the possible combination of how to call the setter (invoke the setter / call set value/ ....) and write transaction already happening or not. There is only one tests now that seems to be the more general path
  • There are wildly different ways in which we can get the current value of the property, so I'd be interested to hear if what I've done makes sense.

Copy link
Member

@fealebenpae fealebenpae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, I wonder if there's a Sync-related reason why we might not want to avoid the redundant set in all cases. @nirinchev, do you know if it matters for the sync history that a set is recorded here anyway?

{
return managingRealm.Write(() =>
if (_getterMi.Invoke(realmObject, null).Equals(parameters[0]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not crazy about the reflection here. I would've preferred punching straight to the accessor's GetValue. But this is by definition not a hot path, so I suppose it doesn't really matter. But a comment explaining why we do this is necessary, ideally linking to the issue for the full context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll definitely add a comment.
I was thinking about using GetValue here, but it starts to get a little more complex because we also don't know exactly what is the mapTo name of the property

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the property value is null this will throw, wouldn't it?

Copy link
Member

@nirinchev nirinchev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems reasonable. I have a few minor comments/suggestions.

{
return managingRealm.Write(() =>
if (_getterMi.Invoke(realmObject, null).Equals(parameters[0]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the property value is null this will throw, wouldn't it?

return null;
}

var managingRealm = (obj as IRealmObjectBase)?.Realm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have realmObject - there's no need to do an extra cast here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defnitely


public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
{
var managingRealm = (obj as IRealmObjectBase)?.Realm;
var realmObject = obj as IRealmObjectBase;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add an early return here in case obj is not IRealmObjectBase and remove all the null propagation operators?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only have one one null propagation line (realmObject?.IsManaged == true), so I suppose we can keep just that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can still make the checks cleaner by merging the cast with the if-clause (I'm kind of surprised VS isn't hinting this):

if (obj is IRealmObjectBase realmObject && realmObject.IsManaged)
{
    // ...
}

@nirinchev
Copy link
Member

Now that I think about it, I wonder if there's a Sync-related reason why we might not want to avoid the redundant set in all cases. @nirinchev, do you know if it matters for the sync history that a set is recorded here anyway?

Yes, it matters for conflict resolution, but I believe this solution is still the right one considering MAUI's bug. Re-setting all properties every time the UI renders is most likely not what the user intends to do anyway, so this one is the lesser of two evils.

Verified

This commit was signed with the committer’s verified signature.
tecosaur Timothy
var managingRealm = realmObject.Realm;

// If managingRealm is not null and not currently in transaction, wrap setting the property in a realm.Write(...)
if (realmObject.Realm?.IsInTransaction == false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use managingRealm here.


public override object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
{
var managingRealm = (obj as IRealmObjectBase)?.Realm;
var realmObject = obj as IRealmObjectBase;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can still make the checks cleaner by merging the cast with the if-clause (I'm kind of surprised VS isn't hinting this):

if (obj is IRealmObjectBase realmObject && realmObject.IsManaged)
{
    // ...
}

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
@papafe papafe changed the title Added equality check on binding setter Avoid calling setter for bound properties for same value Dec 13, 2022
@papafe papafe merged commit 69614d5 into main Dec 13, 2022
@papafe papafe deleted the fp/fix-maui-setter branch December 13, 2022 14:17
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug] MAUI calls setter unnecessarily with CollectionView
4 participants