Jak zabezpieczyć Typ bazy danych i wspierać refaktoryzację

Gdy chcę powiązać kontrolkę z właściwością mojego obiektu, muszę podać nazwę właściwości jako łańcuch znaków. To nie jest zbyt dobre, ponieważ:

  1. jeśli nieruchomość zostanie usunięta lub zmiana nazwy, nie dostaję kompilatora Uwaga.
  2. jeśli zmiana nazwy nieruchomości przy pomocy narzędzia do refaktoryzacji jest prawdopodobnie powiązanie z danymi nie będzie aktualizacja.
  3. I don ' t get an error until runtime jeżeli typ właściwości jest błędne, np. Wiązanie liczby całkowitej do a date wybierz.

Czy istnieje wzorzec projektowy, który obejdzie to, ale nadal ma łatwość użycia wiązania danych?

(jest to problem w WinForm, Asp.net i WPF i najprawdopodobniej wiele innych systemów)

Znalazłem teraz " obejścia dla operatora nameof() w C#: typesafe databinding", które również mają dobry punkt wyjścia dla rozwiązania.

Jeśli chcesz użyć post procesora po skompilowaniu kodu, notifypropertyweaver jest dobrze warte obejrzenia.


Ktoś zna dobre rozwiązanie dla WPF gdy wiązania są wykonywane w XML a nie C#?

Author: Community, 2009-08-25

7 answers

Dzięki Oliverowi za rozpoczęcie pracy mam teraz rozwiązanie, które obsługuje refaktoryzację i jest bezpieczne dla typu. To również pozwolił mi zaimplementować INotifyPropertyChanged więc radzi sobie z właściwości są zmieniane.

Jego użycie wygląda następująco:

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

Klasa person pokazuje, jak zaimplementować INotifyPropertyChanged w bezpieczny sposób typu (lub zobacz tę odpowiedź dla innego dość miłego sposobu implementacji INotifyPropertyChanged, ActiveSharp-Automatic INotifyPropertyChanged również wygląda dobrze):

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

Klasa pomocnicza wiążąca WinForms ma w sobie mięso, które sprawia, że wszystko działa:

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

To wykorzystuje wiele nowych rzeczy w C# 3.5 i pokazuje, co jest możliwe. Teraz gdybyśmy tylko mieli higieniczne makra programista Lispu może przestać nazywać nas obywatelami drugiej klasy)

 51
Author: Ian Ringrose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:13

Operator nameof został zaimplementowany w C # 6.0 Z. NET 4.6 i VS2015 w lipcu 2015 roku. Dla C #

Aby uniknąć łańcuchów zawierających nazwy właściwości, napisałem prostą klasę używającą drzew wyrażeń do zwracania nazwy elementu:

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

Możesz użyć tej klasy w następujący sposób. Mimo, że można go używać tylko w kodzie (więc nie w XAML), jest to dość pomocne (przynajmniej dla mnie), ale twój kod nadal nie jest typesafe. Możesz rozszerzyć nazwę metody z drugim argumentem typu, który określa wartość zwracaną funkcji, która ograniczyłaby typ właściwości.

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

Do tej pory nie znalazłem niczego, co rozwiązuje problem z typami danych.

Pozdrawiam

 27
Author: Oliver Hanappi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 11:04:47

Framework 4.5 zapewnia nam CallerMemberNameAttribute, co sprawia, że podanie nazwy właściwości jako ciągu znaków jest niepotrzebne:

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

Jeśli pracujesz nad frameworkiem 4.0 z zainstalowanym kb2468871, możesz zainstalować Microsoft BCL Compatibility Pack poprzez nuget, który również dostarcza ten atrybut.

 23
Author: takrl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-06 07:20:49

Ten artykuł na blogu stawia kilka dobrych pytań na temat skuteczności tego podejścia. Możesz poprawić te niedociągnięcia, konwertując wyrażenie na ciąg znaków jako część pewnego rodzaju inicjalizacji statycznej.

Rzeczywista mechanika może być trochę nieestetyczna, ale nadal byłaby bezpieczna dla typu i w przybliżeniu równa wydajności surowej INotifyPropertyChanged.

Coś w tym stylu:

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);
        }
    }
}
 5
Author: nedruod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-24 22:29:37

Jednym ze sposobów na uzyskanie informacji zwrotnej, jeśli wiązania są uszkodzone, jest utworzenie DataTemplate i zadeklarowanie jego typu danych jako typu ViewModel, z którym wiąże się, np. jeśli masz PersonView i PersonViewModel, wykonasz następujące czynności:

  1. Deklaracja DataTemplate za pomocą DataType = PersonViewModel i klucza (np. PersonTemplate)

  2. Wytnij wszystkie PersonView xaml i wklej go do szablonu danych (który najlepiej może być na górze PersonView.

3A. Utwórz ContentControl i ustaw ContentTemplate = PersonTemplate i powiązaj jego zawartość z PersonViewModel.

3B. inną opcją jest nie podawanie klucza do DataTemplate i nie ustawianie ContentTemplate ContentControl. W tym przypadku WPF zorientuje się, jakiego typu dane użyć, ponieważ wie, do jakiego typu obiektu jesteś wiązany. Będzie wyszukiwać w górę drzewa i znaleźć dane, a ponieważ pasuje do typu wiązania, to automatycznie zastosuje go jako ContentTemplate.

Kończysz z zasadniczo tym samym widokiem, co wcześniej, ale ponieważ zmapowałeś DataTemplate do bazowego typu danych, narzędzia takie jak Resharper mogą dać ci informację zwrotną (za pomocą identyfikatorów kolorów-Resharper-Options-Settings-Color Identifiers), aby upewnić się, że twoje powiązania są uszkodzone lub nie.

Nadal nie otrzymasz ostrzeżeń kompilatora, ale możesz wizualnie sprawdzić, czy nie ma uszkodzonych wiązań, co jest lepsze niż sprawdzanie tam iz powrotem między twój widok i viewmodel.

Kolejną zaletą tych dodatkowych informacji, które podajesz, jest to, że można je również wykorzystać do zmiany nazwy refaktoringów. O ile pamiętam Resharper jest w stanie automatycznie zmienić nazwy powiązań na wpisanych płytach danych, gdy nazwa właściwości ViewModel jest zmieniona i odwrotnie.

 3
Author: Thorsten Lorenz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-12 20:48:52

1.Jeśli właściwość zostanie usunięta lub zmieniona, nie otrzymuję ostrzeżenia kompilatora.

2.Jeśli zmienisz nazwę właściwości za pomocą narzędzia do refaktoryzacji, prawdopodobnie powiązanie z danymi nie zostanie zaktualizowane.

3.Błąd pojawia się dopiero w trybie runtime, jeśli typ właściwości jest nieprawidłowy, np. powiązanie liczby całkowitej z wyborem daty.

Tak, Ian, to są dokładnie problemy z powiązaniem danych z łańcuchem nazw. Prosiłeś o wzór. Zaprojektowałem Widok bezpieczny dla typu Wzór modelu (TVM), który jest konkrecją części modelu widoku wzorca Model-View-ViewModel (MVVM). Opiera się na wiązaniu bezpiecznym dla typu, podobnym do Twojej własnej odpowiedzi. Właśnie wrzuciłem rozwiązanie dla WPF:

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

 3
Author: Harry von Borstel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-04 07:51:38

X: bind (zwany także "skompilowanymi powiązaniami danych") dla XAML (universal app) w windows 10 i Windows phone 10 może rozwiązać ten problem, zobacz https://channel9.msdn.com/Events/Build/2015/3-635

Nie mogę znaleźć dokumentów on line, ale nie włożyłem wiele wysiłku, ponieważ jest to coś, czego Nie będę używał przez jakiś czas. Jednak ta odpowiedź powinna być użytecznym wskaźnikiem dla innych ludzi.

 1
Author: Ian Ringrose,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 17:40:55