【问题标题】:How can I hook into all property setters of derived classes from a base class?如何从基类挂钩派生类的所有属性设置器?
【发布时间】:2015-04-09 20:08:56
【问题描述】:

我有一个 .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。

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

在我的研究中,最接近的方法是使用 PostSharp,这是不可能的。

【问题讨论】:

  • 这是INotifyPropertyChagned 问题(让每个属性在更改时自动引发PropertyChanged 事件),对此没有好的解决方案。 WPF 解决它的方法是通过依赖属性。您创建的属性只是 DependencyObject 上的 GetValue(SetValue( 方法的包装@
  • 我会在基类中使用受保护的索引器,该索引器使用私有 Dictionary 来保存值。这样,它可以充当派生类不实现自己的成员变量的看门人。

标签: c# inheritance aop


【解决方案1】:

在从事不同的项目时,我想出了一个解决方案,可以在很大程度上实现我的最初目标。

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

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

 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 级别创建的不需要任何状态跟踪的对象,以及从需要状态跟踪的数据访问级别生成的对象。

【讨论】:

    猜你喜欢
    • 2014-06-27
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 2017-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多