可以包含两种类型项目的列表的模式

时间:2018-10-30 17:47:20

标签: c# generics design-patterns covariance backwards-compatibility

我正在尝试找出一种采用API更改的好方法,我们最近对API进行了更改,以“削弱”我们的对象之一的类型。

过去,用户可以提供对“狗”的引用列表。我们已经决定现在接受“狗”和“猫”。

客户端库使用强类型引用,因此该属性当前看起来像:

public List<IReference<Dog>> occupants { get; set; }

我考虑过将其更改为:

public List<IReference<Animal>> occupants { get; set; }

但是我不喜欢以前的编译时验证现在是运行时(和服务器端)验证。我本来希望输入更强的类型,例如:

public List<Occupant> occupants { get; set; }

public class Occupant : IReference<Animal> {
    IReference<Animal> _internalReference;
    public Occupant(IReference<Dog> dog) { _internalReference = dog }
    public Occupant(IReference<Cat> cat) { _internalReference = cat }
    //... (Implement IReference<Animal> as a pass-through to _internalReference)
}

不幸的是,由于C#不允许隐式运算符从接口进行转换,因此更新其客户端库的现有用户将不得不进行数百行代码更改,以将其所有现有Dog引用“包装”在{ {1}}构造函数。

所以接下来我考虑使用一些辅助方法为列表创建一个新类:

Occupant

不幸的是,C#无法在后台公开覆盖核心public class OccupantList : List<Occupant> { public void Add(IReference<Dog> newDog) => base.Add(new Occupant(newDog)); public void Add(IReference<Cat> newCat) => base.Add(new Occupant(newCat)); // For backwards compatibility with new list assignments public static implicit operator OccupantList(List<IReference<Dog>> legacy) => new OccupantList(legacy.Select(dog => new Occupant(dog))); } 实现的方法,各种反射实用程序和我的JSON序列化程序都使用C#来填充此列表,因此当给定要添加的对象时,它会抱怨尚未为ICollection.Add类型的文件。我个人在使用JSON解串器时遇到问题。我可以为此类编写一个自定义反序列化器,但我以此为标志,试图继承Occupant并添加对不继承自List<Occupant>的类型的支持,这注定是失败的。 / p>

有人有什么想法或例子可以指导我们正确的方向吗?


这只是“我的问题”

真正的缺点是,这甚至不是API层的向后不兼容。该API使用动态类型(带有Python解释器的JSON),因此唯一的变化就是以下JSON:

Occupant

成为:

{ "occupants": [{"ref_id":"abc123"}, {"ref_id":"zzz999"}] }

具有向后兼容的规则,如果未指定类型,则将引用视为“狗”。

不幸的是,由于我早先决定向这些引用对象添加编译时类型,因此C#客户端库用户是唯一遭受类型弱化的用户,而API仍能够动态找出合适的类型在运行时。

1 个答案:

答案 0 :(得分:2)

最糟糕的是:

您会讨厌这一点,但是如果该API有任何使用者,则最佳实践表明您的重大更改意味着现在应该对API进行版本了。

充其量:

我没有看到您提到的API控制器的签名,但是如果有问题的模型被定义为该方法的参数,您可能可以摆脱重载控制器方法的麻烦,模型绑定器处理不同的形状,然后在某种中间件或自动映射器中解决该问题。

为什么不能通过更改模型来解决?

因为听起来您已经针对某种外部使用发布了此API,您实际上确实不应该违反该输入模型所描述的合同。