【问题标题】:Changing pointer type and value under interface with reflection使用反射在接口下更改指针类型和值
【发布时间】:2018-03-02 16:56:11
【问题描述】:

接口定义的变量的指针类型和值可以改变吗?

我可以通过反射改变指针值:v.Elem().Set(reflect.ValueOf(&Greeter{"Jack"}).Elem()),相当于a = &Greeter{"Jack"}。

但是我怎样才能使 a = &Greeter2{"Jack"} 的反射等效?

更新:不幸的是,在我试图解决的实际问题中,我无法解决原始变量 (panic: reflect: reflect.Value.Set using unaddressable value),这就是我尝试在指针级别解决问题的原因。

Here 是完整的示例代码:

package main

import (
    "fmt"
    "reflect"
)

type Greeter struct {
    Name string
}

func (g *Greeter) String() string {
    return "Hello, My name is " + g.Name
}

type Greeter2 struct {
    Name string
}

func (g *Greeter2) String() string {
    return "Hello, My name is " + g.Name
}

func main() {
    var a fmt.Stringer
    a = &Greeter{"John"}

    v := reflect.ValueOf(a)
    v.Elem().Set(reflect.ValueOf(&Greeter{"Jack"}).Elem())
    //v.Elem().Set(reflect.ValueOf(&Greeter2{"Jack"}).Elem()) // panic: reflect.Set: value of type main.Greeter2 is not assignable to type main.Greeter
    fmt.Println(a.String()) // Hello, My name is Jack

    a = &Greeter2{"Ron"}
    fmt.Println(a.String()) // Hello, My name is Ron
}

【问题讨论】:

    标签: pointers go reflection interface


    【解决方案1】:

    Go 中的所有内容都是按值传递的。接口也是。当你传递一个接口类型的值时,会复制一个接口值(连同里面的(value;type)),你只能修改这个副本,不会影响原来的。

    你有一个接口类型的变量a。如果你想修改这个变量中存储的值,你必须传递/使用这个变量的地址。

    首先让我们修改Greeter2.String() 方法来知道调用的是哪一个:

    func (g *Greeter2) String() string {
        return "Hello2, My name is " + g.Name
    }
    

    并且还在第一次初始化之后调用a.String() 以查看它最初确实包含*Greeter 值,因为我们很快就会对其进行更改。

    var a fmt.Stringer
    a = &Greeter{"John"}
    fmt.Println(a.String()) // Hello, My name is John
    
    v := reflect.ValueOf(&a).Elem()
    v.Set(reflect.ValueOf(&Greeter2{"Jack"}))
    fmt.Println(a.String()) // Hello2, My name is Jack
    
    a = &Greeter2{"Ron"}
    fmt.Println(a.String()) // Hello2, My name is Ron
    

    输出(在Go Playground 上试试):

    Hello, My name is John
    Hello2, My name is Jack
    Hello2, My name is Ron
    

    所以本质上没有改变接口的值和类型值,只改变可能是接口类型的变量(具有内存地址)中的数据。

    所以关键是:

    1. 从一个地址开始:reflect.ValueOf(&a)
    2. 您必须在其中设置一个指针值,因为只有*Greeter2 实现了fmt.String,而不是Greeter。详情请见Go, X does not implement Y (... method has a pointer receiver)。

    更多详情和深入介绍,请阅读:The Go Blog: The Laws of Reflection

    编辑:关于修改副本

    我的回答的第一部分:每当你传递一些东西时,都会制作一份副本。这还包括将接口值传递给reflect.ValueOf(): 也会收到一个副本,因此您无法避免这种情况。完成这项工作的唯一方法是传递一个指针;因为指针也将被复制,但我们正在修改 pointed 值(不是指针),它是相同的。

    【讨论】:

    • 不幸的是,我错过了问题中的重要内容,我需要反射技巧来影响 a 的副本。我正在尝试修复github.com/boolean64/inject/blob/master/inject_test.go#L227 以使用Greeter2。在这种情况下,原始变量不可设置panic: reflect: reflect.Value.Set using unaddressable value。
    • @vearutop 不可能,因为调用reflect.ValueOf() 已经制作了(另一个)副本,因此您最多只能修改副本的副本。请参阅编辑后的答案。
    猜你喜欢
    • 2018-10-14
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 2020-10-25
    • 1970-01-01
    • 2020-01-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多