Недавно я унаследовал довольно большой проект, разработанный на C # и WPF.Он использует привязки вместе с интерфейсом INotifyPropertyChanged для распространения изменений в / из представления.
Небольшое предисловие: в разных классах у меня есть свойства, которые зависят от других свойств в том же классе (думаю,например, свойство «TaxCode», которое зависит от таких свойств, как «Имя» и «Фамилия»).С помощью некоторого кода, который я нашел здесь в SO (не могу найти ответ снова, хотя), я создал абстрактный класс "ObservableObject" и атрибут "DependsOn".Источник следующий:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace TestNameSpace
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public sealed class DependsOn : Attribute
{
public DependsOn(params string[] properties)
{
this.Properties = properties;
}
public string[] Properties { get; private set; }
}
[Serializable]
public abstract class ObservableObject : INotifyPropertyChanged
{
private static Dictionary<Type, Dictionary<string, string[]>> dependentPropertiesOfTypes = new Dictionary<Type, Dictionary<string, string[]>>();
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private readonly bool hasDependentProperties;
public ObservableObject()
{
DependsOn attr;
Type type = this.GetType();
if (!dependentPropertiesOfTypes.ContainsKey(type))
{
foreach (PropertyInfo pInfo in type.GetProperties())
{
attr = pInfo.GetCustomAttribute<DependsOn>(false);
if (attr != null)
{
if (!dependentPropertiesOfTypes.ContainsKey(type))
{
dependentPropertiesOfTypes[type] = new Dictionary<string, string[]>();
}
dependentPropertiesOfTypes[type][pInfo.Name] = attr.Properties;
}
}
}
if (dependentPropertiesOfTypes.ContainsKey(type))
{
hasDependentProperties = true;
}
}
public virtual void OnPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
if (this.hasDependentProperties)
{
//check for any computed properties that depend on this property
IEnumerable<string> computedPropNames = dependentPropertiesOfTypes[this.GetType()].Where(kvp => kvp.Value.Contains(propertyName)).Select(kvp => kvp.Key);
if (computedPropNames != null && !computedPropNames.Any())
{
return;
}
//raise property changed for every computed property that is dependant on the property we did just set
foreach (string computedPropName in computedPropNames)
{
//to avoid stackoverflow as a result of infinite recursion if a property depends on itself!
if (computedPropName == propertyName)
{
throw new InvalidOperationException("A property can't depend on itself");
}
this.OnPropertyChanged(computedPropName);
}
}
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
return this.SetField<T>(ref field, value, false, propertyName);
}
protected bool SetField<T>(ref T field, T value, bool forceUpdate, [CallerMemberName] string propertyName = null)
{
bool valueChanged = !EqualityComparer<T>.Default.Equals(field, value);
if (valueChanged || forceUpdate)
{
field = value;
this.OnPropertyChanged(propertyName);
}
return valueChanged;
}
}
}
Эти классы позволяют мне:
- Использовать только
this.SetValue(ref this.name, value)
внутри установщика моих свойств. - Использоватьатрибут
DependsOn(nameof(Name), nameof(LastName))
в свойстве TaxCode
Таким образом, TaxCode имеет только свойство-получатель, которое объединяет FirstName, LastName (и другие свойства) и возвращает соответствующий код.Даже с привязкой это свойство является актуальным благодаря этой системе зависимостей.
Таким образом, пока TaxCode имеет зависимости от свойств, которые находятся в одном классе, все работает правильно.Однако мне нужно иметь свойства, которые имеют одну или несколько зависимостей от их дочернего объекта .Например (я просто использую json, чтобы сделать иерархию более простой):
{
Name,
LastName,
TaxCode,
Wellness,
House:
{
Value
},
Car:
{
Value
}
}
Итак, свойство Wellness человека может быть реализовано так:
[DependsOn(nameof(House.Value), nameof(Car.Value))]
public double Wellness { get =>(this.House.Value + this.Car.Value);}
ПервыйПроблема в том, что «House.Value» и «Car.Value» не являются допустимыми параметрами для nameof в этом контексте.Второе - это то, что с моим реальным кодом я могу вызывать свойства, которые находятся только в одном и том же объекте, поэтому нет ни свойств потомков, ни свойств, которые распространяются на приложение (у меня есть, например, свойство, которое представляет, если единицыизмерения выражаются в метрических / британских единицах, и их изменение влияет на способ отображения значений).
Теперь решение, которое я мог бы использовать, могло бы заключаться в вставке словаря событий в мой ObservableObject, ключом которого было бы имясвойство и сделать родительский зарегистрировать обратный вызов.Таким образом, когда свойство дочернего элемента изменяется, событие запускается с кодом, чтобы уведомить об изменении свойства в родительском объекте.Однако этот подход вынуждает меня регистрировать обратные вызовы каждый раз, когда создается новый дочерний объект.Это, конечно, немного, но мне понравилась идея просто указать зависимости, и пусть мой базовый класс сделает всю работу за меня.
Итак, короче говоря, я пытаюсь создать системукоторый может уведомлять об зависимых изменениях свойств, даже если задействованные свойства являются его дочерними или не связаны с этим конкретным объектом.Поскольку база кода довольно большая, я бы не хотел просто отбрасывать существующий подход ObservableObject + DependsOn, и я ищу более элегантный способ, чем просто размещать обратные вызовы по всему моему коду.
Конечно, еслимой подход неверен / то, что я хочу, не может быть достигнуто с помощью кода, который у меня есть, пожалуйста, не стесняйтесь предлагать лучшие способы.Я не эксперт WPF и пытаюсь узнать как можно больше об этом.
Заранее спасибо.