英文:
How can I convert struct value into struct pointer using reflection
问题
目标是通过指针接收器类型,创建一个满足特定接口I
的变量B
,使用反射和B
创建另一个变量C
,将B
的值复制到C
中,修改C
(而不改变B
),并将C
作为类型I
返回。
假设我有以下类型,以下代码片段模拟生产代码:
import (
"reflect"
)
type IFace interface {
A()
B()
C()
}
type Meta struct {
s string
}
func (m *Meta) A() {}
func (m *Meta) B() {}
func (m *Meta) C() {}
type One struct {
M *Meta
B bool
}
func (o *One) A() {}
func (o *One) B() {}
func (o *One) C() {}
我有一个方法执行以下操作:
func Alias(src, dest *Meta) (IFace, error) {
base, err := find(src) //假设`find`已实现且err为nil
if err != nil {
return err
}
//从这里开始出现问题...
//分配新的“实例”
aliased := reflect.New(reflect.TypeOf(base)).Elem().Interface()
//复制基本值
aliased = base
aliasedV := reflect.ValueOf(aliased).Elem()
fm := aliasedV.FieldByName("M")
fm.Set(reflect.ValueOf(dest))
return aliasedV.Interface().(Iface), nil
}
它可以编译和运行,但是使用以下TestFunction时,它给我返回以下错误消息:
interface conversion: One is not IFace: missing method C [recovered]
panic: interface conversion: One is not IFace: missing method C
测试函数如下:
func TestOne(t *testing.T) {
srcID := &Meta{S: "SRC"}
destID := &Meta{S: "DEST"}
aliased, err := Alias(srcID, destID)
if err != nil {
t.Error(err)
}
one, isOne := aliased.(*One)
if !isOne {
t.Error("fail")
}
}
有没有一种方法可以将包装结构值的interface{}
类型变为包装结构指针的interface{}
类型,而不直接使用底层结构类型,例如避免使用var any interface{} = aliased.(*One)
?unsafe
包是否有助于解决这个问题?
以下是一个复制panic的playground链接,感谢RayfenWindspear:
https://play.golang.org/p/860uAE7qLc
英文:
The goal is
> Having a variable B
that satisfies a specific interface I
through pointer receivers type, create another variable C
(with reflection and using B
), copy B
's values into C
, modify C
(without changing B
) and return C
as type I
.
Suppose that I have the following types, the following snippets mimic production code:
import (
"reflect"
)
type IFace interface {
A()
B()
C()
}
type Meta struct {
s string
}
func (m *Meta) A() {}
func (m *Meta) B() {}
func (m *Meta) C() {}
type One struct {
M *Meta
B bool
}
func (o *One) A() {}
func (o *One) B() {}
func (o *One) C() {}
And I have a method that does the following:
func Alias(src, dest *Meta) (IFace, error) {
base, err := find(src) //asume that `find` is implemented and err is nil
if err != nil {
return err
}
// trouble starts here ...
// allocate new "instance"
aliased := reflect.New(reflect.TypeOf(base)).Elem().Interface()
// copy the base value
aliased = base
aliasedV := reflect.ValueOf(aliased).Elem()
fm := aliasedV.FieldByName("M")
fm.Set(reflect.ValueOf(dest))
return aliasedV.Interface().(Iface), nil
}
It compiles and runs however with the following TestFunction it gives me this error message:
interface conversion: One is not IFace: missing method C [recovered]
panic: interface conversion: One is not IFace: missing method C
and the test function:
func TestOne(t *testing.T) {
srcID := &Meta{S: "SRC"}
destID := &Meta{S: "DEST"}
aliased, err := Alias(srcID, destID)
if err != nil {
t.Error(err)
}
one, isOne := aliased.(*One)
if !isOne {
t.Error("fail")
}
}
Is there a way to have an interface{}
type that wraps a struct value become an interface{}
type that wraps a struct pointer without using the underlying struct type directly, like avoiding: var any interface{} = aliased.(*One)
??,
Could the unsafe
package be of help here?
Here is a playground that replicates the panic, thanks to RayfenWindspear
答案1
得分: 1
不确定这是否符合您的要求,但如果我理解正确的话,在您从评论中更新的示例中,返回的base
是您想要复制、修改,然后返回副本的内容,而不更改base
中的任何内容。如果是这种情况,那么这行代码aliased = base
在基础类型为指针的情况下不会实现您想要的效果,而在这个示例中是指针类型。
请注意,我已经修改了变量名以更好地反映您的赋值。
// 通过指针接收器类型,创建一个满足特定接口I的变量B。
B, err := find(src)
if err != nil {
return nil, err
}
// 使用反射和B创建另一个变量C。
C := reflect.New(reflect.TypeOf(B).Elem())
// 将B的值复制到C。
bv := reflect.ValueOf(B).Elem()
for i := 0; i < bv.NumField(); i++ {
fv := bv.Field(i)
if fv.Kind() == reflect.Ptr {
v := reflect.New(fv.Elem().Type()) // 为B指针字段分配一个与其指向的类型相同的新指针,此处为'Meta'
v.Elem().Set(fv.Elem()) // 将新分配的指针的值设置为与B的指针字段指向的值相同,此处为'Meta{S: "SRC"}'
C.Elem().Field(i).Set(v) // 将新分配的指针设置为C的字段
} else {
C.Elem().Field(i).Set(fv) // 非指针字段?直接设置即可
}
// 注意:如果B的字段具有指针子字段,如果要进行深拷贝,您将需要对所有子字段执行此操作
}
// 修改C(不更改B)
C.Elem().FieldByName("M").Elem().FieldByName("S").Set(reflect.ValueOf("Hello, 世界"))
这是Playground链接:https://play.golang.org/p/bGTdy2vYUu
如果这不是您要找的内容,我很抱歉。
<details>
<summary>英文:</summary>
Not sure if this is what you want, but if I understood you correctly, in your updated example from the comments the returned `base` is what you want to copy, modify, and then return the copy of, without changing anything in `base`. If that's the case, this code `aliased = base` is not gonna do what you want if the base's underlying type is a pointer, which is true in this case.
Note, I've modified the var names to better `reflect` your assignment.
// Having a variable B that satisfies a specific
// interface I through pointer receivers type.
B, err := find(src)
if err != nil {
return nil, err
}
// create another variable C (with reflection and using B),
C := reflect.New(reflect.TypeOf(B).Elem())
// copy B's values into C
bv := reflect.ValueOf(B).Elem()
for i :=0; i < bv.NumField(); i++ {
fv := bv.Field(i)
if fv.Kind() == reflect.Ptr {
v := reflect.New(fv.Elem().Type()) // allocate a new pointer of the same type as the B's pointer field is pointing to, in this case 'Meta'
v.Elem().Set(fv.Elem()) // set the newly allocated pointer's value to the same value as B's pointer field is pointing to, in this case 'Meta{S: "SRC"}'
C.Elem().Field(i).Set(v) // set the newly allocated pointer as the C's field
} else {
C.Elem().Field(i).Set(fv) // non pointer field? just set and that's it
}
// NOTE: if B's field's have subfields that are pointers you'll have to do this for all of them if you want a deep copy
}
// modify C (without changing B)
C.Elem().FieldByName("M").Elem().FieldByName("S").Set(reflect.ValueOf("Hello, 世界"))
Here's the playground: https://play.golang.org/p/bGTdy2vYUu
Sorry if this not what you're looking for.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论