如何将函数中泛型类型的参数约束为兄弟参数

How To constraint parameter of generic type in function, to sibling parameter

为了理解问题的起源,让我们从一些代码开始:

protocol MyProtocol {
   var val1: Int { get set }
}


struct StructA: MyProtocol {
   var val1: Int
   var structAVal: Int
}


struct StructB: MyProtocol {
   var val1: Int
   var structBVal: Int 
   var thirdProperty: Int
}

然后我有一个具有异构数组类型的结构 MyProtocol:

struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]
}

如果我要使用 Values 中的方法更改其中一个值,例如:

  struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0)]

    mutating func set<T: MyProtocol>(at index: Int, _ newValue: T) {
        arr[index] = newValue
    }
}

那就顺利了。 我面临的问题是,假设我想更改 var arr: [MyProtocol]structB 项中的 var thirdProperty: Int,我将无法这样做,因为我的 mutating func set<T: MyProtocol>(at index: Int, _ newValue: T),因为它只知道 MyProtocol 类型。

所以我解决这个问题的 2 美分是使用这样的闭包:

 mutating func set<T: MyProtocol>(at index: Int, closure: (T?) -> (T)) {
        arr[index] = closure(arr[index] as? T)
 }

这个问题是每次我调用这个方法时,我首先需要向下转换参数(从 MyProtocolStructB)。这似乎更像是一种变通办法,可能会在路上引起不必要的行为。

所以我开始思考也许有一种方法可以将泛型参数限制为类似这样的兄弟参数(伪代码):

 mutating func set<T: MyProtocol>(type: MyProtocol.Type, at index: Int, closure: (T?) -> (T)) where T == type {
        arr[index] = closure(arr[index] as? T)
}

如您所料,无法编译。

关于如何更好地处理此事的任何想法。 T.I.A

set(type:at:closure:) 方法中使用 T.Type 而不是 MyProtocol.Type

struct Values {
    var arr: [MyProtocol] = [StructA(val1: 0, structAVal: 0), StructB(val1: 0, structBVal: 0, thirdProperty: 0)]

    mutating func set<T: MyProtocol>(type: T.Type, at index: Int, closure: ((T?) -> (T?))) {
        if let value = closure(arr[index] as? T) {
            arr[index] = value
        }
    }
}

示例:

var v = Values()
v.set(type: StructB.self, at: 1) {
    var value = [=11=]
    value?.thirdProperty = 20
    return value
}

请告诉我这是否是对您要求的正确理解。

PGDev 的解决方案触及了问题的核心,但 IMO 以下的解决方案更容易使用:

enum Error: Swift.Error { case unexpectedType }
mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
                                     applying: ((inout T) throws -> Void)) throws {
    guard var value = arr[index] as? T else { throw Error.unexpectedType }
    try applying(&value)
    arr[index] = value
}

...

var v = Values()
try v.set(type: StructB.self, at: 1) {
    [=10=].thirdProperty = 20
}

= T.self 语法允许在已知类型时稍微简化一下:

func updateThirdProperty(v: inout StructB) {
    v.thirdProperty = 20
}
try v.set(at: 1, applying: updateThirdProperty)

另一种更灵活但对调用者稍微困难的方法是 returns MyProtocol 的闭包,因此更新函数可以修改类型。如果它在您的程序中确实有用,我只会添加它:

mutating func set<T: MyProtocol>(type: T.Type = T.self, at index: Int,
                                 applying: ((T) throws -> MyProtocol)) throws {
    guard let value = arr[index] as? T else { throw Error.unexpectedType }
    arr[index] = try applying(value)
}

...

try v.set(type: StructB.self, at: 1) {
    var value = [=12=]
    value.thirdProperty = 20
    return value // This could return a StructA, or any other MyProtocol
}

(非常接近 PGDev 的示例,但不需要可选。)