为什么我无法将Protocol.Type传递给通用T.Type参数? [英] Why can't I pass a Protocol.Type to a generic T.Type parameter?
问题描述
我在这个游乐场里总结了我的问题
protocol Protocol {}
$ b $ class Class:Protocol {}
let test:Protocol.Type = Class .self
func printType(confromingClassType:Protocol.Type){$ b $ print(confromingClassType)
}
func printType< Service>(serviceType:Service .Type){
print(serviceType)
}
print(Class.self)//Class
printType(serviceType:Class.self)// class
print(test)//Class
printType(confromingClassType:test)//Class
printType(serviceType:test)//note:预期会有一个类型为'(serviceType:Service.Type)'
的参数列表test.self或type(of:test),但它们都不起作用。
所以我想我不能用一个通用参数调用一个函数作为变量?
P.Type
与 P.Protocol
有两种协议元类型。对于某些协议 P
和符合类型 C
:
- 一个
P.Protocol
描述一个协议类型本身<它唯一可以容纳的值是<$P.self
)。 - em>符合协议。它可以保存
C.self
,但不是P.self
的值,因为<协议不符合自己的要求(尽管这个规则的一个例外是任何$ c $作为
任何
都是顶级类型 a>,所以任何元类型值都可以被键入为Any.Type
;包括Any.self
)。
您遇到的问题是,对于给定的通用占位符 T
,当 T
是一些协议 P
, T.Type
是 P.Type
- 它是 P.Protocol
。
所以如果我们回到你的例子:
protocol P {}
class C :P {}
func printType< T>(serviceType:T.Type){
print(serviceType)
}
let test:P .Type = C.self
//不能调用'printType '的类型为'(serviceType:P.Type)'的参数列表'
printType(serviceType:test)
我们无法将 test
作为参数传递给 printType(serviceType:)
。为什么?因为 test
是 P.Type
;并且没有替换 T
,这使得 serviceType:
参数取<
如果我们用 P
代替 T
,该参数需要一个
P.Protocol
:
printType(serviceType:P.self)//很好,P.self是P.Protocol类型,而不是P.Type
如果我们用 类型替换 T
,比如 C
,参数需要一个 C.Type
:
printType serviceType:C.self)// C.self是C.Type类型
使用协议扩展破解
好的,所以我们知道如果我们可以用对于 T
,我们可以将 C.Type
传递给函数。我们可以在动态类型中替换 P.Type
包装吗?不幸的是,这需要一种称为开放存在< a>,目前不能直接提供给用户。
然而,当访问协议类型的成员时,Swift 确实隐含地打开了存在实例或元类型(即,它挖出运行时类型并使其以通用占位符的形式可访问)。我们可以在协议扩展中利用这个事实:
协议P {}
class C:P {}
func printType< T>(serviceType:T.Type){
print(T.self = \(T.self))
print(serviceType = \(serviceType))
}
extension P {
static func callPrintType / *< Self:P> * /(/ * _ self:Self.Type * /){
printType(serviceType:self)
}
}
let test:P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
这里有很多东西在做,所以让我们稍微解开它:
-
扩展成员
P
上的callPrintType()有一个隐含的通用占位符Self
,它被限制为P
。隐式的self
参数是使用这个占位符键入的。 当调用
callPrintType ()
在 P.Type
中,Swift隐式地挖掘了 P.Type
wrap(这是存在的开头),并用它来满足 Self
占位符。然后它将这个动态元类型传递给隐式的 self
参数。 因此, Self
将被 C
满足,然后它可以被转发到 printType
的s $ b $ $ b
当 T ==时,为什么 T.Type
不是 P.Type
P
?
T.Type
不是 P.Type
P 您会注意到上述解决方法是如何工作的,因为我们避免替换 P
为通用占位符 T
。但是为什么当用一个协议类型 P
替换 T
时,是 T.Type < code $不是
P.Type
?
好的,请考虑:
func foo< T>(_:T.Type){
let t:T.Type = T.self
print(t)
}
如果我们用 P
用于 T
?如果 T.Type
是 P.Type
,那么我们得到的是:
func foo(_:P.Type){
//无法将'P.Protocol'类型的值转换为指定的类型'P.Type '
let p:P.Type = P.self
print(p)
}
这是非法的;我们不能将 P.self
分配给 P.Type
,因为它的类型 P.Protocol ,而不是
P.Type
。
所以,结果是如果你想一个函数参数,它需要一个元类型来描述符合 P
的任何具体类型(而不仅仅是一个具体的符合类型) - 你只需要一个 P.Type
参数,而不是泛型。泛型不会为异构类型建模,这就是协议类型的用途。
这正是您使用 printType(conformingClassType:
func printType(conformingClassType:P.Type){
print(conformingClassType)
$ b printType(conformingClassType:test)//好的
你可以传递 test
给它,因为它有一个 P.Type
类型的参数。但你会注意到这意味着我们不能将 P.self
传递给它,因为它不是类型 P.Type
:
//无法将'P.Protocol'类型的值转换为期望的参数类型'P.Type'
printType(conformingClassType:P.self)
I was working with Swinject and a problem is bugging me. I have been stuck one this for almost an entire day. I suspect this is due to Swift being a statictly typed language but I'm not entirely sure.
I summarized my problem in this playground
protocol Protocol {}
class Class: Protocol {}
let test: Protocol.Type = Class.self
func printType(confromingClassType: Protocol.Type) {
print(confromingClassType)
}
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.self) // "Class"
printType(serviceType: Class.self) // "Class"
print(test) // "Class"
printType(confromingClassType: test) // "Class"
printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'"
I tried different solutions like test.self or type(of: test) but none of them work.
So I guess I can't call a function with a generic parameter provided as a variable ?
P.Type
vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P
, and a conforming type C
:
- A
P.Protocol
describes the type of a protocol itself (the only value it can hold isP.self
). - A
P.Type
describes a concrete type that conforms to the protocol. It can hold a value ofC.self
, but notP.self
because protocols don't conform to themselves (although one exception to this rule isAny
, asAny
is the top type, so any metatype value can be typed asAny.Type
; includingAny.self
).
The problem you're facing is that for a given generic placeholder T
, when T
is some protocol P
, T.Type
is not P.Type
– it is P.Protocol
.
So if we jump back to your example:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print(serviceType)
}
let test: P.Type = C.self
// Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)'
printType(serviceType: test)
We cannot pass test
as an argument to printType(serviceType:)
. Why? Because test
is a P.Type
; and there's no substitution for T
that makes the serviceType:
parameter take a P.Type
.
If we substitute in P
for T
, the parameter takes a P.Protocol
:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T
, such as C
, the parameter takes a C.Type
:
printType(serviceType: C.self) // C.self is of type C.Type
Hacking around with protocol extensions
Okay, so we've learnt that if we can substitute in a concrete type for T
, we can pass a C.Type
to the function. Can we substitute in the dynamic type that the P.Type
wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
The extension member
callPrintType()
onP
has an implicit generic placeholderSelf
that's constrained toP
. The implicitself
parameter is typed using this placeholder.When calling
callPrintType()
on aP.Type
, Swift implicitly digs out the dynamic type that theP.Type
wraps (this is the opening of the existential), and uses it to satisfy theSelf
placeholder. It then passes this dynamic metatype to the implicitself
parameter.So,
Self
will be satisfied byC
, which can then be forwarded ontoprintType
's generic placeholderT
.
Why is T.Type
not P.Type
when T == P
?
You'll notice how the above workaround works because we avoided substituting in P
for the generic placeholder T
. But why when substituting in a protocol type P
for T
, is T.Type
not P.Type
?
Well, consider:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
What if we substituted in P
for T
? If T.Type
is P.Type
, then what we've got is:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
which is illegal; we cannot assign P.self
to P.Type
, as it's of type P.Protocol
, not P.Type
.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P
(rather than just one specific concrete conforming type) – you just want a P.Type
parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's exactly what you have with printType(conformingClassType:)
:
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
You can pass test
to it because it has a parameter of type P.Type
. But you'll note this now means we cannot pass P.self
to it, as it is not of type P.Type
:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
这篇关于为什么我无法将Protocol.Type传递给通用T.Type参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!