【问题标题】:Why can't I use a subclass of a generic type in Swift?为什么我不能在 Swift 中使用泛型类型的子类?
【发布时间】:2016-10-19 18:48:02
【问题描述】:

为什么 Swift 不允许我将值 Foo<U> 分配给 Foo<T> 类型的变量,其中 U 是 T 的子类?

例如:

class Cheese {
    let smell: Int
    let hardness: Int
    let name: String

    init(smell: Int, hardness: Int, name: String) {
        self.smell = smell
        self.hardness = hardness
        self.name = name
    }

    func cut() {
        print("Peeyoo!")
    }
}

class Gouda: Cheese {
    let aged: Bool

    init(smell: Int, hardness: Int, name: String, aged: Bool) {
        self.aged = aged
        super.init(smell: smell, hardness: hardness, name: name)
    }

    override func cut() {
        print("Smells delicious")
    }
}

class Platter<Food> {
    var food: Food

    init(food: Food) {
        self.food = food
    }
}

let goudaCheese = Gouda(smell: 6, hardness: 5, name: "Gouda", aged: false)
let goudaPlatter = Platter(food: goudaCheese)  //Platter<Gouda>

//error: cannot assign value of type 'Platter<Gouda>' to type 'Platter<Cheese>'
let platter: Platter<Cheese> = goudaPlatter

但为什么它不起作用?您可以为变量分配一个对象,该对象是其类型的子类,例如

let gouda = Gouda(smell: 6, hardness: 5, name: "Gouda", aged: false)
let cheese: Cheese = gouda

您可以将子类添加到集合中:

let plainCheese = Cheese(smell: 2, hardness: 5, name: "American")
let gouda = Gouda(smell: 6, hardness: 5, name: "Gouda", aged: false)
var cheeses: [Cheese] = [plainCheese]
cheeses.append(gouda)

那么let platter: Platter&lt;Cheese&gt; = goudaPlatter 有什么不同呢?是否有任何情况下如果它起作用会不安全?仅仅是当前版本 Swift 的限制吗?

【问题讨论】:

  • 不是肯定的,但你能把Platter&lt;Food&gt;改成Platter&lt;T: Food&gt;(然后是var food: Tinit(food: T))吗?
  • 泛型在 Swift 中是不变的——在类型系统的眼中,Platter&lt;Gouda&gt;Platter&lt;Cheese&gt; 完全不相关。如果要在它们之间进行转换,则需要编写代码来执行该转换。 Array 只是一个特例,编译器在幕后做了一些魔术,以便为您执行此转换(例如,参见 this Q&A)。
  • 很相似,但我认为不一样。这个问题是关于协议的,而这是关于子类的。此外,这个问题专门针对收藏。 Rob Napier 的回答,他展示了类型系统将如何破坏,非常好,但我正在努力将它应用于我的问题。 let platter: Platter&lt;Cheese&gt; = goudaPlatter 会如何破坏类型系统?
  • @ConfusedByCode 抱歉,我完全忘记回复你了!实际上,我最近回答了a similar Q&A here。如果这现在完全回答了您的问题,请告诉我:)

标签: swift generics subclassing


【解决方案1】:

您可以使用称为type erasure 的技术解决此问题。基本上,您创建了一个“包装器”结构,它从泛型中隐藏了底层类的详细信息。这并不理想,但它可以让您完成与您正在尝试做的事情类似的事情。

class Cheese {
    func doSomethingCheesy() {
        print("I'm cheese")
    }
}

class Gouda: Cheese {
    override func doSomethingCheesy() {
        print("I'm gouda")
    }
}

struct AnyCheese {
    let cheese: Cheese
}

class Container<T> {
    init(object: T) {
        self.object = object
    }
    let object: T
}

let cheese = Cheese()
let cheeseContainer: Container<AnyCheese> = Container(object: AnyCheese(cheese: cheese))

let gouda = Gouda()
let goudaContainer: Container<AnyCheese> = Container(object: AnyCheese(cheese: gouda))

cheeseContainer.object.cheese.doSomethingCheesy() // prints "I'm cheese"
goudaContainer.object.cheese.doSomethingCheesy()  // prints "I'm gouda"

【讨论】:

  • 我对你的答案投了赞成票,因为它很好地解释了类型擦除,我相信它会帮助找到这个问题的人。但是,我没有将其标记为已接受,因为我要求解释为什么 Swift 会这样做,而不是寻找解决方法。我认为我的问题中的 cmets 可以重新设计成一个很好的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-24
  • 1970-01-01
  • 2020-09-17
  • 2020-09-10
  • 2020-07-01
  • 1970-01-01
相关资源
最近更新 更多