如何在WPF中编辑不可变对象而不重复代码?

12

我们的领域模型中有许多不可变的值对象,例如位置,由纬度、经度和高度定义。

/// <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和setter,以及一个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

自定义类型描述符可用于解决此问题。在绑定到位置之前,您的类型描述符可以介入并提供获取和设置方法来临时构建值。当更改提交时,它可以构建不可变对象。

可能会看起来像这样:

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

你的绑定仍然可以像这样:

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

因为可变对象将通过其类型描述符“假装”拥有像Latitude这样的属性。或者您可以在绑定中使用转换器并想出某种约定。
您的可变类将获取当前不可变对象和一个Func<IDictionary,object>,该函数允许您在编辑完成后创建新的不可变对象。您的可变类将利用类型描述符,该描述符将创建PropertyDescriptor,一旦设置便会创建新的不可变对象。
关于如何使用类型描述符的示例,请参见此处:http://www.paulstovell.com/editable-object-adapter 编辑: 如果您想限制不可变对象的创建频率,则还可以查看BindingGroups和IEditableObject,您的Mutable类也可以实现它们。

2

在研究我的可能选择时,我发现了这个旧问题。如果有人偶然遇到它,我想我应该更新一下:

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

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

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

它的构造函数接受一个不可变类型的实例和一个委托,该委托将其从字典中构造为新实例,就像Paul的答案一样。然而,这里的区别在于,您覆盖了TryGetMember和TrySetMember以填充内部字典,最终将用作构造函数委托的参数。您使用反射来验证您接受的唯一属性是那些实际上在ImmutableType中实现的属性。
就性能而言,我打赌Paul的答案更快,并且不涉及动态对象,这已知会使C#开发人员感到恼火。但是,这个解决方案的实现也稍微简单一些,因为类型描述符有点神秘。
以下是所请求的概念验证/示例实现:https://bitbucket.org/jwrush/mutable-generic-example

这是一个不错的解决方案,我更喜欢它而不是Paul的,因为它有更少的样板代码。当然,你也可以在他的解决方案中使用反射来减少将属性映射到字典的程序员开销。实际上并没有一个被接受的答案,因为我用另一种方式解决了这个问题,并且在最初提出问题大约6个月后才注意到Paul的回答。如果你能提供一个可行的代码片段供人们直接使用,我会将其标记为被接受的答案。 - Jacob Stanley

0
你能想到一个优雅的方法来解决这个问题吗?
老实说,你只是绕着问题打转,但没有提到问题本身 ;).
如果我猜对了你的问题,那么 MultiBinding 和 IMultiValueConverter 的组合应该可以解决问题。
希望有所帮助。
顺便说一下,你拥有不可变的类实例,而不是值对象。使用值对象(由 struct 关键字描述)时,无论是否有 setter,你都需要更多地绕弯子 :).

2
我认为你对值对象有误解。正如你所说,值类型是结构体,但值对象并不是同一回事。请参阅此文章以获取更多信息:http://www.lostechies.com/blogs/jimmy_bogard/archive/2008/05/20/entities-value-objects-aggregates-and-roots.aspx - Jacob Stanley
1
你能详细说明一下如何让问题更加清晰吗?简而言之,我有一个不可变的对象,并且我想以与可变对象相同的方式轻松地将文本框绑定到其属性。 - Jacob Stanley
抱歉关于值对象的问题 - 是我的错,不知道有这个。关于详细阐述问题 - 例如,您可以提供想要泛型化的代码片段之一。也许,如果您提供一些您在XAML中理想的通用解决方案的元代码片段,可能也会很有用。 - archimed7592

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接