简体   繁体   English

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

[英]Go reflection with interface embedded in struct - how to detect “real” functions?

The situation I have now is the same as was asked about in this thread: Meaning of a struct with embedded anonymous interface?我现在的情况与在此线程中询问的情况相同: 具有嵌入式匿名接口的结构的含义?

  type A interface {
     Foo() string
  }

  type B struct {
     A
     bar string
  }

Idiomatically, coming from a backround in OOP languages, what it looks like this pattern is "trying to say" to me is that B must implement interface A. But I get by now that "Go is different".习惯上,来自 OOP 语言的背景,这种模式对我来说“试图说”是 B 必须实现接口 A。但我现在明白“Go 是不同的”。 So, rather than the compile-time check I expected at first, this is happy to compile with or without a因此,与我最初预期的编译时检查不同,这很高兴在有或没有

  func (B) Foo() string { .... }

present.展示。 As the above question points out (paraphrased): "using embedded interfaces in structs is great for when you only want to implement /part/ of an interface".正如上面的问题所指出的(释义):“在结构中使用嵌入式接口非常适合您只想实现接口的 /part/ ”。

Presumably, this is because what is happening with this embed is just like in every other case - a value of type B would have an anonymous interface value of type A, as a field.据推测,这是因为这个嵌入发生的事情就像在所有其他情况下一样 - 类型 B 的值将具有类型 A 的匿名接口值作为字段。 Personally while I find that orthogonality comforting, I also find it confusing that the reflection package would then let me get methods of A directly from B's type this way, and not error/nil if no method with receiver B is present.就我个人而言,虽然我发现正交性令人欣慰,但我也发现反射包会让我以这种方式直接从 B 的类型获取 A 的方法,如果不存在接收器 B 的方法,则不会出错/零。 But - this question isn't about the thinking behind that - it is about how that interface value is initialized after b := 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")
  }
}

When this is run, it causes a horrible panic当它运行时,它会引起可怕的恐慌

 HAS IT: func
 panic: runtime error: invalid memory address or nil pointer dereference

... or doesn't - depending on if the compiler/runtime was able to find the above method. ...或不- 取决于编译器/运行时是否能够找到上述方法。 So: How can I detect that situation before I trigger it?那么:如何在触发之前检测到这种情况?

That is - is there something about the bMeth value I can use to see that there is no "real" implementation present in the reflection-returned returned Method and func values?也就是说 - 是否有关于 bMeth 值的一些信息,我可以使用它来查看反射返回的返回 Method 和 func 值中不存在“真实”实现? Is that more precisely something like "is the pointer to the function in the function table of the anonymous interface value in zero", or what exactly is going on with methods you pull from an interface with reflection where there is no implementation?这是否更准确地说是“匿名接口值的函数表中函数的指针为零”,或者从没有实现的带有反射的接口中提取的方法究竟发生了什么?

Wrapping the whole thing in a goroutine and attempting to run the function under defer/panic isn't the answer - not only because of the expense of the panic/defer but because the function in general might, if it does exist, have side effects I don't want right now...将整个事情包装在一个 goroutine 中并尝试在 defer/panic 下运行该函数并不是答案——不仅是因为 panic/defer 的开销,而且因为该函数通常可能(如果它确实存在)有副作用我现在不想...

Do I want something like a run-time implementation that mirrors the compiler's type check?我是否想要一个类似于编译器类型检查的运行时实现? Or is there an easier way?或者有更简单的方法吗? Am I thinking about this incorrectly?我是否错误地考虑了这一点?

Above example in a Go playground上面在 Go 游乐场中的示例

You needn't reflection to my mind你不需要在我脑海里反省

method_in_table := B.Foo
fmt.Printf("%T \n", method_in_table)

will output you会输出你

func(main.B) string

Interface type A initialized at predeclared nil which has no dynamic type接口类型 A 在预先声明的 nil 处初始化,没有动态类型

var a A
if a==nil{
    fmt.Printf("It's nil")
}
a.Foo()

will give you same error.会给你同样的错误。 So practical check can be just所以实际检查可以只是

if b.A != nil { b.Foo()}

This question is old with some good answers, but none presents the possibility that this can be done.这个问题很老,有一些很好的答案,但没有一个提出可以做到这一点的可能性。

Before presenting the solution: I think it's not your job to make sure the implementation does not panic because it fails to set an embedded interface field.在提出解决方案之前:我认为确保实现不会因为无法设置嵌入式接口字段而恐慌不是你的工作。 Someone could pass an implementation which explicitly defines the methods in which panic() is called explicitly.有人可以传递一个实现,该实现显式定义了显式调用panic()的方法。 You could not detect that case, yet, that implementation wouldn't be any better than a nil embedded interface field.您无法检测到这种情况,但是,该实现不会比nil嵌入式接口字段更好。

OK, so how to tell if a method cannot be called because it would panic due to the implementation not being available because the embedded interface field is nil ?好的,那么如何判断一个方法是否不能被调用,因为它会因为嵌入的接口字段为nil导致实现不可用而导致恐慌?

You said you can't / don't want to call the method and recover from a panic because if the method is available, this would call it and have its side effect.你说你不能/不想调用该方法并从恐慌中恢复,因为如果该方法可用,这将调用它并产生副作用。

The fact is that we don't have to call it.事实是我们不必调用它。 We can just refer to the method via an instance (not type), and then the actual receiver has to be resolved.我们可以通过实例(而不是类型)来引用方法,然后必须解析实际的接收者。 Of course if the receiver would be the dynamic value of an embedded interface, and if that interface is nil , the resolving will cause a runtime panic, but the method will not be called even if the embedded interface is not nil .当然,如果接收者是嵌入式接口的动态值,并且该接口为nil ,则解析将导致运行时恐慌,但即使嵌入式接口不是nil也不会调用该方法。 Note that this is in fact a Method value , and obtaining a method value evaluates and saves the receiver.请注意,这实际上是一个Method value ,并且获得一个 method value 会评估并保存接收者。 This receiver evaluation is what will fail.这种接收器评估将失败。

Let's see an example:让我们看一个例子:

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

What will this program output?这个程序会输出什么? Only "We got this far, b.Int is realized" .只有"We got this far, b.Int is realized" Because the Int() method is explicitly defined for the B type, and so b.Int can be resolved.因为Int()方法是为B类型显式定义的,所以b.Int可以解析。 And since it's not called, "B.Int() called" will not be printed.因为它没有被调用, "B.Int() called"不会打印"B.Int() called"

What if we do this:如果我们这样做会怎样:

_ = b.Foo

Since Foo is a promoted method from BA embedded interface, and bA is nil , resolving b.Foo will fail at runtime, and produce a runtime error, something like this:由于FooBA嵌入式接口的提升方法,而bAnil ,解析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

But we can recover from this:但我们可以从中恢复:

defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered:", r)
        fmt.Println("This means b.Foo is not realized!")
    }
}()
_ = b.Foo

This will output:这将输出:

Recovered: runtime error: invalid memory address or nil pointer dereference
This means b.Foo is not realized!

Try the examples on the Go Playground .试试Go Playground上的例子。

Let me put my two cents in, after you've already received good answers for your question.在你的问题已经得到很好的答案之后,让我投入我的两分钱。

Presumably, this is because what is happening with this embed is just like in every other case - a value of type B would have an anonymous interface value of type A, as a field.据推测,这是因为这个嵌入发生的事情就像在所有其他情况下一样 - 类型 B 的值将具有类型 A 的匿名接口值作为字段。

You've basically solved the problem here.你已经基本上解决了这里的问题。 This is just a field, but because it's anonymous all its methods are being promoted and you can use them directly on the struct.这只是一个字段,但因为它是匿名的,所以它的所有方法都被提升,你可以直接在结构上使用它们。 This is not only related to interfaces, but the problem you've pointed to exists within ordinary structures as well:这不仅与接口有关,而且您指出的问题也存在于普通结构中:

package main

type A struct {
}

func (a A) Foo() {
}

type B struct {
    *A
}

func main() {
    B{}.Foo()
}

This will cause panic.这会引起恐慌。 I believe this is expected: we're saying B embeds *A , but then leave it uninitialised, so what am I thinking?我相信这是意料之中的:我们说B嵌入了*A ,然后让它未初始化,那么我在想什么? We could try to find an analogy here with, for example, C++ and find out it is similar to a null pointer in C++ – how do we deal with it there?我们可以尝试在这里找到一个类比,例如,C++ 并发现它类似于 C++ 中的空指针——我们如何处理它? We either expect it to be non-null (by a contract) or need to check before using.我们要么期望它是非空的(通过合同),要么需要在使用前检查。 The latter it what Uvelichitel suggested in the accepted answer and it's by no means correct and there is no better solution I think.后者是 Uvelichitel 在接受的答案中建议的,它绝不是正确的,我认为没有更好的解决方案。 Although it's not very plausible.虽然不太靠谱。 We do expect the caller to know the method they're calling is a promoted method of an anonymous field which is a pointer (or interface) type and as such can be nil.我们确实希望调用者知道他们正在调用的方法是匿名字段的提升方法,该字段是指针(或接口)类型,因此可以为零。 As an author of such code I would either need to make sure it's never nil (contract) or state it clearly in documentation that a caller needs to check it (but why would I embed this type then instead of having normal field, I'm not sure).作为此类代码的作者,我需要确保它永远不会为零(合同)或在文档中明确说明调用者需要检查它(但为什么我要嵌入这种类型而不是普通字段,我是没有把握)。

It bothers me with interfaces though, because looking back at your example and making A an interface, we have a following problem:不过,它让我对接口感到困扰,因为回顾您的示例并将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()
    }
}

Whoops, panic.哎呀,恐慌。 I explicitly don't use reflect package here to indicate your problem exists within "normal" language usage.我在这里明确不使用反射包来表明您的问题存在于“正常”语言使用中。 I have an interface object b and want to check whether it implements interface A .我有一个接口对象b并想检查它是否实现了接口A The answer is yes, but I'm getting panic.答案是肯定的,但我越来越恐慌。 Who is to blame?谁是罪魁祸首? I would feel much more comforting saying the creator of object behind the interface b who advertise some functionality, but don't care to provide the implementation.如果说接口b背后的对象创建者宣传了一些功能,但并不关心提供实现,我会感到更加安慰。 As such I would like it to call a bad practice or at least force it to be clearly stated in the documentation rather than assuming ok in the above type assertion means actually ok.因此我想它称之为一种不好的做法,或至少迫使它在文件中清楚地说明,而不是假设ok的上述类型的断言手段实际确定。

It's getting too long and off topic I think.我认为它变得太长而且离题了。 My answer to your question is then a mixture of already given answers: directly check A is not null and if it's not possible (you don't know the exact field promoting the method), hope for the best and blame someone else.我对你的问题的回答是已经给出的答案的混合:直接检查A不为空,如果不可能(你不知道推广该方法的确切领域),希望最好并责怪别人。

I don't think this is possible.我不认为这是可能的。 From what I can see in reflect 's documentation and code , there is no way to know, whether a method is defined on the type or promoted .从我在reflect的文档和代码中看到的,无法知道方法是在类型上定义的还是提升的 Seems like panic-recover is the best you can do here.似乎恐慌恢复是您在这里可以做的最好的事情。

There are 3 questions here.这里有3个问题。

  1. An embedded interface does not mean "implements A".嵌入式接口并不意味着“实现 A”。 It's exactly the same as embedding any other type of object.这与嵌入任何其他类型的对象完全相同。 If you want to implement A, just make a method: func (b B) Foo() string .如果要实现 A,只需创建一个方法: func (b B) Foo() string

    When you say:当你说:

    using embedded interfaces in structs is great for when you only want to implement /part/ of an interface当您只想实现接口的 /part/ 时,在结构中使用嵌入式接口非常有用

    That does work, but you have to make sure to create the object properly.这确实有效,但您必须确保正确创建对象。 Think of it like wrapping an existing object:把它想象成包装一个现有的对象:

     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} }

    I'm not sure how Go does it internally, but conceptually it's as if it creates a Close method for you:我不确定 Go 在内部是如何做到的,但从概念上讲,它好像为您创建了一个Close方法:

     func (mrc *MyReadCloser) Close() error { return mrc.ReadCloser.Close() }
  2. The panic is because A is nil .恐慌是因为A nil If you had:如果你有:

     type concrete string func (c concrete) Foo() string { return string(c) } func main() { b := B{A: c("test")} // etc... }

    It would work.它会工作。 In other words when you call:换句话说,当你打电话时:

     bMeth.Func.Call([]reflect.Value{reflect.ValueOf(B{})})

    That's:那是:

     B{}.Foo()

    Which is:这是:

     B{}.A.Foo()

    And A is nil so you get a panic. Anil所以你会感到恐慌。

  3. As to the question about how to get only the methods directly implemented by an object (not methods implemented by an embedded field), I wasn't able to see a way using the reflect library.关于如何仅获取对象直接实现的方法(而不是嵌入字段实现的方法)的问题,我无法看到使用reflect库的方法。 MethodByName gives no indication: MethodByName没有给出任何指示:

     <func(main.B) string Value>

    Internally that's basically a function like this:在内部,这基本上是一个这样的函数:

     func(b B) string { return bAFoo() }

    And I don't think there's anything in reflect that allows you to peer into the internals of a function.我认为在reflect中没有任何内容可以让您查看函数的内部结构。 I tried looping over the fields, grabbing their methods and comparing the two, but that doesn't work either.我尝试遍历字段,获取它们的方法并比较两者,但这也不起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM