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

StackOverflow https://stackoverflow.com/questions/862209

  •  21-08-2019
  •  | 
  •  

Вопрос

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

/// <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 и некоторых вспомогательных элементов.Учитывая это, я рассмотрел возможность создания трех ViewModel для обработки различных возможностей, где каждая ViewModel должна будет предоставлять свойства для значения каждого свойства, а также описание для отображения для каждой метки (например.«Широта»).

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

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

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

Latitude  |  Longitude  |  Height
      32           115         12

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

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

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

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

Решение

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

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

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

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

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

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


Вот запрошенное доказательство концепции/пример реализации:

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

Другие советы

Для решения этой проблемы можно использовать пользовательские дескрипторы типов.Прежде чем вы привяжетесь к позиции, ваш дескриптор типа может включиться и предоставить методы 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.

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

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

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

ХТХ.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top