Reflection 带嵌入结构中的接口的Go反射-如何检测;“真正的”;功能?

Reflection 带嵌入结构中的接口的Go反射-如何检测;“真正的”;功能?,reflection,interface,go,embedding,Reflection,Interface,Go,Embedding,我现在的情况与本帖中询问的情况相同: 习惯地说,从OOP语言的背景来看,这个模式对我来说“试图说”的是B必须实现接口a。但我现在明白了“Go是不同的”。因此,与我最初期望的编译时检查不同,无论是否使用 func (B) Foo() string { .... } 现在。正如上面的问题所指出的(意译):“当您只想实现/部分/接口时,在结构中使用嵌入式接口是非常好的” 据推测,这是因为此嵌入所发生的事情与其他任何情况都一样——类型B的值将具有类型a的匿名接口值,作为字段。就我个人而言,虽然我觉

我现在的情况与本帖中询问的情况相同:

习惯地说,从OOP语言的背景来看,这个模式对我来说“试图说”的是B必须实现接口a。但我现在明白了“Go是不同的”。因此,与我最初期望的编译时检查不同,无论是否使用

  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!