Как обрабатывать инициализацию с объектом, у которого есть отслеживание изменений свойств

Благодаря замечательному ответу Марка Гравелла я смог реализовать отслеживание изменений свойств в своем классе. Однако, когда объект инициализируется впервые и свойства устанавливаются в первый раз, все свойства помечаются как грязные. Как лучше всего обработать первоначальную настройку свойств, чтобы они не помечались как грязные при первой загрузке объекта?

Таким образом, когда свойства ExtendedFieldDto устанавливаются впервые, dirtyProperties всегда имеет счетчик, равный свойствам наследующего класса, в данном случае ExtendedFieldDto.

public abstract class NotifyPropertyChanged {
    public IDictionary<string, object> dirtyProperties { get; private set; }

    protected NotifyPropertyChanged() {
        dirtyProperties = new Dictionary<string, object>();
    }

    protected void setProperty<T>(ref T property, T value, string propertyName) {
        if (!EqualityComparer<T>.Default.Equals(property, value)) {
            property = value;
            if (dirtyProperties.Keys.Contains(propertyName))
                dirtyProperties[propertyName] = property;
            else
                dirtyProperties.Add(propertyName, property);
        }
    }
}

public class ExtendedFieldDto : NotifyPropertyChanged {
    private string _id;
    public string id {
        get { return _id; }
        set { setProperty(ref _id, value, "id"); }
    }

    private int _idLocation;
    public int idLocation {
        get { return _idLocation; }
        set { setProperty(ref _idLocation, value, "idLocation"); }
    }

    private string _columnName;
    public string columnName {
        get { return _columnName; }
        set { setProperty(ref _columnName, value, "columnName"); }
    }

    private string _description;
    public string description {
        get { return _description; }
        set { setProperty(ref _description, value, "description"); }
    }

    private string _help;
    public string help {
        get { return _help; }
        set { setProperty(ref _help, value, "help"); }
    }
}

В настоящее время dto используется как свойство на странице aspx, где я храню объект в ViewState (требование из устаревшего кода). Поэтому я не уверен, как бы я использовал конструктор для ExtendedFieldDto для установки частных свойств с этим типом реализации.

private ExtendedFieldDto extendedField {
    get {
        if (ViewState[EXTENDED_FIELD_VIEWSTATE_KEY] == null)
            ViewState[EXTENDED_FIELD_VIEWSTATE_KEY] = new ExtendedFieldDto();
        return (ExtendedFieldDto)ViewState[EXTENDED_FIELD_VIEWSTATE_KEY];
    }
    set { ViewState[EXTENDED_FIELD_VIEWSTATE_KEY] = value; }
}

person bflemi3    schedule 05.02.2014    source источник
comment
Не могли бы вы использовать конструктор, который устанавливает ваши частные поля? Это не должно вызывать setProperty.   -  person Vlad    schedule 05.02.2014
comment
@Vlad Да, это определенно сработает, но реальный объект имеет более 20 свойств, и это делает управление объектом (то есть: добавление / удаление свойств) довольно затруднительным. Хотя, возможно, это лучший способ :/   -  person bflemi3    schedule 05.02.2014
comment
@Vlad Кроме того, с моей текущей реализацией я сохраняю этот объект в ViewState, поэтому у меня есть свойство для объекта в моем aspx, которое в геттере инициализирует объект, если пара ключ-значение ViewState не существует. Поэтому не уверен, как бы я передал туда все свойства. get { if (ViewState[EXTENDED_FIELD_VIEWSTATE_KEY] == null) ViewState[EXTENDED_FIELD_VIEWSTATE_KEY] = new ExtendedFieldDto(); return (ExtendedFieldDto)ViewState[EXTENDED_FIELD_VIEWSTATE_KEY]; }   -  person bflemi3    schedule 06.02.2014


Ответы (2)


Это одна из распространенных проблем, для решения которых ISupportInitialize.

public abstract class NotifyPropertyChanged : ISupportInitialize {
   ...
   public void BeginInit() { }
   public void EndInit() { PropertyChangedObserver(); }
}

и вы используете его так:

ExtendedFieldDto foo = new ExtendedFieldDto();
foo.BeginInit();
foo.id = "abc123";
foo.idLocation = 0;
...
foo.EndInit();

EDIT: вы также можете подумать о реализации INotifyPropertyChanged пока вы это делаете, так как вы уже прошли большую часть пути.

person Oblivious Sage    schedule 05.02.2014
comment
Приятный интерфейс: ISupportInitialize. Я не знал этого. К сожалению, XmlSerializer.Deserialize (.Net v4.0), похоже, не использует его. - person Knowleech; 06.02.2014

Однажды у меня была очень похожая проблема. Идея использовать ctor у меня не сработала, потому что объект был создан и инициализирован десериализацией XML.

Довольно уродливым решением, которое я использовал, была псевдоглобальная инициализирующая логическая переменная (потоковая статическая переменная, http://msdn.microsoft.com/library/system.threadstaticattribute%28v=vs.110%29.aspx).). Перед десериализацией данных я установил для этого логического значения значение true, а после завершения десериализации я сбросил его на значение false (используя конструкцию try-finally для безопасности). В моих обработчиках PropertyChanged я проверил это логическое значение. Если бы это было правдой, я просто не запускал свои события.

Это очень некрасивое решение. Мне даже совсем не нравится называть это решением из-за проверки этой переменной bool каждый раз, когда вызывается обработчик PropertyChanged. Это не чистый или сохраненный подход, так как вы должны быть уверены, что установили это логическое значение только во время инициализации, и вы должны обязательно сбросить его после этого. Но это сработало для меня.

person Knowleech    schedule 05.02.2014
comment
Я тоже думал об этом, но мне не нравилась идея о том, что клиенту нужно беспокоиться ни о чем, кроме установки объекта. - person bflemi3; 06.02.2014
comment
Возможно, это можно изменить так, чтобы свойства имели некоторые неясные начальные значения, а setproperty не срабатывал, если значение изменяется «от» начального значения. Хотя это, вероятно, выглядело бы довольно странно, и я не уверен, какими могут/должны быть начальные значения. - person Vlad; 06.02.2014