如何在不重复代码的情况下编辑WPF中的不可变对象?

时间:2009-05-14 08:34:00

标签: wpf immutability

我们的域模型中有许多不可变的值对象,其中一个例子是由纬度,经度和范围定义的位置。高度。

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

允许编辑位置的显而易见的方法是构建一个具有getter setter的ViewModel,以及一个ToPosition()方法来提取经过验证的不可变位置实例。虽然这个解决方案没问题,但会产生大量重复的代码,尤其是XAML。

有问题的价值对象包括三到五个属性,这些属性通常是X,Y,Z和Z的一些变体。一些辅助的东西。鉴于此,我考虑创建三个ViewModel来处理各种可能性,其中每个ViewModel需要公开每个属性的值的属性以及为每个标签显示的描述(例如“Latitude”)。

更进一步,似乎我可以将它简化为一个可以处理N个属性并使用反射挂钩所有内容的一般ViewModel。像属性网格,但对于不可变对象。属性网格的一个问题是我希望能够更改外观,以便我可以使用标签和文本框,例如:

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

或者将其放在DataGrid中,例如:

Latitude  |  Longitude  |  Height
      32           115         12

所以我的问题是:

你能想出一个解决这个问题的优雅方法吗?有没有这样做的图书馆或类似的文章?

我主要是寻找:

  • 最小化代码重复
  • 轻松添加新的价值对象类型
  • 可以通过某种验证进行扩展

3 个答案:

答案 0 :(得分:5)

自定义类型描述符可用于解决此问题。在绑定到Position之前,您的类型描述符可以启动,并提供get和set方法来临时构建值。提交更改时,它可以构建不可变对象。

它可能看起来像这样:

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

您的绑定仍然如下所示:

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

由于其TypeDescriptor,Mutable对象将“假装”拥有像Lattitude这样的属性。

或者,您可以在绑定中使用转换器并提出某种约定。

您的Mutable类将获取当前不可变对象,以及Func<IDictionary, object>,它允许您在编辑完成后创建新的不可变对象。您的Mutable类将使用类型描述符,它将创建PropertyDescriptors,在设置时创建新的不可变对象。

有关如何使用类型描述符的示例,请参见此处:

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

编辑:如果要限制不可变对象的创建频率,您还可以查看BindingGroups和IEditableObject,您的Mutable也可以实现它们。

答案 1 :(得分:2)

我在同样的情况下研究我可能的选择时发现了这个老问题。我想我应该更新它,万一其他人偶然发现它:

另一个选项(自从.Net 4还没有提供他的解决方案时不可用)是使用相同的策略,但不是使用CustomTypeDescriptors实现它,而是使用泛型,动态对象和反射的组合来实现同样的效果。

在这种情况下,您可以定义一个类

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

它的构造函数接受一个不可变类型的实例和一个委托,它从字典中构造一个新的实例,就像Paul的答案一样。但是,这里的区别在于您重写TryGetMember和TrySetMember以填充您最终将用作构造函数委托的参数的内部字典。您使用反射来验证您接受的唯一属性是那些在ImmutableType中实际实现的属性。

性能方面,我打赌Paul的答案更快,并且不涉及动态对象,已知这些对象可以使C#开发人员适应。但是这个解决方案的实现也有点简单,因为类型描述符有点神秘。


这是所请求的概念验证/示例实现:

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

答案 2 :(得分:0)

  

你能想出一个解决这个问题的优雅方法吗?

老实说,你只是围绕这个问题跳舞,但不要提问问题本身;)。

如果我正确猜出你的问题,那么MultiBinding和IMultiValueConverter的组合应该可以解决问题。

HTH。

P.S。顺便说一下,你有不可变的类实例,而不是值对象。使用值对象(由struct关键字描述),无论是否有设置者,你都会跳得更多:)。