我有一系列不同的结构,都实现了Equatable协议,并试图将其传递给需要collection的函数where T.Iterator.Element: Equatable。我知道如何使用类来解决此问题,只需创建一个class Vehicle: Identifiable, Equatable,然后制作Car并Tractor实现Vehicle。但是我想知道使用结构和协议是否可行?
Equatable
where T.Iterator.Element: Equatable
class Vehicle: Identifiable, Equatable
Car
Tractor
Vehicle
这是我想要做的一个人为的例子
//: Playground - noun: a place where people can play protocol Identifiable { var ID: String { get set } init(ID: String) init() } extension Identifiable { init(ID: String) { self.init() self.ID = ID } } typealias Vehicle = Identifiable & Equatable struct Car: Vehicle { var ID: String init() { ID = "" } public static func ==(lhs: Car, rhs: Car) -> Bool { return lhs.ID == rhs.ID } } struct Tractor: Vehicle { var ID: String init() { ID = "" } public static func ==(lhs: Tractor, rhs: Tractor) -> Bool { return lhs.ID == rhs.ID } } class Operator { func operationOnCollectionOfEquatables<T: Collection>(array: T) where T.Iterator.Element: Equatable { } } var array = [Vehicle]() //Protocol 'Equatable' can only be used as a generic constraint because Self or associated type requirements array.append(Car(ID:"VW")) array.append(Car(ID:"Porsche")) array.append(Tractor(ID:"John Deere")) array.append(Tractor(ID:"Steyr")) var op = Operator() op.operationOnCollectionOfEquatables(array: array) //Generic parameter 'T' could not be inferred
正如错误所指出的那样,问题是,您不能将具有“自我”或相关类型要求的协议用作实际类型,因为您会丢失有关这些要求的类型信息。在这种情况下,您将丢失==实现参数的类型信息,因为Equatable它们必须与一致类型(即Self)具有相同的类型。
==
Self
解决方案几乎总是建立一个类型的橡皮擦。在期望类型相等的情况下,如果它们的id属性相等,则可以简单地id将其存储起来并在==实现中进行比较。
id
struct AnyVehicle : Equatable { static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool { return lhs.id == rhs.id } let id : String init<T : Vehicle>(_ base: T) { id = base.id } }
(请注意,为了符合Swift命名约定,我将您的ID属性重id命名为)
ID
但是,更通用的解决方案是在类型橡皮擦中存储一个函数,该函数可以在类型转换之后Vehicle根据 其 ==实现比较两个任意符合的实例,以确保它们与创建类型橡皮擦的具体类型相同。 。
struct AnyVehicle : Equatable { static func ==(lhs: AnyVehicle, rhs: AnyVehicle) -> Bool { // forward to both lhs's and rhs's _isEqual in order to determine equality. // the reason that both must be called is to preserve symmetry for when a // superclass is being compared with a subclass. // if you know you're always working with value types, you can omit one of them. return lhs._isEqual(rhs) || rhs._isEqual(lhs) } let base: Identifiable private let _isEqual: (_ to: AnyVehicle) -> Bool init<T : Vehicle>(_ base: T) { self.base = base _isEqual = { // attempt to cast the passed instance to the concrete type that // AnyVehicle was initialised with, returning the result of that // type's == implementation, or false otherwise. if let other = $0.base as? T { return base == other } else { return false } } } }
print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Tractor(id: "foo"))) // false print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "bar"))) // false print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "foo"))) // true var array = [AnyVehicle]() array.append(AnyVehicle(Car(id: "VW"))) array.append(AnyVehicle(Car(id: "Porsche"))) array.append(AnyVehicle(Tractor(id: "John Deere"))) array.append(AnyVehicle(Tractor(id: "Steyr"))) var op = Operator() // compiles fine as AnyVehicle conforms to Equatable. op.operationOnCollectionOfEquatables(array: array)