【问题标题】:Returning an item of type T from a list of classes that inherit from a base type - Typescript从继承自基本类型的类列表中返回 T 类型的项目 - Typescript
【发布时间】:2018-05-14 10:59:04
【问题描述】:

我有一个类 (Foo) 用于保存项目列表,所有项目的列表都继承自一个基类型 (IBar),这个列表可以包含任意数量的这些项目。

我遇到的问题是我试图在 Foo 上创建一个 get 方法,该方法采用仅限于从 IBar 继承的类型的泛型类型。

我目前拥有的接口和类是:

interface IBar {
  bar : string;
}

interface IFizz extends IBar {
  buzz : string;
}

class Foo {

  get<T extends IBar>() : T {
    var item = this.list[0];
    return item;
  }

  list : Array<IBar>;
}

我尝试运行的代码是:

var foo = new Foo();
var item = foo.get<IFizz>();

我在上面知道列表是空的,但这更多是为了让 Typescript 编译器不显示错误。调用 foo.get 的行很好并且没有错误,问题是 get 方法本身。 我从上面得到的错误是“类型'IBar'不可分配给类型'T'”。

如果查看 C# 作为参考,上述方法会起作用(我相信),并且欢迎任何书面示例来帮助我解决这个问题。

谢谢

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

    上面的代码在 C# 中也不起作用。两种语言都试图阻止您这样做,因为这不是类型安全的。考虑以下代码:

    class Foo {
        get<T extends IBar>() : T {
            var item = this.list[0];
            return item;
        }
    
        list : Array<IBar> = [ new OneClass() ];
    }
    
    var foo = new Foo();
    var item = foo.get<AnotherClass>();
    

    OneClassAnotherClass 都实现了IBar,但list[0] 的类型为OneClass,但调用者请求AnotherClass 的实例。因此,如果编译器允许您编写的代码,您将得到 item 类型为 AnotherClass 但持有 OneClass 的实例,这可能会导致运行时错误。

    就像在 C# 中一样,您可以强制 typescript 编译器让您使用类型断言(或类似的 C# 中的强制转换)来执行此操作。尽管您可以这样做,但您应该有另一种机制来确保数组中的项目实际上是 T 的正确类型:

    class Foo {
        get<T extends IBar>() : T {
            var item = this.list[0];
            return item as T;
        }
        list : Array<IBar> = [];
    }
    
    var foo = new Foo();
    var item = foo.get<IFizz>();
    

    【讨论】:

    • 感谢您的回复。我认为您使用“将项目作为 T”的答案可能是我以前在 C# 中看到的,因为我以前使用过这样的代码(未编写),但我同意这不好。鉴于您在上面提供的示例,我理解在运行时可能会出现问题的担忧,我可能会研究一个额外的安全网以减少任何运行时问题。我写这个的用例虽然是针对非生产测试套件,所以我希望我们在这条路上不会遇到很多问题
    • @GreaseMonkey 你当然可以使用断言,有时它们是不可避免的,为了完整起见,我只是指出它可能存在的缺陷。祝你好运:)
    【解决方案2】:

    您试图断言索引 0 处的项目是 IFizz,而不仅仅是 IBar;鉴于Foo 的声明不能静态保证,它只能在运行时使用特定数据为真。虽然您可以断言它 (return item as T;),但这只是隐藏了问题:当代码实际运行时,索引 0 处的项目可能不是 IFizz

    如果列表将包含IFizz,则应声明这样做。如果它要包含任何类型的IBar,则应该声明它这样做,并且任何将它用于IFizz 实例的代码都负责确保这是真的。

    相反,Foo 应使用 IBar 的实际类型参数化,它将包含:

    class Foo<T extends IBar> {
    
      get() : T {
        var item = this.list[0];
        return item;
      }
    
      list : Array<T>;
    }
    

    然后

    var item = foo.get();
    

    【讨论】:

    • 感谢您的回复。您几乎给出了与标记的正确答案相同的答案(对不起,我无法将两者都标记为正确!)。您提到的将泛型作为类初始化的一部分传递的唯一问题是列表可以并且将包含不止一种类型的 IBar 而不仅仅是一种。所以例如它可能包含 [IBar, IFizz, ISomethingElse] 都基于 IBar。
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多