Подтвердить что ты не робот

Как редактировать неизменяемые объекты в WPF без дублирования кода?

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

/// <remarks>When I grow up I want to be an F# record.</remarks>
public class Position
{
    public double Latitude
    {
        get;
        private set;
    }

    // snip

    public Position(double latitude, double longitude, double height)
    {
        Latitude = latitude;
        // snip
    }
}

Очевидным способом разрешить редактирование позиции является создание ViewModel, в котором есть геттеры и сеттеры, а также метод ToPosition() для извлечения проверенного неизменяемого экземпляра позиции. Хотя это решение будет в порядке, это приведет к большому дублированному коду, особенно к XAML.

Объекты ценности, о которых идет речь, состоят из трех и пяти свойств, которые обычно являются некоторым вариантом X, Y, Z и некоторых вспомогательных материалов. Учитывая это, я рассмотрел возможность создания трех ViewModels для обработки различных возможностей, где каждая ViewModel должна была бы выставлять свойства для значения каждого свойства, а также описание для отображения для каждой метки (например, "Широта" ).

Идя дальше, похоже, я могу упростить его до одного общего ViewModel, который может иметь дело с свойствами N и подключать все, используя отражение. Что-то вроде сетки свойств, но для неизменяемых объектов. Одна проблема с сеткой свойств заключается в том, что я хочу иметь возможность изменять внешний вид, чтобы иметь ярлыки и текстовые поля, например:

Latitude:   [      32 ]  <- TextBox
Longitude:  [     115 ]
Height:     [      12 ]

Или поместите его в DataGrid, например:

Latitude  |  Longitude  |  Height
      32           115         12

Итак, мой вопрос:

Можете ли вы придумать элегантный способ решить эту проблему? Существуют ли библиотеки, которые делают это, или статьи о чем-то подобном?

Я в основном ищу:

  • Уменьшение дублирования кода
  • Легко добавлять новые типы объектов значений
  • Возможно расширение с некоторой проверкой
4b9b3361

Ответ 1

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

Еще один вариант (недоступный, когда Пол предложил свое решение с тех пор, как .NET 4 еще не был), должен использовать ту же стратегию, но вместо того, чтобы внедрять его с помощью CustomTypeDescriptors, используйте комбинацию дженериков, динамических объектов и отражения для достижения тот же эффект.

В этом случае вы определяете класс

class Mutable<ImmutableType> : DynamicObject
{
   //...
}

Конструктор принимает экземпляр неизменяемого типа и делегат, который строит новый экземпляр из словаря, как и в ответе Павла. Разница здесь, однако, в том, что вы переопределите TryGetMember и TrySetMember для заполнения внутреннего словаря, который в конечном итоге вы собираетесь использовать в качестве аргумента для делегата-конструктора. Вы используете отражение, чтобы убедиться, что единственными свойствами, которые вы принимаете, являются те, которые фактически реализованы в ImmutableType.

Производительность мудрая, я ставлю, что Пол отвечает быстрее, и не включает динамические объекты, которые, как известно, помещают разработчиков С# в подгонки. Но реализация этого решения также немного проще, потому что дескрипторы типов немного тайны.


Здесь запрошенная реализация концепции/примера:

https://bitbucket.org/jwrush/mutable-generic-example

Ответ 2

Пользовательские дескрипторы типа могут использоваться для решения этой проблемы. Прежде чем привязываться к позиции, ваш дескриптор типа может взорваться и предоставить методам get и set для временного создания значений. Когда изменения будут совершены, он может создать неизменяемый объект.

Он может выглядеть примерно так:

DataContext = new Mutable(position, 
    dictionary => new Position(dictionary["lattitude"], ...)
);

Ваши привязки могут выглядеть следующим образом:

<TextBox Text="{Binding Path=Lattitude}" />

Поскольку объект Mutable будет "притворяться", чтобы иметь свойства, подобные Lattitude, благодаря свойству TypeDescriptor.

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

Ваш класс Mutable примет текущий неизменяемый объект и Func<IDictionary, object>, который позволит вам создать новый неизменяемый объект после завершения редактирования. Ваш класс Mutable будет использовать дескриптор типа, который будет создавать PropertyDescriptors, которые создадут новый неизменяемый объект после его установки.

Пример использования дескрипторов типов см. здесь:

http://www.paulstovell.com/editable-object-adapter

Изменить. Если вы хотите ограничить частоту создания неизменяемых объектов, вы также можете посмотреть BindingGroups и IEditableObject, которые также может реализовать ваш Mutable.

Ответ 3

Можете ли вы придумать элегантный способ решить эту проблему?

Честно говоря, вы просто танцуете вокруг проблемы, но не говорите о самой проблеме;).

Если я правильно угадываю вашу проблему, тогда комбинация MultiBinding и IMultiValueConverter должна сделать трюк.

НТН.

P.S. BTW, у вас есть неизменяемые экземпляры классов, а не значения объектов. С объектами значения (которые описываются ключевым словом struct) вы будете танцевать гораздо больше независимо от того, были ли сеттеры или нет:).