golang 如何访问提升的类型

golang how to access promoted type

我在两个特定结构中提升了一个 'common' 结构。例如:

type common struct {
    name string
}

type apple struct {
    common
}

type orange struct {
    common
}

省略了 appleorange 的详细信息。

我有每个类型的特定映射,例如 map[string]*applemap[string]*orange

我正在尝试制作一个可以提取 common 指针的函数。从我到目前为止的尝试来看,似乎需要反思。

我的函数是:

func getFruitArray(theMap interface{}) []*common {
    m := reflect.ValueOf(theMap)
    cf := make([]*common, 0, m.Len())
    for _, mk := range m.MapKeys() {
        v := m.MapIndex(mk)
        cf = append(cf, v.Interface().(*common))
    }

    return cf
}

此函数在 cf = append(cf, v.Interface().(*common)) 处失败,原因是:

panic: interface conversion: interface {} is *main.apple, not *main.common

有没有办法访问提升的结构 common 而无需在此函数中专门引用 appleorange

playground example

你不需要反省。一种方法是使用接口:

type common struct {
    name string
    tag  string
}

func (c *common) GetCommon() *common {return c}

type WithCommon interface {
   GetCommon() *common
}

那么你可以这样做:

func getFruitArray(theMap map[string]WithCommon) []*common {
   cf := make([]*common, 0, theMap.Len())
   for _,k:=range theMap {
      cf=append(cf,k.GetCommon())
   }
   return cf
}

但你还必须做:

 appleMap := make(map[string]WithCommon)
 orangeMap := make(map[string]WithCommon)

请参阅 Burak 的回答,该回答在必须调用方法来接收值方面做出了合理的妥协。

无论如何,下面是一个按照您的计划使用反射的解决方案。请注意,common 必须是 Common(导出字段),否则反射包无法读取它。

package main

import (
    "log"
    "reflect"
)

type Common struct {
    name string
}

type apple struct {
    Common
}

type orange struct {
    Common
}

func getFruitArray(theMap interface{}) []*Common {
    m := reflect.ValueOf(theMap)
    cf := make([]*Common, 0, m.Len())
    for _, mk := range m.MapKeys() {
        v := m.MapIndex(mk)
        f := v.Elem().FieldByName("Common")
        cf = append(cf, f.Addr().Interface().(*Common))
    }

    return cf
}

func main() {
    appleMap := make(map[string]*apple)
    orangeMap := make(map[string]*orange)

    a1 := &apple{}
    a1.name = "my apple"
    appleMap["test"] = a1

    o1 := &orange{}
    o1.name = "my orange"
    orangeMap["test2"] = o1

    f1 := getFruitArray(appleMap)
    for _, c := range f1 {
        log.Printf("f1: %s", c.name)
    }

    f2 := getFruitArray(orangeMap)
    for _, c := range f2 {
        log.Printf("f2: %s", c.name)
    }
}

https://go.dev/play/p/FrkRnu_G2Xd

如果您知道自己在做什么,可以使用 unsafe 包。但是,如果你不这样做,那就不要。

func getFruitArray(theMap interface{}) []*common {
    m := reflect.ValueOf(theMap)
    cf := make([]*common, 0, m.Len())
    for _, mk := range m.MapKeys() {
        v := m.MapIndex(mk).Elem() // use elem to dereference the pointer
        t := v.Type()

        // If you know that common is always the first field
        // then you can just use v.Field(0). But if common's
        // position is not guaranteed then use a loop like below.
        for i := 0; i < v.NumField(); i++ {
            sf := t.Field(i)
            if sf.Anonymous && sf.Name == "common" {
                f := v.Field(i)
                // 1. get the address of the common field
                // 2. convert it first to unsafe.Pointer
                // 3. then convert it to *common
                c := (*common)(unsafe.Pointer(f.UnsafeAddr()))
                cf = append(cf, c)
            }
        }
    }

    return cf
}

https://go.dev/play/p/XMi86jj2wiW