TypeScript编译器tsc
编译以下代码而不会抱怨,即使使用--strict
标志也是如此。但是,代码包含一个基本错误,在Java或C#等语言中会被阻止。
interface IBox<T> {
value: T;
}
const numberBox: IBox<number> = { value: 1 };
function insertString(items: IBox<string | number>): void {
items.value = 'Test';
}
// this call is problematic
insertString(numberBox);
// throws at runtime:
// "TypeError: numberBox.value.toExponential is not a function"
numberBox.value.toExponential();
可以配置tsc
以便识别出这样的错误吗?
答案 0 :(得分:3)
TypeScript实际上没有一个很好的通用方法来处理contravariance or invariance。粗略估计,如果输出某些东西(函数输出,只读属性),你可以输出比预期类型(协方差)更窄但不宽的东西,如果你输入的东西(函数输入,只写属性)你被允许接受更广泛但不比预期类型更窄的东西(逆变)。如果您正在读取和写入相同的值,则不允许缩小或扩大类型(不变性)。
这个问题在Java中并没有显示出来(不确定C#)主要是因为你不能轻易地创建类型的联合或交集,并且因为在泛型中有extends
和{{1}作为协方差和逆变的标记的约束。你确实在Java数组中看到了这一点,至少它们被认为是不合理的协变(尝试上面的super
和Object[]
,你会看到有趣的事情发生。)
TypeScript在将函数输出视为协变时通常做得很好。在TypeScript v2.6之前,编译器将函数输入视为bivariant,这是不合理的(但有一些有用的效果;请阅读链接的FAQ条目)。现在有一个--strictFunctionTypes编译器标志,允许您对独立函数(而不是方法)的函数输入强制执行逆变。
目前,TypeScript将属性值和泛型类型视为协变,这意味着它们适合阅读但不适合写入。这直接导致了你所看到的问题。请注意,对于属性值也是如此,因此您可以在没有泛型的情况下重现此问题:
Integer[]
除了“小心”之外,我没有很好的建议。 TypeScript是not intended,具有严格的声音类型系统(参见非目标#3);相反,语言维护者倾向于仅在程序中引起实际错误的程度上解决问题。如果这种事情对你影响很大,可能会转到Microsoft/TypeScript#10717或类似的问题,如果你觉得它具有说服力,可以给它一个或描述你的用例。
希望这有帮助。祝你好运!
答案 1 :(得分:0)
由于TypeScript 2.6 tsc
有一个命令行选项--strictFunctionTypes
(--strict
自动包含)。如果给定,则强制执行函数类型参数的逆变类型检查。出于兼容性原因,方法和构造函数被设计排除在此规则之外。因此,为了采用逆变,似乎需要使用函数类型:
interface IBox<T> {
getValue: () => T;
setValue: (value: T) => void;
}
class Box<T> implements IBox<T> {
private value: T;
public constructor(value: T) {
this.value = value;
}
public getValue() {
return this.value;
}
public setValue(value: T) {
this.value = value;
};
}
const numberBox: IBox<number> = new Box<number>(1);
function insertString(items: IBox<string | number>): void {
items.setValue('Test');
}
// this call does not compile anymore
insertString(numberBox);
如果在最后3个语句中IBox
被Box
替换,则不适用逆变类型检查。因此,为了防止某人意外地引用此类类型,Box<T>
和IBox<T>
可以放在单独的ES6模块中,只显示接口和工厂函数。