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

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

所讨论的值对象由三到五个属性组成,这些属性通常是 X、Y、Z 和一些辅助材料的变体。鉴于此,我曾考虑创建三个 ViewModel 来处理各种可能性,其中每个 ViewModel 都需要为每个属性的值公开属性以及为每个标签显示的描述(例如“纬度”)。

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

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

或将其放在 DataGrid 中,例如:

Latitude  |  Longitude  |  Height
      32           115         12

所以我的问题是:

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

我主要在寻找:

  • 尽量减少代码重复
  • 易于添加新的值对象类型
  • 可以通过某种验证进行扩展
4

3 回答 3

5

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

它可能看起来像这样:

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

您的绑定仍可能如下所示:

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

因为 Mutable 对象将“假装”拥有像 Latitude 这样的属性,这要归功于它的 TypeDescriptor。

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

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

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

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

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

于 2009-11-05T06:58:16.883 回答
2

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

另一种选择(当 Paul 提供他的解决方案时不可用,因为 .Net 4 尚未推出)是使用相同的策略,但不是使用 CustomTypeDescriptors 来实现它,而是使用泛型、动态对象和反射的组合来实现相同的效果.

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

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

它的构造函数采用不可变类型的实例和从字典中构造它的新实例的委托,就像在保罗的回答中一样。然而,这里的不同之处在于您覆盖 TryGetMember 和 TrySetMember 以填充您最终将用作构造函数委托的参数的内部字典。您使用反射来验证您接受的唯一属性是那些在 ImmutableType 中实际实现的属性。

性能方面,我打赌 Paul 的答案更快,并且不涉及动态对象,众所周知,动态对象会使 C# 开发人员陷入困境。但是这个解决方案的实现也稍微简单一些,因为类型描述符有点神秘。


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

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

于 2013-08-17T02:49:22.547 回答
0

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

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

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

HTH。

PS BTW,你有不可变的类实例,而不是值对象。使用值对象(由struct关键字描述),无论是否有 setter,您都会跳得更多:)。

于 2009-11-03T05:17:33.977 回答