【问题标题】:Restore type information after passing through function as "interface {}"?通过函数作为“接口{}”后恢复类型信息?
【发布时间】:2016-05-28 12:06:06
【问题描述】:

我现在在使用 Golang 时遇到了一个轻微的架构问题,这导致我复制/粘贴的代码比我希望的要多。我觉得必须有一个解决方案,所以请让我知道这是否可能:

当我通过interface {}-typed 函数参数传递东西时,我开始收到诸如“预期的结构或切片”等错误......即使我之前传递的是结构或切片。我意识到我可以在该函数中收到它们后手动将它们转换为另一种类型,但是在这样的实例中变得乏味:

本地接口类型 *interface {} 只能从远程解码 接口类型;收到具体类型

...在这种情况下,接收函数似乎需要硬编码才能将所有interface {} 项目转换回它们各自的原始类型才能正常工作,因为接收函数需要知道准确的类型,以便正确处理项目。

有没有办法将 Golang interface {} 类型化的变量动态重新输入到它们的原始类型? 像这样,How to I convert reflect.New's return value back to the original type ... 可能吗?


编辑:为了澄清,基本上,我将&out 传递给一个函数,并且在它到达另一个内部函数调用时它必须是它的原始类型。

示例代码:

// NOTE: This is sort of pseudo-Golang code, not meant to be compiled or taken too seriously.

func PrepareTwoDifferentThings(keyA string, keyB string) {
    var somethingA TypeA;
    var somethingB TypeB;

    loadFromCache(keyA, &somethingA, nil);
    loadFromCache(keyB, &somethingB, nil);

    fmt.Printf("Somethings: %v, %v", somethingA, somethingB);

}

func loadFromCache(key string, isNew, out interface {}, saveNewData interface {}) {
    if err := cache.load(key, &out); err!=nil { // NOTE: Current issue is that this expects "&out" to be `TypeA`/`TypeB` not "interface {}", but I don't want to copy and paste this whole function's worth of code or whatever.
        panic("oh no!");

    }

    if (saveNewData!=nil) {
        cache.save(key, saveNewData); // This doesn't seem to care if "saveNewData" is "interface {}" when saving, but later cache fetches above using the "load()" method to an "interface {}"-typed `&out` parameter throw an exception that the "interface {}" type on `&out` does not match the original when it was saved here (`TypeA`/`TypeB`).

    }

}

【问题讨论】:

  • 你无法在不知道它是什么的情况下转换为原始类型。 Reflect 可以让你动态处理各种类型,但是没有断言就无法获得“真实”的类型。
  • 我刚刚发布了一些澄清作为编辑。
  • 我目前的解决方法是将闭包传递给函数而不是 interface {} 类型属性,并且函数在必要时调用该函数而不会丢失原始变量的类型,因为它在先前的范围内并且从不改变类型。但是,这需要我将代码复制并粘贴到调用此方法的各种实例中,我认为这是不必要的……似乎是保留我找到的类型的唯一方法。
  • 然后你需要将它断言回正确的类型。如果您知道需要在编译时调用的函数的签名,那么您就知道需要断言的类型。如果没有,你必须使用反射来完成整个事情。
  • 发布代码的相关部分可能会有所帮助。

标签: go


【解决方案1】:

要将接口的类​​型更改为正确的类型,您可以使用type assertions

package main

import r "reflect"

type A struct {
    Name string
}

func main() {
    // No pointer
    aa := A{"name"}
    var ii interface{} = aa

    bb := ii.(A)
    // main.A

    // Pointer
    a := &A{"name"}
    var i interface{} = a

    b := *i.(*A)
    // main.A

    c := i.(*A)
    // *main.A

    d := r.Indirect(r.ValueOf(i)).Interface().(A)
    // main.A
}

Playground 1

使用类型断言时,您必须知道接口的底层类型。在 Go 中,无法将类型断言与动态类型一起使用。 reflect.Type 不是类型,它是表示类型的接口。所以不,你不能这样使用它。

如果您有多种类型的可能性,解决方案是type switch

package main

import "fmt"

type TypeA struct {
    A string
}

type TypeB struct {
    B string
}

func doSomethingA(t TypeA) {
    fmt.Println(t.A)
}

func doSomethingB(t TypeB) {
    fmt.Println(t.B)
}

func doSomething(t interface{}) {
    switch t := t.(type) {
    case TypeA:
        doSomethingA(t)
    case TypeB:
        doSomethingB(t)
    default:
        panic("Unrecognized type")
    }
}

func main() {
    a := TypeA{"I am A"}
    b := TypeB{"I am B"}

    doSomething(a)
    // I am A
    doSomething(b)
    // I am B
}

Playground 2

【讨论】:

  • 你能解释一下如何直接获取 interface {} 元素的原始类型并在将其传递给另一个函数之前将其重新声明为该类型吗?如果是这样,我可以接受这个解决方案。
【解决方案2】:

事实证明,使用JSON 而不是Gob 进行序列化可以完全避免我遇到的错误。其他函数可以处理传入接口等。

【讨论】:

  • 请发布您是如何解决问题的。但是,序列化和反序列化结构以获得正确的类型并不是解决问题的好方法。而且,你仍然有在最后取回正确结构的问题。
  • 这不是真正的问题。如果传递了 interface {} 而不是本机类型,则在我的示例中,缓存加载/保存方法更令人恐慌。由于该函数可能无法访问原始类型以声明各种可能的类型,因此我不得不切换到JSON 作为缓存方法,它比Gob 不那么挑剔。
  • 我最终为不同结构的对象编写了不同的存储和加载方法,这样我就可以明确指定要存储的对象的类型。这样你仍然可以使用 gob。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-21
  • 2019-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多