Reflection 带嵌入结构中的接口的Go反射-如何检测;“真正的”;功能?
我现在的情况与本帖中询问的情况相同: 习惯地说,从OOP语言的背景来看,这个模式对我来说“试图说”的是B必须实现接口a。但我现在明白了“Go是不同的”。因此,与我最初期望的编译时检查不同,无论是否使用Reflection 带嵌入结构中的接口的Go反射-如何检测;“真正的”;功能?,reflection,interface,go,embedding,Reflection,Interface,Go,Embedding,我现在的情况与本帖中询问的情况相同: 习惯地说,从OOP语言的背景来看,这个模式对我来说“试图说”的是B必须实现接口a。但我现在明白了“Go是不同的”。因此,与我最初期望的编译时检查不同,无论是否使用 func (B) Foo() string { .... } 现在。正如上面的问题所指出的(意译):“当您只想实现/部分/接口时,在结构中使用嵌入式接口是非常好的” 据推测,这是因为此嵌入所发生的事情与其他任何情况都一样——类型B的值将具有类型a的匿名接口值,作为字段。就我个人而言,虽然我觉
func (B) Foo() string { .... }
现在。正如上面的问题所指出的(意译):“当您只想实现/部分/接口时,在结构中使用嵌入式接口是非常好的”
据推测,这是因为此嵌入所发生的事情与其他任何情况都一样——类型B的值将具有类型a的匿名接口值,作为字段。就我个人而言,虽然我觉得正交性令人欣慰,但我也感到困惑的是,反射包会让我通过这种方式直接从B的类型中获取A的方法,而不是错误/零(如果没有接收器B的方法)。但是-这个问题不是关于它背后的思想-而是关于在b:=b{}
之后如何初始化接口值:
func main() {
bType := reflect.TypeOf(B{})
bMeth, has := bType.MethodByName("Foo")
if has {
fmt.Printf("HAS IT: %s\n",bMeth.Type.Kind())
res := bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})})
val := res[0].Interface()
fmt.Println(val)
} else {
fmt.Println("DOESNT HAS IT")
}
}
当它运行时,会引起可怕的恐慌
HAS IT: func
panic: runtime error: invalid memory address or nil pointer dereference
。。。或者没有-取决于编译器/运行时是否能够找到上述方法。所以:我如何在触发之前检测到这种情况?
也就是说,关于bMeth值,我可以用它来确定反射返回的方法和func值中没有“真正”的实现吗?更确切地说,是“匿名接口的函数表中指向函数的指针的值为零”,还是从没有实现的反射接口中提取的方法的具体情况
将整件事情包装在goroutine中,并尝试在defer/panic下运行函数并不是答案-不仅因为panic/defer的代价,而且因为如果函数确实存在,通常可能会产生我现在不想要的副作用
我是否需要类似于镜像编译器类型检查的运行时实现?还是有更简单的方法?我想得不对吗
我认为这是不可能的。从我在
中看到的反映
的文档中,无法知道方法是在类型上定义的还是在类型上定义的。看来恐慌恢复是你在这里能做的最好的了。你不必在我的脑海里思考
method_in_table := B.Foo
fmt.Printf("%T \n", method_in_table)
将输出您
func(main.B) string
接口类型A初始化为预声明的nil,它没有动态类型
var a A
if a==nil{
fmt.Printf("It's nil")
}
a.Foo()
会给你同样的错误。因此,实际检查可以
if b.A != nil { b.Foo()}
这里有三个问题
func(b)Foo()string
当你说:
在结构中使用嵌入式接口非常适合只需要
实现/部分/接口
这确实有效,但必须确保正确创建对象。将其视为包装现有对象:
type MyReadCloser struct {
io.ReadCloser
}
func (mrc *MyReadCloser) Read(p []byte) (int64, error) {
// do your custom read logic here
}
// you get `Close` for free
func main() {
// assuming we have some reader
var rc io.ReadCloser
// you have to build the object like this:
myReader := MyReadCloser{rc}
}
我不确定如何在内部执行,但从概念上讲,它似乎为您创建了一个Close
方法:
func (mrc *MyReadCloser) Close() error {
return mrc.ReadCloser.Close()
}
A
是nil
。如果你有:
type concrete string
func (c concrete) Foo() string {
return string(c)
}
func main() {
b := B{A: c("test")}
// etc...
}
这会奏效的。换句话说,当你打电话时:
bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})})
那就是:
B{}.Foo()
即:
B{}.A.Foo()
而且A
是nil
,所以你会感到恐慌reflect
库的方法MethodByName
未给出任何指示:
<func(main.B) string Value>
我不认为reflect
中有任何东西可以让你窥探函数的内部。我试着在字段上循环,抓住它们的方法并比较两者,但这也不起作用等你的问题得到很好的答案后,我再给你两分钱 据推测,这是因为此嵌入所发生的事情与其他任何情况都一样——类型B的值将具有类型a的匿名接口值,作为字段 你基本上解决了这里的问题。这只是一个字段,但因为它是匿名的,所以它的所有方法都将被提升,您可以直接在结构上使用它们。这不仅与接口有关,而且您指出的问题也存在于普通结构中:
package main
type A struct {
}
func (a A) Foo() {
}
type B struct {
*A
}
func main() {
B{}.Foo()
}
这会引起恐慌。我相信这是意料之中的:我们说的B
嵌入*A
,但之后不进行初始化,那么我在想什么呢?我们可以在这里找到一个类似的例子,例如C++,并发现它类似于C++中的空指针——我们如何处理它呢?我们要么期望它是非空的(根据合同),要么需要在使用前进行检查。后者是Uvelichitel在被接受的答案中提出的,它绝对不正确,我认为没有更好的解决方案。虽然这不太可能。我们确实希望调用方知道他们调用的方法是匿名字段的升级方法,该字段是指针(或接口)类型,因此可以为nil。作为这类代码的作者,我要么需要确保它永远不会为nil(contract),要么在文档中明确说明调用者需要检查它(但我不确定为什么我要嵌入这种类型而不是普通字段)
不过,接口让我很困扰,因为回顾一下您的示例,并将A
作为一个接口,我们有以下问题:
package main
import "fmt"
type A interface {
Foo()
}
type B struct {
A
}
func main() {
var b interface{}
b = &B{}
// Nicely check whether interface is implemented
if a, ok := b.(A); ok {
a.Foo()
}
}
哎呀,恐慌。我在这里明确地不使用reflect包来表示您的问题存在于“正常”语言使用中。我有一个接口对象bpackage main
type A struct {
}
func (a A) Foo() {
}
type B struct {
*A
}
func main() {
B{}.Foo()
}
package main
import "fmt"
type A interface {
Foo()
}
type B struct {
A
}
func main() {
var b interface{}
b = &B{}
// Nicely check whether interface is implemented
if a, ok := b.(A); ok {
a.Foo()
}
}
type A interface {
Foo() string
}
type B struct {
A
}
func (b B) Int() int {
fmt.Println("B.Int() called")
return 0
}
func main() {
b := B{}
_ = b.Int
fmt.Println("We got this far, b.Int is realized")
}
_ = b.Foo
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47d382]
goroutine 1 [running]:
main.main()
/tmp/sandbox877757882/prog.go:24 +0x2
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
fmt.Println("This means b.Foo is not realized!")
}
}()
_ = b.Foo
Recovered: runtime error: invalid memory address or nil pointer dereference
This means b.Foo is not realized!