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

Ian*_*ose 69 .net c# data-binding refactoring type-safety

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

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

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

(这是WinForm,Asp.net和WPF中的一个问题,很可能是很多其他系统)

我现在已经找到了" C#中的nameof()运算符的变通方法:typesafe databinding ",它也是解决方案的一个很好的起点.

如果您在编译代码后愿意使用后处理器,那么notifypropertyweaver非常值得关注.


当绑定在XML而不是C#中完成时,任何人都知道WPF的良好解决方案吗?

Ian*_*ose 52

感谢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);
Run Code Online (Sandbox Code Playgroud)

person类显示如何以类型安全的方式实现INotifyPropertyChanged(或者查看此答案以获得实现INotifyPropertyChanged的另一种相当不错的方法,ActiveSharp - Automatic 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;
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Oli*_*ppi 27

nameof运算符是在2015年7月使用.NET 4.6和VS2015在C#6.0中实现的.以下内容对C#<6.0仍然有效

为了避免包含属性名称的字符串,我编写了一个使用表达式树的简单类来返回成员的名称:

using System;
using System.Linq.Expressions;
using System.Reflection;

public static class Member
{
    private static string GetMemberName(Expression expression)
    {
        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:
                return String.Empty;

            default:
                throw new ArgumentException("The expression is not a member access or method call expression");
        }
    }

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

    public static string Name<T>(Expression<Action<T>> expression)
    {
        return GetMemberName(expression.Body);
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以按如下方式使用此类.即使你只能在代码中使用它(所以不能在XAML中使用它),它是非常有用的(至少对我而言),但你的代码仍然不是类型安全的.您可以使用第二个类型参数扩展方法Name,该参数定义函数的返回值,这将约束属性的类型.

var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty"
Run Code Online (Sandbox Code Playgroud)

到目前为止,我还没有找到任何解决数据绑定类型安全问题的方法.

最好的祝福

  • 感谢您的出发点,我刚刚发布了一个答案,扩展您的工作以提供类型安全. (2认同)

tak*_*krl 24

Framework 4.5为我们提供了CallerMemberNameAttribute,它使得将属性名称作为字符串传递是不必要的:

private string m_myProperty;
public string MyProperty
{
    get { return m_myProperty; }
    set
    {
        m_myProperty = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
    // ... do stuff here ...
}
Run Code Online (Sandbox Code Playgroud)

如果您正在使用安装了KB2468871的 Framework 4.0,可以通过nuget安装Microsoft BCL兼容包,它还提供此属性.


ned*_*uod 5

这篇博客文章提出了一些关于这种方法的性能的好问题.您可以通过将表达式转换为字符串作为某种静态初始化的一部分来改善这些缺点.

实际的机制可能有点难看,但它仍然是类型安全的,并且与原始的INotifyPropertyChanged大致相同.

有点像这样:

public class DummyViewModel : ViewModelBase
{
    private class DummyViewModelPropertyInfo
    {
        internal readonly string Dummy;

        internal DummyViewModelPropertyInfo(DummyViewModel model)
        {
            Dummy = BindingHelper.Name(() => model.Dummy);
        }
    }

    private static DummyViewModelPropertyInfo _propertyInfo;
    private DummyViewModelPropertyInfo PropertyInfo
    {
        get { return _propertyInfo ?? (_propertyInfo = new DummyViewModelPropertyInfo(this)); }
    }

    private string _dummyProperty;
    public string Dummy
    {
        get
        {
            return this._dummyProperty;
        }
        set
        {
            this._dummyProperty = value;
            OnPropertyChanged(PropertyInfo.Dummy);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

21597 次

最近记录:

5 年,9 月 前