IT박스

데이터 바인딩 유형을 안전하게 만들고 리팩토링을 지원하는 방법

itboxs 2020. 11. 5. 07:49
반응형

데이터 바인딩 유형을 안전하게 만들고 리팩토링을 지원하는 방법


컨트롤을 개체의 속성에 바인딩하려면 속성 이름을 문자열로 제공해야합니다. 이는 다음과 같은 이유로별로 좋지 않습니다.

  1. 속성이 제거되거나 이름이 변경되면 컴파일러 경고가 표시되지 않습니다.
  2. 리팩토링 도구를 사용하여 속성의 이름을 바꾸면 데이터 바인딩이 업데이트되지 않을 수 있습니다.
  3. 속성 유형이 잘못된 경우 런타임까지 오류가 발생하지 않습니다 (예 : 날짜 선택기에 정수 바인딩).

이 문제를 해결하는 디자인 패턴이 있지만 여전히 데이터 바인딩 사용이 용이합니까?

(이것은 WinForm, Asp.net 및 WPF 및 대부분의 다른 시스템에서 발생하는 문제입니다.)

이제 " C #의 nameof () 연산자에 대한 해결 방법 : typesafe 데이터 바인딩 "을 찾았습니다 . 이 역시 솔루션을위한 좋은 시작점이됩니다.

코드를 컴파일 한 후 포스트 프로세서를 사용하려는 경우 notifypropertyweaver를 살펴볼 가치가 있습니다.


바인딩이 C #이 아닌 XML로 수행 될 때 누구나 WPF에 대한 좋은 솔루션을 알고 있습니까?


시작해 준 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 프로그래머는 우리를 2 등 시민이라고 부르는 것을 멈출 수 있습니다)


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

이 클래스는 다음과 같이 사용할 수 있습니다. 코드에서만 사용할 수 있지만 (XAML에서는 사용할 수 없음) 상당히 유용하지만 (적어도 저에게는) 코드가 여전히 형식 안전하지 않습니다. 속성의 유형을 제한하는 함수의 반환 값을 정의하는 두 번째 유형 인수로 Name 메서드를 확장 할 수 있습니다.

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

Until now I haven't found anything which solves the databinding typesafety issue.

Best Regards


The Framework 4.5 provides us with the CallerMemberNameAttribute, which makes passing the property name as a string unnecessary:

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

If you're working on Framework 4.0 with KB2468871 installed, you can install the Microsoft BCL Compatibility Pack via nuget, which also provides this attribute.


This blog article raises some good questions about the performance of this approach. You could improve upon those shortcomings by converting the expression to a string as part of some kind of static initialization.

The actual mechanics might be a little unsightly, but it would still be type-safe, and approximately equal performance to the raw INotifyPropertyChanged.

Something kind of like this:

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

One way to get feedback if your bindings are broken, is to create a DataTemplate and declare its DataType to be the type of the ViewModel that it binds to e.g. if you have a PersonView and a PersonViewModel you would do the following:

  1. Declare a DataTemplate with DataType = PersonViewModel and a key (e.g. PersonTemplate)

  2. Cut all PersonView xaml and paste it into the data template (which ideally can just be at the top of the PersonView.

3a. Create a ContentControl and set the ContentTemplate = PersonTemplate and bind its Content to the PersonViewModel.

3b. Another option is to not give a key to the DataTemplate and not set the ContentTemplate of the ContentControl. In this case WPF will figure out what DataTemplate to use, since it knows what type of object you are binding to. It will search up the tree and find your DataTemplate and since it matches the type of the binding, it will automatically apply it as the ContentTemplate.

You end up with essentially the same view as before, but since you mapped the DataTemplate to an underlying DataType, tools like Resharper can give you feedback (via Color identifiers - Resharper-Options-Settings-Color Identifiers) as to wether your bindings are broken or not.

You still won't get compiler warnings, but can visually check for broken bindings, which is better than having to check back and forth between your view and viewmodel.

Another advantage of this additional information you give, is, that it can also be used in renaming refactorings. As far as I remember Resharper is able to automatically rename bindings on typed DataTemplates when the underlying ViewModel's property name is changed and vice versa.


1.If the property is removed or renamed, I don’t get a compiler warning.

2.If a rename the property with a refactoring tool, it is likely the data binding will not be updated.

3.I don’t get an error until runtime if the type of the property is wrong, e.g. binding an integer to a date chooser.

Yes, Ian, that are exactly the problems with name-string driven data binding. You asked for a design-pattern. I designed the Type-Safe View Model (TVM) pattern that is a concretion of the View Model part of the Model-View-ViewModel (MVVM) pattern. It is based on a type-safe binding, similar to your own answer. I've just posted a solution for WPF:

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


x:bind (also called "compiled data bindings") for XAML (universal app) in windows 10 and windows phone 10 may solve this problem, see https://channel9.msdn.com/Events/Build/2015/3-635

I can't find the on line docs for it, but have not put much effort in, as it is something I will not be using for some time. However this answer should be a useful pointer to other people.

참고URL : https://stackoverflow.com/questions/1329138/how-to-make-databinding-type-safe-and-support-refactoring

반응형