Swift:根据协议一致性的可能性重载或路由到泛型方法?

rpppsulh  于 2023-04-04  发布在  Swift
关注(0)|答案(3)|浏览(128)

在泛型处理链中,我有一个处理结果的方法,如果泛型类型碰巧符合协议,我想以不同的方式处理。
我想重载基于协议一致性的方法。这种切换方法可以通过编译器(如果基于协议一致性的切换则不能)。
不幸的是,当处理链中的一个方法还没有为该协议类型化时,编译器从不使用更具体的重载。
下面是一个例子:

protocol MyProtocol {
    var protocolString: String { get }
}

struct SpecialResult: MyProtocol {
    let protocolString: String = "special MyProtocol"
}

func preProcess<T>(result: T) {
    // Process chain for any kind of T
    print("PreProcess: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
    process(result: result)
}

func process<T>(result: T) where T: MyProtocol { // Doesn't matter if I use process<T: MyProtocol>
    // process specifically for MyProtocol
    print("I'm processing a \(result.protocolString) result!")
}

func process<T>(result: T) {
    // process generically for T
    print("I'm processing a result.")
    print("Process: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
}

let result = SpecialResult()
preProcess(result: result)
process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a result.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol result!

我希望它打印特殊的MyProtocol行两次。
有什么解决方案吗?即使没有方法重载,我似乎也不能让路由工作。
我可以跳过重载并使用两个不同的方法名,在if T.self is MyProtocol.Type后面调用每个方法,但编译器仍然抱怨true case需要符合MyProtocol,如果它是true case,它显然必须符合!
我可以强制转换result as? MyProtocol,但这样我就失去了它的底层类型,这是我在整个处理链中所需要的。我真的需要一种方法来做类似result as? T: MyProtocol的事情,但我似乎找不到一种方法来...

qnakjoqk

qnakjoqk1#

你在这里有一些错误。最基本的是这个结构:

T.self is MyProtocol.Type

那是不对的。你的意思是:

result is MyProtocol

或者你可以用途:

T.self is MyProtocol

(but您可能应该使用result版本)
你使用的东西大概是“T是MyProtocol类型的子类型”,这意味着“T是一个元类型”,这真的不是你的意思(事实上,我不确定它有什么有用的意思)。
所以这给你:

func preProcess<T>(result: T) {
    // Process chain for any kind of T
    print("PreProcess: T conforms to MyProtocol: \(result is MyProtocol)") // always prints true
    process(result: result)
}

由于你想在运行时考虑类型,你需要使用运行时检查(即“if”语句,而不是泛型)。我不是100%清楚你想从中得到什么,但我希望它沿着这些路线:

func process<T>(result: T) {
    let resultType: String
    if let myProtocol = result as? MyProtocol {
        resultType = myProtocol.protocolString
    } else {
        resultType = ""
    }

    print("I'm processing a \(resultType) result.")
    print("Process: T conforms to MyProtocol: \(result is MyProtocol)")
}

这就产生了:

PreProcess: T conforms to MyProtocol: true
I'm processing a special MyProtocol result.
Process: T conforms to MyProtocol: true
I'm processing a special MyProtocol result.
Process: T conforms to MyProtocol: true

当然,你也可以用一个紧凑的??来重写这个大的if语句:

let resultType = (result as? MyProtocol)?.protocolString ?? ""

或者,您可以将处理过程拆分到if中,而不是使用字符串值或其他方式。
你也可以这样写(这可能是你的意思):

func process(result: MyProtocol) {
    print("I'm processing a \(result.protocolString) result!")
}

func process<T>(result: T) {
    // process generically for T
    if let result = result as? MyProtocol {
        process(result: result)
    } else {
        print("I'm processing a result.")
    }
}

我倾向于对重载使用相同的名称有点小心,像这样,它可能会有歧义(我可能会命名第一个process(myProtocol:))。它完全可以工作,并且非常有用(请参阅stdlib中Encoder容器中的各种encode方法)。但它也可能有点混乱,所以应该非常小心地使用它。
但最重要的是matt的回答。泛型不是动态分派。它们是在编译时基于编译时信息100%确定的。一般来说,泛型重载应该只用于提高性能(支持BidirectionalCollections比Collections更好的算法)。它们不应该用于改变行为。这条路很少会以你期望的方式工作。

hxzsmxv2

hxzsmxv22#

你似乎认为这里会发生某种动态分派,但事实并非如此,泛型不是这样的。
泛型是在编译时解析的。运行时不会根据参数的实时检查来覆盖编译器的行为。在preProcess中,编译器只知道这个东西是一个T,它可以是任何类型。所以它选择通用实现。
如果你想在代码中检查这是什么类型的东西,并向下转换以告诉编译器,很好,你可以这样做。但是运行时不会为你做这件事。

xdnvmnnf

xdnvmnnf3#

当你写:

let result = SpecialResult()
preProcess(result: result)

编译器知道result的类型,但在func preProcess<T>(result:)中,T可以是任何的东西,因此它将始终调用非专用版本process<T>(result:)
帮助编译器保留类型信息的一种方法是添加Processor协议:

protocol Processor {
    associatedtype T
    func description(for result: T) -> String
}

extension Processor {
    func preProcess(result: T) {
        // Process chain for any kind of T
        print("PreProcess: T conforms to MyProtocol: \(T.self is MyProtocol.Type)") // always prints true
        process(result: result)
    }

    func process(result: T) {
        // process generically for T
        print("I'm processing a \(description(for: result)).")
        print("Process: T conforms to MyProtocol: \(result is MyProtocol)") // always prints true
    }
}

这个想法是让符合类型来处理结果,这里它只是描述,但它可以是任何东西。
现在有两个选择

  • 所有可能结果的泛型类:
struct ResultProcessor<T>: Processor {
    let result: T
    
    func description(for result: T) -> String {
        if let myProtocol = result as? MyProtocol {
            return myProtocol.protocolString
        } else {
            return "result"
        }
    }
}

使用方法:

let result = SpecialResult()
let processor = ResultProcessor(result: result)
processor.preProcess(result: result)
processor.process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true

或者为每个已知类型的结果指定一个专用类:

struct SpecialResultProcessor: Processor {
    let result: SpecialResult
    
    func description(for result: SpecialResult) -> String {
        "special \(result.protocolString) result"
    }
}

使用方法:

let specialResultProcessor = ResultProcessor(result: result)
specialResultProcessor.preProcess(result: result)
specialResultProcessor.process(result: result)

// PreProcess: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true
// I'm processing a special MyProtocol.
// Process: T conforms to MyProtocol: true

相关问题