Как сделать тип привязки данных безопасным и поддержать рефакторинг - PullRequest
69 голосов
/ 25 августа 2009

Когда я хочу привязать элемент управления к свойству моего объекта, я должен предоставить имя свойства в виде строки. Это не очень хорошо, потому что:

  1. Если имущество удалено или переименован, я не получаю компилятор предупреждение.
  2. Если переименовать имущество с помощью инструмента рефакторинга, это скорее всего привязка данных не будет обновлено.
  3. Я не получаю сообщение об ошибке, пока время выполнения, если тип свойства неправильно, например связывание целого числа с Выбор даты.

Существует ли шаблон проектирования, который обходит это, но все же имеет простоту использования привязки данных?

(Это проблема в WinForm, Asp.net и WPF и, скорее всего, во многих других системах)

Теперь я нашел « обходные пути для оператора nameof () в C #: привязка типов к безопасному типу », который также имеет хорошую отправную точку для решения.

Если вы хотите использовать постпроцессор после компиляции кода, стоит посмотреть notifypropertyweaver .


Кто-нибудь знает хорошее решение для WPF, когда привязки выполняются в XML, а не в C #?

Ответы [ 7 ]

51 голосов
/ 26 августа 2009

Спасибо Оливеру за то, что он начал, у меня теперь есть решение, которое поддерживает рефакторинг и безопасно для типов. Это также позволило мне реализовать 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, может перестать называть нас гражданами второго сорта)

27 голосов
/ 25 августа 2009

Оператор nameof был реализован в C # 6.0 с .NET 4.6 и VS2015 в июле 2015 года. Следующее все еще действует для C # <6.0 </h2> Чтобы избежать строк, которые содержат имена свойств, я написал простой класс, использующий деревья выражений для возврата имени члена: 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), это весьма полезно (по крайней мере, для меня), но ваш код все еще не безопасен для типов. Вы можете расширить метод Name аргументом второго типа, который определяет возвращаемое значение функции, что ограничивает тип свойства. var name = Member.Name<MyClass>(x => x.MyProperty); // name == "MyProperty" До сих пор я не нашел ничего, что решало бы проблему безопасности типов данных. С наилучшими пожеланиями

24 голосов
/ 13 сентября 2012

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

Если вы работаете в Framework 4.0 с KB2468871 , вы можете установить Microsoft BCL Compatibility Pack через nuget , который также предоставляет этот атрибут.

5 голосов
/ 25 апреля 2010

Эта статья в блоге поднимает несколько хороших вопросов о производительности этого подхода . Вы можете исправить эти недостатки, преобразовав выражение в строку как часть статической инициализации.

Реальная механика может показаться немного неприглядной, но она все равно будет безопасна для типов и будет примерно такой же производительности, что и необработанный 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);
        }
    }
}
3 голосов
/ 04 сентября 2012

1.Если свойство удалено или переименовано, я не получаю предупреждение компилятора.

2. При переименовании свойства с помощью инструмента рефакторинга, вероятно, привязка данных не будет обновлена.

3.Я не получаю ошибку до времени выполнения, если тип свойства неправильный, например, привязка целого числа к выбору даты.

Да, Ян, именно в этом и заключается проблема с привязкой данных на основе именной строки. Вы просили дизайн-шаблон. Я разработал шаблон Type-Safe View Model (TVM), который представляет собой конкретную часть модели View модели Pattern-View-ViewModel (MVVM). Он основан на типобезопасной привязке, аналогичной вашему собственному ответу. Я только что опубликовал решение для WPF:

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

3 голосов
/ 12 февраля 2011

Один из способов получить обратную связь, если ваши привязки нарушены, - это создать DataTemplate и объявить его DataType типом ViewModel, с которым он связывается, например. если у вас есть PersonView и PersonViewModel, вы бы сделали следующее:

  1. Объявление шаблона данных с DataType = PersonViewModel и ключом (например, PersonTemplate)

  2. Вырежьте все xaml PersonView и вставьте его в шаблон данных (который в идеале может находиться в верхней части PersonView.

3a. Создайте ContentControl и установите ContentTemplate = PersonTemplate и привяжите его содержимое к PersonViewModel.

3b. Другой вариант - не давать ключ к DataTemplate и не устанавливать ContentTemplate для ContentControl. В этом случае WPF выяснит, какой DataTemplate использовать, поскольку он знает, к какому типу объекта вы привязываетесь. Он выполнит поиск по дереву и найдет ваш DataTemplate, а поскольку он соответствует типу привязки, он автоматически применит его как ContentTemplate.

В конечном итоге вы получаете практически то же представление, что и раньше, но поскольку вы сопоставили DataTemplate с базовым DataType, такие инструменты, как Resharper, могут дать вам обратную связь (через идентификаторы цвета - Resharper-Options-Settings-Color Identifiers), чтобы смягчить ваши сломаны привязки или нет.

Вы по-прежнему не будете получать предупреждения компилятора, но сможете визуально проверять наличие нарушенных привязок, что лучше, чем необходимость проверять информацию между вашим представлением и моделью представления.

Еще одно преимущество этой дополнительной информации, которую вы предоставляете, заключается в том, что она также может использоваться при переименовании рефакторингов. Насколько я помню, Resharper может автоматически переименовывать привязки для типизированных шаблонов данных при изменении имени основного ViewModel и наоборот.

1 голос
/ 31 июля 2015

x: bind (также называемый «привязками скомпилированных данных») для XAML (универсальное приложение) в Windows 10 и Windows Phone 10 может решить эту проблему, см. https://channel9.msdn.com/Events/Build/2015/3-635

Я не могу найти онлайн документы для этого, но не приложил много усилий, потому что это то, что я не буду использовать в течение некоторого времени. Однако этот ответ должен быть полезным указателем на других людей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...