Как я могу редактировать неизменяемые объекты в WPF без дублирования кода? - PullRequest
12 голосов
/ 14 мая 2009

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

/// <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, который имеет методы установки getter и , а также метод ToPosition () для извлечения проверенного экземпляра неизменной позиции. Хотя это решение будет в порядке, оно приведет к большому количеству дублированного кода, особенно XAML.

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

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

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

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

Latitude  |  Longitude  |  Height
      32           115         12

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

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

В основном я ищу:

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

Ответы [ 3 ]

5 голосов
/ 05 ноября 2009

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

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

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

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

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

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

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

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

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

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

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

2 голосов
/ 17 августа 2013

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

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

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

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

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

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


Вот запрошенная реализация концепции / примера:

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

0 голосов
/ 03 ноября 2009

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

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

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

НТН.

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

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