首页 > 解决方案 > 在保证无损的情况下,Swift 是否有任何隐式类型转换机制?

问题描述

我试图找出 Swift 是否可以像 C# 和其他语言一样支持类型之间的隐式转换,而这些类型已知是完全兼容和无损的。ExpressibleByXxxLiteral协议似乎很接近,但我不认为它们实际上是我所追求的。更多内容在最后。

但是,现在,请考虑以下DistanceUnit枚举。这是我创建的,目的是让我的 API 的用户ARKit——它基于米——以他们选择的任何单位指定值。如果他们想要在他们的增强场景中将某些东西移动两英寸,他们不必手动进行数学计算。他们只是使用moveBy(x: .inches(2))并且枚举通过其公开的meters属性自动转换它,并将其传递给期望它们的Apple API。

简而言之,将其视为 SceneKit 中仪表的用户友好、美化类型别名。

enum DistanceUnit {

    case meters(SCNFloat)
    case centimeters(SCNFloat)
    case millimeters(SCNFloat)
    case feet(SCNFloat)
    case inches(SCNFloat)
    case points(SCNFloat)

    var meters:SCNFloat {

        switch self {
            case let .meters(meters)  : return meters
            case let .centimeters(cm) : return cm     * 0.01
            case let .millimeters(mm) : return mm     * 0.001
            case let .feet(feet)      : return feet   * 0.3048
            case let .inches(inches)  : return inches * 0.0254
            case let .points(points)  : return (points / 72) * Self.inch.meters
        }
    }

    // Presets
    static let meter      : DistanceUnit = .meters(1)
    static let centimeter : DistanceUnit = .centimeters(1)
    static let millimeter : DistanceUnit = .millimeters(1)
    static let foot       : DistanceUnit = .feet(1)
    static let inch       : DistanceUnit = .inches(1)
    static let point      : DistanceUnit = .points(1)
}

注意:由于 SceneKit 的类型SCNVector3基于平台的不同类型,因此我创建了SCNFloat我的 API 标准化的类型别名,因此无论平台如何它都可以正常工作,而无需在我的代码中进行这些编译器检查,因为它在类型别名处发生一次定义。

#if os(iOS)
    typealias SCNFloat = Float
#else
    typealias SCNFloat = CGFloat
#endif

此外,由于这种类型最终代表仪表,为了让用户更简单,特别是在将默认值等内容添加到自己的函数时,我还实现了ExpressibleByIntegerLiteraland ExpressibleByFloatLiteral,就像这样......

extension DistanceUnit : ExpressibleByIntegerLiteral {

    init(integerLiteral value: Int) {
        self = .meters(SCNFloat(value))
    }
}

extension DistanceUnit : ExpressibleByFloatLiteral {

    init(floatLiteral value: Float) {
        self = .meters(SCNFloat(value))
    }
}

回到问题...

我讨厌在我的 API 内部,value.meters在传递时我总是必须这样做DistanceUnit。我已经知道这个枚举是通过 a 表达的米的美化类型别名SCNFloat,那么为什么我总是必须明确地输入它呢?

我试图找出的是是否有可能以某种方式装饰这种类型,以便它可以直接传递给任何期望SCNFloat像 C# 这样的语言一样的 API。

C#方式

现在在 C# 中,这很容易使用隐式转换运算符。SCNFloat这是和之间隐式转换的示例DistanceUnit(从技术上讲,我在这里混合了一些 Swift 伪代码来说明这一点,因为 C# 没有具有关联值的枚举,但你明白了)......

public static implicit operator DistanceUnit(SCNFloat value) => .meters(value);
public static implicit operator SCNFloat(DistanceUnit units) => units.meters;

上面的第一行说“任何接受 a 的 APIDistanceUnit现在都可以直接接受 a SCNFloat。传递 aSCNFloat时,编译器会自动将该值插入到.meters(x)枚举案例中,然后该枚举值最终传递给期望DistanceUnit.'的接收者。

相反,第二行说'任何接受 a 的 APISCNFloat现在都可以DistanceUnit直接接受 a。传递 aDistanceUnit时,编译器将自动提取.meters属性,该属性已经是一种类型SCNFloat,并将简单地将该值传递给期望 a 的接收者SCNFloat

简单、可预测且完全无损。

注意:上面我已经定义了两种方式的转换——从SCNFloatDistanceUnit和反之亦然。但是,如果没有意义,您不必同时指定两者。

例如,期望 a 的东西Float可以很容易地进行转换Int,因为这样的转换是无损的,但是期望 a 的东西Int不一定会在Float不丢失一些数据的情况下进行。

在这些情况下,您可以将其定义为单向,或者您仍然可以将它们定义为双向,但您将使用explicit而不是标记有损方向implicit。这使得用户必须显式地将一种类型转换为另一种类型,警告他们这可能是一个有损操作,但由他们来进行调用。如果数据不能保证 100% 无损,则永远不要使用。implicit

回到斯威夫特维尔...

这些ExpressibleByXxxLiteral协议起初看起来像是成功了一半,但实际上,我认为这是一条红鲱鱼,因为它实际上并没有在运行时进行转换。提示在名称中...'literal',意思是在代码中按字面意思输入的东西,而不是Xxx. 游乐场似乎也支持这一理论。这让我相信,虽然对于一些简单的类型化文字很方便,但这实际上并不是我在这里所追求的解决方案。

那么,当您知道一种类型与另一种类型完全兼容时,Swift 是否具有任何此类特性/功能,无论是单向还是双向的?

标签: c#swifttype-conversionimplicit-conversion

解决方案


推荐阅读