我怎样才能挂接到基 class 派生的 class 的所有 属性 setter?

How can I hook into all property setters of derived classes from a base class?

我有一个 .net Web 应用程序,对于这个问题的所有意图和目的,它是具有许多不同域对象的 CRUD。

这些对象的一个​​共同主题是需要知道哪些值属性已被修改以及子域模型属性。目前我们为此准备了两个不同的系统。

价值属性是我试图解决这个问题的那个。

现在的模型都继承自 PersistableModel 基础,具有这些字段和注意的方法:

 private readonly List<string> _modifiedProperties = new List<string>();
 public virtual ModelState State { get; set; }
 public IEnumerable<string> ModifiedProperties { get { return _modifiedProperties; } }
 protected bool HasModifiedProperties { get { return 0 < _modifiedProperties.Count; } }
 public bool WasModified(string propertyName)
 {
      return _modifiedProperties.Contains(propertyName);
 }
 public void WasModified(string propertyName, bool modified)
 {
     if (modified)
     {
         if (!WasModified(propertyName)) _modifiedProperties.Add(propertyName);
     }
     else 
     {
         _modifiedProperties.Remove(propertyName);
     }
 }

然后在每个单独的模型中,每当设置 属性 时,我们还需要使用 属性 名称的字符串和布尔值调用 WasModified。

显然这是非常繁琐且容易出错的,我想做的是重新设计这个基础 class 以在派生 class 的 属性 时自动将条目添加到字典中已设置。

在我的研究中,我能得到的最接近的结果是使用 PostSharp,这是不可能的。

在处理另一个项目时,我想出了一个解决方案,该解决方案大部分实现了我最初的目标。

请注意,此解决方案依赖于 Dev Express ViewModelBase 作为其基础 class,但创建一个具有用于非 Dev 的功能的新基础 class 并不难快递项目:

编辑:我发现重置状态逻辑存在问题,此更新消除了该问题。

 public abstract class UpdateableModel : ViewModelBase
{
    private static readonly MethodInfo GetPropertyMethod;
    private static readonly MethodInfo SetPropertyMethod;

    private readonly bool _trackingEnabled;
    private readonly Dictionary<string, Tuple<Expression, object>> _originalValues;
    private readonly List<string> _differingFields;

    static UpdateableModel() 
    {
        GetPropertyMethod = typeof(UpdateableModel).GetMethod("GetProperty", BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
        SetPropertyMethod = typeof(UpdateableModel).GetMethod("SetProperty");
    }

    protected UpdateableModel(bool isNewModel)
    {
        _originalValues = new Dictionary<string, Tuple<Expression, object>>();
        _differingFields = new List<string>();
        if (isNewModel) return;

        State = ModelState.Unmodified;
        _trackingEnabled = true;
    }

    public ModelState State
    {
        get { return GetProperty(() => State); }
        set { base.SetProperty(() => State, value); }
    }

    public new bool SetProperty<T>(Expression<Func<T>> expression, T value)
    {
        var wasUpdated = base.SetProperty(expression, value);
        if (_trackingEnabled && wasUpdated)
        {
            UpdateState(expression, value);
        }
        return wasUpdated;
    }

    /// <summary>
    /// Reset State is meant to be called when discarding changes, it will reset the State value to Unmodified and set all modified values back to their original value.
    /// </summary>
    public void ResetState()
    {
        if (!_trackingEnabled) return;
        foreach (var differingField in _differingFields)
        {
            var type = GetFuncType(_originalValues[differingField].Item1);

            var genericPropertySetter = SetPropertyMethod.MakeGenericMethod(type);
            genericPropertySetter.Invoke(this, new[]{_originalValues[differingField].Item2});
        }
    }

    /// <summary>
    /// Update State is meant to be called after changes have been persisted, it will reset the State value to Unmodified and update the original values to the new values.
    /// </summary>
    public void UpdateState()
    {
        if (!_trackingEnabled) return;
        foreach (var differingField in _differingFields)
        {
            var type = GetFuncType(_originalValues[differingField].Item1);
            var genericPropertySetter = GetPropertyMethod.MakeGenericMethod(type);
            var value = genericPropertySetter.Invoke(this, new object[] { _originalValues[differingField].Item1 });

            var newValue = new Tuple<Expression, object>(_originalValues[differingField].Item1,value);
            _originalValues[differingField] = newValue;
        }

        _differingFields.Clear();
        State = ModelState.Unmodified;
    }

    private static Type GetFuncType(Expression expr)
    {
        var lambda = expr as LambdaExpression;
        if (lambda == null)
        {
            return null;
        }
        var member = lambda.Body as MemberExpression;
        return member != null ? member.Type : null;
    }

    private void UpdateState<T>(Expression<Func<T>> expression, T value)
    {
        var propertyName = GetPropertyName(expression);
        if (!_originalValues.ContainsKey(propertyName))
        {
            _originalValues.Add(propertyName, new Tuple<Expression,object>(expression, value));
        }

        else
        {
            if (!Compare(_originalValues[propertyName].Item2, value))
            {
                _differingFields.Add(propertyName);
            }
            else if (_differingFields.Contains(propertyName))
            {
                _differingFields.Remove(propertyName);                   
            }

            State = _differingFields.Count == 0 
                ? ModelState.Unmodified 
                : ModelState.Modified;
        }
    }

    private static bool Compare<T>(T x, T y)
    {
        return EqualityComparer<T>.Default.Equals(x, y);
    }

另一个快速说明,IsNewModel 构造函数标志用于区分在 UI 级别上创建的不需要任何类型的状态跟踪的对象,以及从需要状态的数据访问级别生成的对象跟踪。