1

I'm looking to create a slick helper function that compares two values, returns true if they are different, and also updates the first value to equal the second value. This is what I came up with:

bool UpdateHelper<T>(ref T originalProperty, T newProperty) =>
    !Equals(originalProperty, newProperty) && (originalProperty = newProperty) is T _;

bool changes = false;
changes |= UpdateHelper(ref name, frmName.Text);
changes |= UpdateHelper(ref description, frmDescription.Text);
...

The goal is that when doing this for dozens of properties, to avoid copy-pasting the same 5 lines of code for each property:

if (myModel.SomeProperty != someUserInput.Text)
{
   myModel.SomeProperty = someUserInput.Text
   changes = true
}

(Especially after having seen someone copy paste and update the if statement portion but forgetting to update the assignment!)

My above helper works for for the most part, but unfortunately this falls apart when I try to use it on properties:

changes |= UpdateHelper(ref myModel.Name, frmName.Text);
changes |= UpdateHelper(ref myModel.Description, frmDescription.Text);

Property access returns temporary value. 'ref' argument must be an assignable variable, field or an array element

Can anyone think of a solution that would work for both?


I tried playing around with expressions, e.g.:

bool UpdateHelper<T>(Expression<Func<T>> originalProperty, T newProperty) =>
    !Equals(originalProperty.Compile().Invoke(), newProperty) && (originalProperty.Body.??? = newProperty) is T _;

changes |= UpdateHelper(() => myModel.Name, frmName.Text);
changes |= UpdateHelper(() => myModel.Description, frmDescription.Text);
changes |= UpdateHelper(() => myModel.ChildObject.Name, frmChildName.Text);

but I feel like it's going to turn into a nightmare of parsing expression trees, especially if it needs to handle a special case of accessing properties nested a few layers down (like the last example above).

Anyone ideas for how to do this elegantly?

2 Answers 2

2

I think I've got it solved. This sort of expression parsing is relatively common when handling e.g. property changed event notifications.

bool UpdateHelper<T>(Expression<Func<T>> originalProperty, T newProperty)
{
    if (Equals(originalProperty.Compile().DynamicInvoke(), newProperty))
        return false;
    MemberExpression mex = (originalProperty.Body is UnaryExpression ux ? ux.Operand : originalProperty.Body) as MemberExpression;
    if (mex?.Member is PropertyInfo pi)
        pi.SetValue(Expression.Lambda(mex.Expression).Compile().DynamicInvoke(), newProperty);
    else
        throw new ArgumentException("The originalProperty argument must be a property expression such as `() => someObject.SomeProperty`");
    return true;
}
Sign up to request clarification or add additional context in comments.

Comments

1

Expressions are slow, as you see in your code you need to compile them for every assignment. One solution if you want to support both cases is to provide separate functions for getting and setting:

bool UpdateHelper<T>(Func<T> originalGet, Action<T> originalSet, T newProperty) => ...

And you'd call it like (for both fields and properties):

changes |= UpdateHelper(() => name, val => name = val, frmName.Text);

Note that the most elegant solution involves never writing this at all, but having it source-generated, either with the template compiler (.tt) or with plain source generators (ISourceGenerator / IIncrementalGenerator).

6 Comments

I appreciate the suggestion, and I'm sure it's the right solution for many in this position. In my case, performance is not critical and my greater concern is a developer making a copy paste error (e.g. having a get function that doesn't match a set action). So while I upvote, I'll also hold out for a solution that addresses this :)
As I said, source generators!
Actually this is an XY problem, you're trying to home-brew MVC/MVVM. There are many already done frameworks that handle this for you, like ReactUI for MVVM.
Not sure source generators apply in my situation. This is a bespoke excel add-in application and my "new property" comes from reading a cell value from a given named range, and "set property" is an object property due to be serialized and sent off to the API. Not a typical View+Model setup! I may have done myself a little disservice by simplifying the code to a more universally recognizable situation.
That's not apparent from any of your examples, or from the question itself. This seems like a static list of properties all known at compile time. Are you sure you understand what you're trying to achieve here?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.