如何使用反射将结构体值转换为结构体指针?

huangapple go评论72阅读模式
英文:

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

https://play.golang.org/p/860uAE7qLc

答案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&#39;s the case, this code `aliased = base` is not gonna do what you want if the base&#39;s underlying type is a pointer, which is true in this case.

Note, I&#39;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&#39;s values into C
	bv := reflect.ValueOf(B).Elem()
	for i :=0; i &lt; 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&#39;s pointer field is pointing to, in this case &#39;Meta&#39;
			v.Elem().Set(fv.Elem()) // set the newly allocated pointer&#39;s value to the same value as B&#39;s pointer field is pointing to, in this case &#39;Meta{S: &quot;SRC&quot;}&#39;
			C.Elem().Field(i).Set(v) // set the newly allocated pointer as the C&#39;s field
		} else {
			C.Elem().Field(i).Set(fv) // non pointer field? just set and that&#39;s it
		}
        // NOTE: if B&#39;s field&#39;s have subfields that are pointers you&#39;ll have to do this for all of them if you want a deep copy
	}
	
	// modify C (without changing B)
    C.Elem().FieldByName(&quot;M&quot;).Elem().FieldByName(&quot;S&quot;).Set(reflect.ValueOf(&quot;Hello, 世界&quot;))

Here&#39;s the playground: https://play.golang.org/p/bGTdy2vYUu

Sorry if this not what you&#39;re looking for.

</details>



huangapple
  • 本文由 发表于 2017年4月6日 00:18:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/43236589.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定