【发布时间】:2018-10-30 17:47:20
【问题描述】:
我正在尝试找出一种很好的方法来采用我们最近经历的 API 更改,该更改“弱化”了我们一个对象的类型。
用户过去可以提供对“狗”的引用列表。我们已决定现在接受“Dogs”和“Cats”。
客户端库使用强类型引用,因此该属性当前如下所示:
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 引用“包装”在 Occupant 中构造函数。
所以接下来我考虑使用一些辅助方法为列表创建一个新类:
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"}] }
如果未指定类型,则使用向后兼容规则将引用视为“狗”。
不幸的是,因为我很早就决定为这些引用对象添加编译时类型,所以 C# 客户端库用户是唯一遭受类型弱化的用户,而 API 仍然能够动态找出合适的类型在运行时。
【问题讨论】:
-
另一个(重大)更改是将
set访问器更改为private(对于occupants),然后提供Add(Dog dog)和Add(Cat cat)方法(而不仅仅是occupants.Add)。还没有真正考虑过,只是一个头脑风暴的想法......
标签: c# generics design-patterns covariance backwards-compatibility