简体   繁体   中英

Avoid recursivity with generic method to set class properties c#

I follow to explain my problem in a much bigger class with an smaller easy to follow exact example. I got quite a big class, with a lot of properties of different types, getting and setting their respective class variable.

public class Foo() {
    int property1 { get => _property1 ; set => _property1 = value;}
    string property2 { get => _property2 ; set => _property2 = value;}
    Vector3 property3 { get => _property3 ; set => _property3 = value;}
    bool property4 { get => _property3 ; set => _property4 = value;}
}

I put 4 properties in the example, but in the real example there are a lot. I need to apply a logic in the set of all the properties, depending on property4 boolean, so instead of writting the same code in all the setters of my properties, I tried to make a generic method to be called in all of them.

So, I made a an enum:

public enum properties {
    property1,
    property2,
    property3,
    property4
}

So that I can set my properties with a method that involves reflection, taking the property type as an argument:

public void setLogic<T>(properties property, T value) {
    //irrelevant code
}

So my setters turn to be:

public class Foo() {
    int property1 { get => _property1 ; set { setLogic(properties.property1 , value) };}
    string property2 { get => _property2 ; set { setLogic(properties.property2 , value) };}
    Vector3 property3 { get => _property3 ; set { setLogic(properties.property3 , value) };}
    bool property4 { get => _property4 ; set{ _property4 = value) };}
}

My problem comes when in my setLogic() the property setter is called recursively producing a stack overflow. So I solved the topic with a boolean controlled from setLogic() that controlls where the setter is being called from. So now my properties turn to be:

public class Foo() {
    int property1 { 
        get => _property1; 
        set { 
            if (!_calledFromSetLogic)
                setLogic(properties.property1 , value);
            else {
                _property1 = value;
                _calledFromSetLogic = false;
            }
        }
    }
    string property2 { 
        get => _property2; 
        set { 
            if (!_calledFromSetLogic)
                setLogic(properties.property2 , value);
            else {
                _property2 = value;
                _calledFromSetLogic = false;
            }
        }
    }
    Vector3 property3 { 
        get => _property3; 
        set { 
            if (!_calledFromSetLogic)
                setLogic(properties.property3 , value);
            else {
                _property3 = value;
                _calledFromSetLogic = false;
            }
        }
    }
    bool property4 { get => property4; set{ _property4 = value) };}
}

The code works fine, but the setter bool control to avoid recursivity throws away all the possible cleannes brought by the SetLogic() generic method. On the other hand I cannot set the class variables in the setLogic method because I access the properties with reflection, so to set the new value in the logic I cannot avoid the recursive set without the boolean (property.SetValue() from the reflection class sets the new value calling the set once again, so infinite loop).

If I dont do this, I have to paste the setLogic() method, instead of been generic, copy pasted for each of the properties in the set, which is not very clean code neither.

Is there not a clean solution for this, where the setter can be passed as an argument, or a generic method that avoids infinite recursive set?

I was thinking of something like

private setLogic<T>(Action<> setterMethod, T value) {
    //so that the property involved might be already in the setter?
}

or other kind of setLogic with the generic class properties that avoids the infinite loop, of whom a cannot think of.

Hope I made myself understood.

Are you able to just use a ref parameter to set the field directly?:

int property1
{
    get => _property1;
    set => setLogic(ref _property1, value);
}

private void setLogic<T>(ref T field, T value)
{
    field = value;
}

I commonly use this pattern when implementing INotifyPropertyChanged :

private int _someProperty;
public int SomeProperty
{
    get => _someProperty;
    set => SetProperty(ref _someProperty, value);
}

private void SetProperty<T>(ref T field, T value, [CallerMemberName] propertyName = "")
{
    if (!field.Equals(value))
    {
        field = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

You could make use of the [CallerMemberName] attribute and a Dictionary<string, object> to store the properties in. This won't incur the same performance penalties reflection would.

For example...

class Foo : PropertyChangeNotifier
{
    public int property1 { get { return Get<int>(); } set { Set(value); } }
    public string property2 { get { return Get<string>(); } set { Set(value); } }
    public Vector3 property3 { get { return Get<Vector3>(); } set { Set(value); } }
    public bool property4 { get { return Get<bool>(); } set { Set(value); } }

    protected override void OnSet<T>(string property, T value)
    {
        // do something meaningful.
    }
}

...and the base class...

abstract class PropertyChangeNotifier
{
    private readonly Dictionary<string, object> properties = new Dictionary<string, object>();

    protected T Get<T>([CallerMemberName] string property = null)
    {
        return (T)properties[property];
    }

    protected void Set<T>(T value, [CallerMemberName] string property = null)
    {
        OnSet(property, value);
        properties[property] = value;
    }

    protected abstract void OnSet<T>(string property, T value);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM