泛型的用法

时间:2018-06-20 21:07:15

标签: swift generics types

我具有以下结构:

struct A<T> {
    var val: T
}

struct B<T> {
    var setOfA: Set<A<T>>
}

如果一组A可以同时具有A<Int>A<Float>类型(或任何其他类型)的值,这是对泛型的错误使用吗?

如果可以,使用Any是正确的方法吗?

如果一组A被限制为单一类型的A<>,那么泛型将是正确的选择吗?

2 个答案:

答案 0 :(得分:2)

Swift是一个移动目标,所有代码都使用Xcode 9.2和Swift 4.0进行了测试。

首先,如果您希望将A<T>项插入集合中,则该类型必须实现Hashable协议。您可以根据需要实现此协议,但出于说明目的,一种简单的方法是要求类型参数THashable,并通过间接访问val来实现该协议:

struct A<T> : Hashable where T : Hashable
{
    var val : T

    var hashValue: Int { return val.hashValue }

    static func ==(lhs: A<T>, rhs: A<T>) -> Bool
    {
        return lhs.val == rhs.val
    }
}

答案的其余部分不需要上述实现,只是A<T>Hashable

Set限制为仅包含A<T>中的任何T实例,这是一个更大的挑战。解决此问题的一种方法是使用 existential类型,在这种情况下,该类型实质上允许将不同类型的值包装在另一个非泛型包装器中。 Swift有一些预定义的存在类型,特别是AnyHashable,您可以使用它们来定义B的{​​{1}}:

var

不幸的是,这并不限制该集合仅对任何struct B { var setOfA : Set<AnyHashable> = Set() ... } 持有A<T>类型的值。

T的类型不能为setOfA,因为对于某些Set<A<AnyHashable>>,它要求类型为A<T>的值才能强制转换为T和Swift不支持这一点(研究 variance 可能有助于理解原因)。

您可能想知道为什么类型不能为A<AnyHashable>。不幸的是(在这种情况下!)Set<Hashable>是具有Hashable要求的 protocol 协议,Swift在这种情况下不支持将其用作通用类型参数。 Self是一个非泛型 AnyHashable,它的类型会擦除现有类型位)其包装的值。

因此回到解决将集合限制为仅包含struct的问题。一种解决方案是将(A)隐藏在private内,并提供通用插入函数,该函数仅对任何B接受A<T>值。然后可以使用只读的计算属性将设置值本身公开。例如:

T

以下是使用上述内容的球型示例代码:

struct B
{
    // private property so only B's functions can modify the set
    private var _setOfA : Set<AnyHashable> = Set()

    // public read-only property so _setOfA cannot be changed directly
    var setOfA : Set<AnyHashable> { return _setOfA } 

    // public insert function which only accepts A<T> values, for any T
    mutating func insert<T>(_ element : A<T>) -> (Bool, A<T>)
    {
        return _setOfA.insert(element)
    }
}

这将输出:

let x = A(val: 4)        // x : A<Int>
let y  = A(val: "hello") // y : A<String>
var z = B()
print(z.insert(x)) // insert being generic accepts any A<T>
print(z.insert(y))
print(z.insert(x)) // should return (false, x) as element already added
print("\(z)")
for member in z.setOfA
{
    print("member = \(member) : \(type(of:member))")
    if let intMember = member as? A<Int>
    {
        print("   intMember = \(intMember) : \(type(of:intMember))")
    }
    else if let stringMember = member as? A<String>
    {
        print("   stringMember = \(stringMember) : \(type(of:stringMember))")
    }
}

HTH

答案 1 :(得分:1)

如果计划让结构包含特定类型的val,请使用<T: DesiredProtocol>。如果类型无关紧要,并且您的val可以是任何类型,只需将结构实现为struct A<T> {}A<T>等于A<T: Any>

请注意,如果您打算让setOfA包含不同类型的A,则示例将无法编译。

  

Set中的元素必须是可哈希的。

     

B<T>中,T仅代表一种类型。因此,您不能在Set<A<T>>中存储其他类型。

由于此要求,struct A<T>必须符合Hashable协议,并且B的实现必须更改。

struct A<T>: Hashable {
    var val: T

    // Implementation of Hashable
}

/// This struct should not be generic. It would constrain its setOfA to only contain the same type. 
/// By removing the generics, and use Any instead, you let each element have its own type.
struct B {
    var setOfA: Set<A<Any>>
}

var set: Set<A<Any>> = Set()
set.insert(A(val: 0 as Int))
set.insert(A(val: 1 as Float))

let b = B(setOfA: set)

编辑:

由于您正在寻找一种在创建时将通用A<T>插入A<Any>而不将其类型指定为A<Any>的方法,因此我提出了两种方法不同的方式:

let v1 = A(val: 5)
var v2 = A(val: 3)

aSet.insert(A(val: v2.val))
aSet.insert(withUnsafePointer(to: &v3, { return UnsafeRawPointer($0) }).assumingMemoryBound(to: A<Any>.self).pointee)

编辑

如果是在编写时,B的{​​{1}}仅具有单一类型的setOfA,则可以选择是否使用泛型,这是上下文的选择。问题在于,A<>是否需要通用。 struct B可以是许多不同任务的值类型,而struct A则不需要。如果struct B更具体,您宁愿写:

B

如上所述,如果您打算让同一集合包含struct B { var setOfA: Set<A<Int>> } A<Int>,那么A<Float>是正确的方法。

如果var setOfA: Set<A<Any>>有一个非常特定的用例,而您实际上并不需要通用,则应更改struct A的类型。您的代码应易于理解且全面。我会推荐以下解决方案之一:

val