简体   繁体   English

如何复制Go中的接口值?

[英]How to copy an interface value in Go?

How to copy an interface value in Go?如何复制Go中的接口值?

My User interface:我的User界面:

type User interface {
    Name() string
    SetName(name string)
}

My Admin struct:我的Admin结构:

type Admin struct {
    name string
}

func (a *Admin) Name() string {
    return a.name
}

func (a *Admin) SetName(name string) {
    a.name = name
}

I try to copy user1 's value.我尝试复制user1的值。

Main Function:总机 Function:

func main() {
    var user1 User
    user1 = &Admin{name:"user1"}

    fmt.Printf("User1's name: %s\n", user1.Name())

    var user2 User
    user2 = user1
    user2.SetName("user2")

    fmt.Printf("User2's name: %s\n", user2.Name()) // The name will be changed as "user2"
    fmt.Printf("User1's name: %s\n", user1.Name())  // The name will be changed as "user2" too, How to make the user1 name does not change?
}

How to achieve that changing the copy's name the original doesn't change?如何实现改变副本的名称原件不改变?

The problem here is that your user1 variable (which is of type User ) holds a pointer to an Admin struct.这里的问题是你的user1变量(它是User类型)持有一个指向Admin结构的指针

When you assign user1 to another variable (of type User ), the interface value which is a pair of the dynamic type and value (value;type) will be copied - so the pointer will be copied which will point to the same Admin struct.当您将user1分配给另一个变量(类型User )时,将复制作为动态类型和值(value;type)对的接口值 - 因此将复制指向同一Admin结构的指针。 So you only have one Admin struct value, both user1 and user2 refer (point) to this.所以你只有一个Admin struct 值, user1user2引用(指向)这个值。 Changing it through any of the interface values changes the one and only value.通过任何接口值更改它会更改一个且唯一的值。

To make user1 and user2 distinct, you need 2 "underlying" Admin structs.要使user1user2不同,您需要 2 个“底层” Admin结构。

One way is to type assert the value in the user1 interface value, and make a copy of that struct, and wrap its address in another User value:一种方法是在user1接口值中键入 assert值,然后复制该结构体,并将其地址包装在另一个User值中:

var user2 User
padmin := user1.(*Admin) // Obtain *Admin pointer
admin2 := *padmin        // Make a copy of the Admin struct
user2 = &admin2          // Wrap its address in another User
user2.SetName("user2")

Now they will be distinct, output (try it on the Go Playground ):现在它们将是不同的,输出(在Go Playground上尝试):

User1's name: user1
User2's name: user2
User1's name: user1

Of course this solution has its limitation: the dynamic type stored in the User interface value is "wired" in the solution ( *Admin ).当然,这个解决方案有其局限性:存储在User界面值中的动态类型在解决方案中是“连线”的( *Admin )。

Using reflection使用反射

If we want a "general" solution (not just one that works with *Admin ), we can use reflection ( reflect package).如果我们想要一个“通用”解决方案(不仅仅是与*Admin一起使用的解决方案),我们可以使用反射( reflect包)。

For simplicity let's assume user1 always contains a pointer (for now).为简单起见,我们假设user1始终包含一个指针(目前)。

Using reflection we can get the dynamic type (here *Admin ), and even the dynamic type without a pointer ( Admin ).使用反射我们可以获得动态类型(这里是*Admin ),甚至是没有指针的动态类型( Admin )。 And we can use reflect.New() to obtain a pointer to a new value of that type (whose type will be identical to the original dynamic type in user1 - *Admin ), and wrap this back into a User .并且我们可以使用reflect.New()来获取一个指向该类型的新值的指针(其类型将与user1 - *Admin的原始动态类型相同),并将其包装回User This is how it could look like:这是它的样子:

var user3 User
user3 = reflect.New(reflect.ValueOf(user1).Elem().Type()).Interface().(User)
user3.SetName("user3")

Output (try this one on the Go Playground ):输出(在Go Playground上试试这个):

User1's name: user1
User3's name: user3
User1's name: user1

Note that reflect.New() will create a new value which is initialized to its zero value (so it will not be a copy of the original).请注意, reflect.New()将创建一个初始化为其零值的新值(因此它不会是原始值的副本)。 It's not a problem here as Admin only has one field which we're about to change anyway, but must be kept on our mind in general.这不是问题,因为Admin只有一个我们将要更改的字段,但总的来说必须牢记在心。

Our initial assumption was that user1 contains a pointer.我们最初的假设是user1包含一个指针。 Now the "full" solution must not make such assumption.现在“完整”解决方案不能做出这样的假设。 If the value in user1 would not be a pointer, this is how it could be "cloned":如果user1的值不是指针,这就是“克隆”它的方式:

var user3 User
if reflect.TypeOf(user1).Kind() == reflect.Ptr {
    // Pointer:
    user3 = reflect.New(reflect.ValueOf(user1).Elem().Type()).Interface().(User)
} else {
    // Not pointer:
    user3 = reflect.New(reflect.TypeOf(user1)).Elem().Interface().(User)
}
user3.SetName("user3")

Another solution could be to add a Clone() method to your User interface:另一种解决方案可能是在您的用户界面中添加一个 Clone() 方法:

type User interface {
    Clone() User
    Name() string
    SetName(name string)
}

type Admin struct {
    name string
}

func (a *Admin) Clone() User {
    u := *a
    return &u
}

func (a *Admin) Name() string {
    return a.name
}

func (a *Admin) SetName(name string) {
    a.name = name
}

func main() {
    var user1 User
    user1 = &Admin{name:"user1"}

    fmt.Printf("User1's name: %s\n", user1.Name())

    var user2 User
    user2 = user1.Clone()
    user2.SetName("user2")

    fmt.Printf("User2's name: %s\n", user2.Name())
    // output: User2's name: user2

    fmt.Printf("User1's name: %s\n", user1.Name())
    // output: User1's name: user1
}

Of course in this case, the implementation gets linked to the interface, which might not be desired, but that's a solution.当然,在这种情况下,实现会链接到接口,这可能不是我们想要的,但这是一个解决方案。

There is a more generic way to do that.有一种更通用的方法可以做到这一点。

func Clone(oldObj interface{}) interface{} {
    newObj := reflect.New(reflect.TypeOf(oldObj).Elem())
    oldVal := reflect.ValueOf(oldObj).Elem()
    newVal := newObj.Elem()
    for i := 0; i < oldVal.NumField(); i++ {
        newValField := newVal.Field(i)
        if newValField.CanSet() {
            newValField.Set(oldVal.Field(i))
        }
    }

    return newObj.Interface()
}

However, it has one pitfall: it cannot set unexported fields.但是,它有一个缺陷:它不能设置未导出的字段。 It can be worked around using this solution with the help of unsafe 's black magic, but I would rather avoid it.可以在unsafe的黑魔法的帮助下使用解决方案来解决它,但我宁愿避免它。

The easiest way of doing this is using a library (I'd recommend https://github.com/jinzhu/copier ) combined with some reflection magic:最简单的方法是使用一个库(我推荐https://github.com/jinzhu/copier )结合一些反射魔法:

var cloneObj MyCustomStruct = reflect.New(reflect.ValueOf(sourceObj).Elem().Type()).Interface()
copier.Copy(cloneObj, sourceObj)

This gives you a deep copy of the source struct correctly typed (which is important).这为您提供了正确键入的源结构的深层副本(这很重要)。 If you don't use an interface, you may need to customize the reflection use a bit (remove the .Interface() ).如果您不使用接口,则可能需要稍微自定义反射使用(删除.Interface() )。

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

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