一尘不染

如何使数据绑定类型安全并支持重构?

c#

当我希望将控件绑定到对象的属性时,必须提供属性的名称作为字符串。这不是很好,因为:

  1. 如果该属性被删除或重命名,那么我不会收到编译器警告。
  2. 如果使用重构工具重命名该属性,则很可能不会更新数据绑定。
  3. 如果属性的类型错误,例如将整数绑定到日期选择器,那么直到运行时我都不会出错。

是否有一种设计模式可以解决此问题,但仍易于使用数据绑定?

(这是WinForms,ASP.NET和WPF以及其他系统中的问题。)

现在,我发现“
C#中的nameof()运算符的变通办法:typesafe数据绑定 ”也为解决方案提供了良好的起点。

如果您愿意在编译代码后使用后处理器,那么NotifyPropertyWeaver值得一看。


当绑定是用XML而不是C#完成时,有人知道WPF的一种好的解决方案吗?


阅读 238

收藏
2020-05-19

共1个答案

一尘不染

感谢Oliver让我入门,现在我有了一个既支持重构又是类型安全的解决方案。它也让我实现INotifyPropertyChanged,以便处理重命名的属性。

它的用法如下:

checkBoxCanEdit.Bind(c => c.Checked, person, p => p.UserCanEdit);
textBoxName.BindEnabled(person, p => p.UserCanEdit);
checkBoxEmployed.BindEnabled(person, p => p.UserCanEdit);
trackBarAge.BindEnabled(person, p => p.UserCanEdit);

textBoxName.Bind(c => c.Text, person, d => d.Name);
checkBoxEmployed.Bind(c => c.Checked, person, d => d.Employed);
trackBarAge.Bind(c => c.Value, person, d => d.Age);

labelName.BindLabelText(person, p => p.Name);
labelEmployed.BindLabelText(person, p => p.Employed);
labelAge.BindLabelText(person, p => p.Age);

person类显示了如何以一种类型安全的方式实现INotifyPropertyChanged(或查看此答案以获取实现INotifyPropertyChanged的另一种相当不错的方法,ActiveSharp-
自动INotifyPropertyChanged
也看起来不错):

public class Person : INotifyPropertyChanged
{
   private bool _employed;
   public bool Employed
   {
      get { return _employed; }
      set
      {
         _employed = value;
         OnPropertyChanged(() => c.Employed);
      }
   }

   // etc

   private void OnPropertyChanged(Expression<Func<object>> property)
   {
      if (PropertyChanged != null)
      {
         PropertyChanged(this, 
             new PropertyChangedEventArgs(BindingHelper.Name(property)));
      }
   }

   public event PropertyChangedEventHandler PropertyChanged;
}

WinForms绑定帮助程序类包含了使它们全部工作的基础:

namespace TypeSafeBinding
{
    public static class BindingHelper
    {
        private static string GetMemberName(Expression expression)
        {
            // The nameof operator was implemented in C# 6.0 with .NET 4.6
            // and VS2015 in July 2015. 
            // The following is still valid for C# < 6.0

            switch (expression.NodeType)
            {
                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression) expression;
                    var supername = GetMemberName(memberExpression.Expression);
                    if (String.IsNullOrEmpty(supername)) return memberExpression.Member.Name;
                    return String.Concat(supername, '.', memberExpression.Member.Name);
                case ExpressionType.Call:
                    var callExpression = (MethodCallExpression) expression;
                    return callExpression.Method.Name;
                case ExpressionType.Convert:
                    var unaryExpression = (UnaryExpression) expression;
                    return GetMemberName(unaryExpression.Operand);
                case ExpressionType.Parameter:
                case ExpressionType.Constant: //Change
                    return String.Empty;
                default:
                    throw new ArgumentException("The expression is not a member access or method call expression");
            }
        }

        public static string Name<T, T2>(Expression<Func<T, T2>> expression)
        {
            return GetMemberName(expression.Body);
        }

        //NEW
        public static string Name<T>(Expression<Func<T>> expression)
        {
           return GetMemberName(expression.Body);
        }

        public static void Bind<TC, TD, TP>(this TC control, Expression<Func<TC, TP>> controlProperty, TD dataSource, Expression<Func<TD, TP>> dataMember) where TC : Control
        {
            control.DataBindings.Add(Name(controlProperty), dataSource, Name(dataMember));
        }

        public static void BindLabelText<T>(this Label control, T dataObject, Expression<Func<T, object>> dataMember)
        {
            // as this is way one any type of property is ok
            control.DataBindings.Add("Text", dataObject, Name(dataMember));
        }

        public static void BindEnabled<T>(this Control control, T dataObject, Expression<Func<T, bool>> dataMember)
        {       
           control.Bind(c => c.Enabled, dataObject, dataMember);
        }
    }
}

这利用了C#3.5中的许多新内容,并显示了可能的结果。现在,如果只有卫生宏,那么
Lisp程序员可能会停止称我们为二等公民)

2020-05-19