Golang如何访问被提升的类型

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

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示例

英文:

I have a 'common' structure promoted within two specific structures. For example:

type common struct {
	name string
}

type apple struct {
	common
}

type orange struct {
	common
}

Details specific to apple and orange are omitted.

I have a type-specific map of each, e.g., map[string]*apple and map[string]*orange.

I am trying to make a single function that can extract the common pointers. From what I've tried so far, reflection appears required.

My function is:

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
}

This function fails at cf = append(cf, v.Interface().(*common)) with:

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

Is there a way to access the promoted struct common without specifically referencing apple or orange in this function?

playground example

答案1

得分: 4

请稍等,我会为您翻译这段代码。

英文:

See Burak's answer which makes the reasonable compromise of having to call a method to receive the value.

Regardless, below is a solution which uses reflection as you planned. Note that common needs to be Common (exported field) else the reflect package cannot read it.

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

答案2

得分: 2

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

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, len(theMap))
   for _, k := range theMap {
      cf = append(cf, k.GetCommon())
   }
   return cf
}

但你还需要做以下操作:

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

You don't need reflection. One way is to use an interface:

type common struct {
name string
tag  string
}
func (c *common) GetCommon() *common {return c}
type WithCommon interface {
GetCommon() *common
}

Then you can do:

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

But you also have to do:

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

答案3

得分: 1

如果你知道自己在做什么,可以使用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() // 使用elem解引用指针
		t := v.Type()

        // 如果你知道common总是第一个字段
        // 那么你可以直接使用v.Field(0)。但如果common的位置不确定,就像下面的循环一样。
		for i := 0; i < v.NumField(); i++ {
			sf := t.Field(i)
			if sf.Anonymous && sf.Name == "common" {
				f := v.Field(i)
                // 1. 获取common字段的地址
                // 2. 首先将其转换为unsafe.Pointer
                // 3. 然后将其转换为*common
				c := (*common)(unsafe.Pointer(f.UnsafeAddr()))
				cf = append(cf, c)
			}
		}
	}

	return cf
}

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

英文:

If you know what you are doing you could use the unsafe package. But if you don't then don't.

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&#39;s
        // position is not guaranteed then use a loop like below.
		for i := 0; i &lt; v.NumField(); i++ {
			sf := t.Field(i)
			if sf.Anonymous &amp;&amp; sf.Name == &quot;common&quot; {
				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

huangapple
  • 本文由 发表于 2022年2月1日 06:12:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/70933451.html
匿名

发表评论

匿名网友

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

确定