如何使Databindingtypes安全并支持重构

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

  1. 如果该属性被删除或重命名,我没有得到一个编译器警告。
  2. 如果使用重构工具重命名属性,可能不会更新数据绑定。
  3. 如果属性的types是错误的,直到运行时才会出现错误,例如将整数绑定到dateselect器。

有没有一种devise模式可以解决这个问题,但仍然具有数据绑定的易用性?

(这是WinForm,Asp.net和WPF以及很多其他系统的问题)

我现在已经find了“ 在C#中使用nameof()运算符的解决方法:types安全的数据绑定 ”,这对解决scheme也有很好的起点。

如果你愿意在编译你的代码后使用后处理器, notifypropertyweaver是值得一看的。


任何人都知道一个很好的WPF解决scheme时,绑定是在XML而不是C#完成?

感谢Oliver让我开始我现在有一个解决scheme,支持重构和types安全。 它也让我实现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类显示了如何以types安全的方式实现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中使用了很多新的东西,并且展示了什么是可能的。 现在只要我们有卫生的macros, lisp程序员可能会停止给我们打电话给二等公民)

运算符的名称是在C#6.0中用.NET 4.6和VS2015在2015年7月实现的。以下对于C#仍然有效<6.0

为了避免包含属性名称的string,我写了一个简单的类,使用expression式树来返回成员的名称:

 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); } } 

你可以使用这个类如下。 即使你只能在代码中使用它(而不是在XAML中),它也是非常有用的(至less对我而言),但是你的代码仍然不是types安全的。 你可以使用第二个types参数来扩展Name方法,该参数定义函数的返回值,这将限制属性的types。

 var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty" 

到目前为止,我还没有find任何解决数据绑定types安全问题的东西。

最好的祝福

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

 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 ... } 

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

这篇博客文章提出了关于这种方法的一些很好的问题 。 通过将expression式转换为string作为某种静态初始化的一部分,可以改善这些缺陷。

实际的机制可能有点难看,但它仍然是types安全的,并且与原始的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); } } } 

如果绑定被破坏,获得反馈的一种方法是创build一个DataTemplate并将其DataType声明为它所绑定的ViewModel的types,例如,如果您有一个PersonView和一个PersonViewModel,您可以执行以下操作:

  1. 用DataType = PersonViewModel和一个键(例如PersonTemplate)声明一个DataTemplate

  2. 剪切所有PersonView xaml并将其粘贴到数据模板中(理想情况下可以位于PersonView的顶部。

3A。 创build一个ContentControl并设置ContentTemplate = PersonTemplate并将其内容绑定到PersonViewModel。

3B。 另一种select是不给DataTemplate的键,也不设置ContentControl的ContentTemplate。 在这种情况下,WPF将找出使用哪个DataTemplate,因为它知道你绑定到什么types的对象。 它会search树并find你的DataTemplate,因为它匹配的绑定types,它会自动将其作为ContentTemplate应用。

您最终得到的视图基本上与之前相同,但是由于您将DataTemplate映射到基础数据types,所以像Resharper这样的工具可以给您反馈(通过颜色标识符 – resharper-Options-Settings-Color Identifiers),以确定您的绑定是否被破坏或不。

你仍然不会得到编译器的警告,但可以直观地检查破坏的绑定,这比在视图和视图模型之间来回检查要好。

您给出的这个附加信息的另一个优点是,它也可以用于重命名重构。 据我记得Resharper能够自动重命名绑定的typesDataTemplates当基础ViewModel的属性名称被改变,反之亦然。

1.如果该属性被删除或重命名,我没有得到编译器警告。

2.如果使用重构工具重命名该属性,则数据绑定可能不会更新。

3.如果属性的types是错误的,例如绑定一个整数到一个dateselect器,我不会得到一个错误。

是的,伊恩,这正是名称string驱动的数据绑定的问题。 你问了一个devise模式。 我devise了Type-Safe View Model(TVM)模式,它是Model-View-ViewModel(MVVM)模式的View模型部分的一个结构。 它基于一个types安全的绑定,类似于你自己的答案。 我刚刚为WPF发布了一个解决scheme:

http://www.codeproject.com/Articles/450688/Enhanced-MVVM-Design-w-Type-Safe-View-Models-TVM

对于Windows 10和Windows Phone 10中的XAML(通用应用程序),x:bind(也称为“编译数据绑定”)可以解决此问题,请参阅https://channel9.msdn.com/Events/Build/2015/3-635

我找不到在线的文档,但没有付出太多的努力,因为这是我不会使用一段时间的东西。 但是,这个答案应该是一个有用的指针,其他人。