Избегайте рекурсивности с помощью метода generi c для установки свойств класса c# - PullRequest
3 голосов
/ 05 августа 2020

Я объясню свою проблему в гораздо большем классе на меньшем, легко следовать точному примеру. У меня довольно большой класс с множеством свойств разных типов, получение и установка соответствующей переменной класса.

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;}
}

Я поместил 4 свойства в пример, но в реальном примере их много . Мне нужно применить logi c в наборе всех свойств, в зависимости от логического свойства property4, поэтому вместо того, чтобы писать один и тот же код во всех установщиках моих свойств, я попытался сделать вызываемый метод generi c во всех из них.

Итак, я создал перечисление:

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

Чтобы я мог установить свои свойства с помощью метода, который включает отражение, принимая тип свойства в качестве аргумента:

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

Итак, мои сеттеры становятся:

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) };}
}

Моя проблема возникает, когда в моем setLogi c () вызывается установщик свойств, рекурсивно вызывающий переполнение стека. Итак, я решил topi c с логическим значением, управляемым из setLogi c (), которое контролирует, откуда вызывается сеттер. Итак, теперь мои свойства выглядят так:

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) };}
}

Код работает нормально, но элемент управления setter bool, чтобы избежать рекурсивности, отбрасывает все возможные чистки, полученные методом SetLogi c () generi c. . С другой стороны, я не могу установить переменные класса в методе setLogi c, потому что я обращаюсь к свойствам с отражением, поэтому, чтобы установить новое значение в logi c, я не могу избежать рекурсивного набора без логического (property.SetValue () из класса отражения устанавливает новое значение, снова вызывая набор, так что бесконечное l oop).

Если я этого не сделаю, мне придется вместо этого вставить метод setLogi c () of been generi c, копия вставлена ​​для каждого свойства в наборе, что тоже не очень чистый код.

Нет ли для этого чистого решения, где сеттер можно передать в качестве аргумента , или метод generi c, который избегает бесконечного рекурсивного набора?

Я думал о чем-то вроде

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

или о другом типе setLogi c с классом generi c свойства, которые избегают бесконечных l oop, о которых нельзя думать.

Надеюсь, я понял.

Ответы [ 2 ]

8 голосов
/ 05 августа 2020

Можете ли вы использовать параметр ref для непосредственной установки поля?:

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

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

Я обычно использую этот шаблон при реализации 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));
    }
}
1 голос
/ 05 августа 2020

Вы можете использовать атрибут [CallerMemberName] и Dictionary<string, object> для хранения свойств. Это не повлечет за собой таких же штрафов за производительность при отражении.

Например ...

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

... и базовый класс ...

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);
}
...