我具有以下结构:
struct A<T> {
var val: T
}
struct B<T> {
var setOfA: Set<A<T>>
}
如果一组A
可以同时具有A<Int>
和A<Float>
类型(或任何其他类型)的值,这是对泛型的错误使用吗?
如果可以,使用Any
是正确的方法吗?
如果一组A
被限制为单一类型的A<>
,那么泛型将是正确的选择吗?
答案 0 :(得分:2)
Swift是一个移动目标,所有代码都使用Xcode 9.2和Swift 4.0进行了测试。
首先,如果您希望将A<T>
项插入集合中,则该类型必须实现Hashable
协议。您可以根据需要实现此协议,但出于说明目的,一种简单的方法是要求类型参数T
为Hashable
,并通过间接访问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