为了账号安全,请及时绑定邮箱和手机立即绑定

使用嵌入在结构中的接口进行反射 - 如何检测“真实”功能?

使用嵌入在结构中的接口进行反射 - 如何检测“真实”功能?

Go
温温酱 2021-10-04 15:42:25
 type A interface {     Foo() string  }  type B struct {     A     bar string  }习惯上,来自 OOP 语言的背景,这种模式对我来说“试图说”是 B 必须实现接口 A。但我现在明白“Go 是不同的”。因此,与我最初预期的编译时检查不同,这很高兴在有或没有  func (B) Foo() string { .... }展示。正如上面的问题所指出的(释义):“在结构中使用嵌入式接口非常适合您只想实现接口的 /part/ ”。据推测,这是因为这个嵌入发生的事情就像在其他所有情况下一样 - 类型 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 值的一些信息,我可以使用它来查看反射返回的返回 Method 和 func 值中不存在“真实”实现?这是否更准确地说是“匿名接口值的函数表中函数的指针为零”,或者从没有实现的带有反射的接口中提取的方法究竟发生了什么?将整个事情包装在一个 goroutine 中并尝试在 defer/panic 下运行该函数并不是答案——不仅是因为 panic/defer 的开销,而且因为该函数通常可能(如果它确实存在)有副作用我现在不想...我是否想要一个类似于编译器类型检查的运行时实现?或者有更简单的方法吗?我是否错误地考虑了这一点?
查看完整描述

3 回答

?
呼如林

TA贡献1798条经验 获得超3个赞

这个问题很老,有一些很好的答案,但没有一个提出可以做到这一点的可能性。


在提出解决方案之前:我认为确保实现不会因为无法设置嵌入式接口字段而恐慌不是你的工作。有人可以传递一个实现,该实现显式定义了panic()显式调用的方法 。您无法检测到这种情况,但是,该实现不会比nil嵌入式接口字段更好。


好的,那么如何判断一个方法是否不能被调用,因为它会因为嵌入的接口字段而导致实现不可用而导致恐慌nil?


你说你不能/不想调用该方法并从恐慌中恢复,因为如果该方法可用,这将调用它并产生副作用。


事实是我们不必调用它。我们可以通过实例(而不是类型)来引用方法,然后必须解析实际的接收者。当然,如果接收者是嵌入式接口的动态值,如果该接口是nil,解析将导致运行时恐慌,但即使嵌入式接口不是 ,该方法也不会被调用nil。请注意,这实际上是一个Method value,并且获得一个 method value 会评估并保存接收者。这种接收器评估将失败。


让我们看一个例子:


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")

}

这个程序会输出什么?只有"We got this far, b.Int is realized"。因为Int()方法是为B类型显式定义的,所以b.Int可以解析。并且因为它没有被调用,所以"B.Int() called"不会被打印。


如果我们这样做会怎样:


_ = b.Foo

由于Foo是来自B.A嵌入式接口的提升方法,并且b.A是nil,因此解析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!

试试Go Playground上的例子。


查看完整回答
反对 回复 2021-10-04
  • 3 回答
  • 0 关注
  • 166 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信