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



我正在试图找到一种很好的方法来采用我们最近进行的API更改,该更改"削弱"了其中一个对象上的类型。

用户过去可以提供"狗"的参考列表。我们已经决定接受"狗"one_answers"猫"。

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

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#不允许隐式运算符从接口转换,更新其客户端库的现有用户将不得不经历数百行代码更改,才能在Occupant构造函数中"包装"所有现有的Dog引用。

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

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

不幸的是,C#没有公开在幕后覆盖核心ICollection.Add实现的方法,各种反射实用程序和我的JSON序列化程序都使用它来填充这个列表,所以当它添加的对象不是Occupant类型时,它会抱怨。我个人在使用JSON反序列化程序时遇到了问题。我可以为这个类编写一个自定义的反序列化程序,但我认为尝试继承List<Occupant>并添加对不从Occupant继承的类型的支持是一个注定会失败的坏主意。

有人有什么想法或例子可以引导我们朝着正确的方向前进吗?


这只是"我的问题">

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

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

变为:

{ "occupants": [{"ref_id":"abc123", "ref_type": "Dog"}, {"ref_id":"gdr736", "ref_type":"Cat"}] }

如果没有指定类型,则使用向后兼容性规则将引用视为"Dog"。

不幸的是,由于我很早就决定向这些引用对象添加compile-time类型,因此C#客户端库用户是唯一受到类型弱化影响的用户,而API仍然能够在运行时动态地找出合适的类型。

最坏情况下:

你会讨厌这样,但如果API有任何消费者,最佳实践表明,你的突破性改变意味着现在是时候对API进行版本化了。

充其量:

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

为什么不通过改变模型来解决呢

因为听起来你已经为某种外部消费发布了这个API,所以你真的真的不应该破坏输入模型所描述的契约。

最新更新