当前位置: 首页 > 面试题库 >

无论接收器类型如何,接口{}上的动态调用方法

公孙黎昕
2023-03-14
问题内容

我正在使用Go编写的模板系统,这意味着它需要自由使用该reflect软件包。在这种特定情况下,我需要能够在上动态调用方法interface{}。奇怪的是,只要我的数据是已知类型,反射逻辑就可以正常工作,但是如果数据是type则不能interface{}

在下面的例子中可以看到,在逻辑main()Pass()是相同的。唯一的区别是数据是内部的已知类型还是已知类型interface{}

播放:http://play.golang.org/p/FTP3wgc0sZ

package main

import (
    "fmt"
    "reflect"
)

type Test struct {
    Start string
}

func (t *Test) Finish() string {
    return t.Start + "finish"
}

func Pass(i interface{}) {
    _, ok := reflect.TypeOf(&i).MethodByName("Finish")
    if ok {
        fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0])
    } else {
        fmt.Println("Pass() fail")
    }
}

func main() {
    i := Test{Start: "start"}

    Pass(i)
    _, ok := reflect.TypeOf(&i).MethodByName("Finish")
    if ok {
        fmt.Println(reflect.ValueOf(&i).MethodByName("Finish").Call([]reflect.Value{})[0])
    } else {
        fmt.Println("main() fail")
    }
}

执行此代码后,我们得到以下结果

Pass() fail
startfinish

这意味着我的动态调用方法的方法可以正常工作,除非在我的对象当前位于的情况下interface{}

相反,如果我不使用指针接收器并通过,i那么它将按预期工作。

播放:http://play.golang.org/p/myM0UXVYzX

这使我相信我的问题是,&i当i()是时,我无法访问其地址interface{}。我已经检查了反射包并测试了诸如和的东西reflect.Value.Addr()reflect.PtrTo()但是我都无法按照我需要的方式工作。我的直觉是,这与以下事实有关interface{}:根据定义,an
是参考对象。


问题答案:

感谢@Jeremy Wall,我相信我能够解决我的问题。基本问题是在上调用动态命名方法interface{}。有4种情况。

  1. interface{} 基础数据是价值,接收者是价值
  2. interface{} 基础数据是指针,接收者是值
  3. interface{} 基础数据是值,接收者是指针
  4. interface{} 基础数据是指针,接收者是指针

使用反射,我们可以确定接口的基础值。然后,使用进一步的反射,我们可以生成当前数据类型的替代数据类型。如果传入的数据是一个值,我们需要生成一个指向它的指针

value := reflect.ValueOf(data)
if value.Type().Kind() == reflect.Ptr {
    ptr = value
    value = ptr.Elem() // acquire value referenced by pointer
} else {
    ptr = reflect.New(reflect.TypeOf(i)) // create new pointer
    temp := ptr.Elem() // create variable to value of pointer
    temp.Set(value) // set value of variable to our passed in value
}

现在我们有了两种数据类型,我们可以简单地使用每种数据类型来检查现有方法

var finalMethod reflect.Value
method := value.MethodByName(methodName)
if method.IsValid() {
    finalMethod = method
}
// check for method on pointer
method = ptr.MethodByName(methodName)
if method.IsValid() {
    finalMethod = method
}

if (finalMethod.IsValid()) {
    return finalMethod.Call([]reflect.Value{})[0].String()
}

因此,考虑到这一点,我们可以有效地动态调用任何方法,无论是声明为*receiver还是receiver

完整的概念证明:http://play.golang.org/p/AU-
Km5VjZs

package main

import (
    "fmt"
    "reflect"
)

type Test struct {
    Start string
}

// value receiver
func (t Test) Finish() string {
    return t.Start + "finish"
}

// pointer receiver
func (t *Test) Another() string {
    return t.Start + "another"
}

func CallMethod(i interface{}, methodName string) interface{} {
    var ptr reflect.Value
    var value reflect.Value
    var finalMethod reflect.Value

    value = reflect.ValueOf(i)

    // if we start with a pointer, we need to get value pointed to
    // if we start with a value, we need to get a pointer to that value
    if value.Type().Kind() == reflect.Ptr {
        ptr = value
        value = ptr.Elem()
    } else {
        ptr = reflect.New(reflect.TypeOf(i))
        temp := ptr.Elem()
        temp.Set(value)
    }

    // check for method on value
    method := value.MethodByName(methodName)
    if method.IsValid() {
        finalMethod = method
    }
    // check for method on pointer
    method = ptr.MethodByName(methodName)
    if method.IsValid() {
        finalMethod = method
    }

    if (finalMethod.IsValid()) {
        return finalMethod.Call([]reflect.Value{})[0].Interface()
    }

    // return or panic, method not found of either type
    return ""
}

func main() {
    i := Test{Start: "start"}
    j := Test{Start: "start2"}

    fmt.Println(CallMethod(i, "Finish"))
    fmt.Println(CallMethod(&i, "Finish"))
    fmt.Println(CallMethod(i, "Another"))
    fmt.Println(CallMethod(&i, "Another"))
    fmt.Println(CallMethod(j, "Finish"))
    fmt.Println(CallMethod(&j, "Finish"))
    fmt.Println(CallMethod(j, "Another"))
    fmt.Println(CallMethod(&j, "Another"))
}


 类似资料:
  • 问题内容: 我正在学习接口,类型转换和带有指针接收器的方法。指针接收器方法背后的规则和术语使我感到困惑。让我展示我对一个程序的困惑。 这是我的Go程序。 这是输出。 如果删除最后注释的行,则会出现此错误。 如何修复该行代码,以便能够使用指针接收器调用该方法?请通过提出带有指针接收器的方法的概念来说明解决方案,以阐明为什么它不起作用。 问题答案: 您不能(在这种情况下,对于指针接收器是隐式的)获取表

  • 11.12.1 Go 的动态类型 在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为 类 的概念:类包含它们两者,并且不能剥离。 Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。 Go 中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的实现。但是更加灵活通用:任何提供了接口方法实现代码的类型都隐式地实现了该接口,而不用显式地声明。 和其它

  • 我想使用广播接收器在固定时间调用此方法的警报 } 删除所有消息,但没有发生,请提前帮我谢谢 /***删除所有消息。0-成功删除。1-没有要*删除的内容。*/ 我正在使用这个广播接收器 正如你所说我做了我创建了一个活动 公共类Active扩展PendingMessages实现Runnable{ } 然后打电话过去 context.start服务(新意图(上下文,Activity.class)); 但

  • 下面是我试图做的一个非常简单的说明: 最后一个方法调用(consumer.consumer())给了我一个编译器错误 Out projected type’EventConsumer 我知道Kotlin对泛型的要求比Java严格得多,这可能就是它不起作用的原因,但我该如何正确地实现这样的东西呢?

  • 问题内容: Java-8允许在接口内部定义静态方法,但仅通过接口名称限制其调用: 9.4:接口可以声明静态方法,这些方法在不引用特定对象的情况下被调用。 例如: 导致错误: 在JLS中,这种禁令经常有一种解释。在这种情况下,我没有发现任何详细信息。因此,我正在寻找对此规则的全面或权威的解释:为什么禁止通过特定的对象引用调用静态方法?它有什么坏处? 问题答案: 相当强烈的共识是,有关类的静态方法也不

  • 问题内容: 目前我有这样的事情 main.go 还有我的工作包 和 我正在传递给NewJob的函数是在goroutine上每2秒执行一次,但是我想访问我传递的匿名结构…但是当我尝试访问时 编号 我越来越 t.Id未定义(类型interface {}是没有方法的接口) 但是打印t给我预期的结果 {1} 问题答案: 您必须先将其声明为兼容类型,然后才能访问其字段。