Go语言 按名称访问结构属性

nx7onnlm  于 2023-01-15  发布在  Go
关注(0)|答案(5)|浏览(134)

下面是一个简单的围棋程序,但它不能正常工作:

package main
import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

func getProperty(v *Vertex, property string) (string) {
    return v[property]
}

错误:
程序执行:18:无效操作:v [特性](类型索引 * 顶点)
我想要的是使用它的名字来访问Vertex X属性。如果我使用v.X,它会工作,但是v["X"]不会。
有人能告诉我怎么做吗?

bksxznpy

bksxznpy1#

大多数代码都不需要这种动态查找,与直接访问相比效率很低(编译器知道Vertex结构中X字段的偏移量,它可以将v.X编译成一条机器指令,而动态查找需要某种哈希表实现或类似的东西),它还抑制了静态类型:编译器无法检查您是否试图动态访问未知字段,并且它无法知道结果类型应该是什么。
但是...该语言提供了一个reflect模块,以满足您在极少数情况下的需要。

package main

import "fmt"
import "reflect"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getField(&v, "X"))
}

func getField(v *Vertex, field string) int {
    r := reflect.ValueOf(v)
    f := reflect.Indirect(r).FieldByName(field)
    return int(f.Int())
}

这里没有错误检查,所以如果您请求一个不存在的字段,或者该字段不是int类型,您将得到一个异常。

qni6mghb

qni6mghb2#

现在您有了项目oleiade/reflections,它允许您获取/设置结构值或指针上的字段。
这使得使用reflect package不那么棘手。

s := MyStruct {
    FirstField: "first value",
    SecondField: 2,
    ThirdField: "third value",
}

fieldsToExtract := []string{"FirstField", "ThirdField"}

for _, fieldName := range fieldsToExtract {
    value, err := reflections.GetField(s, fieldName)
    DoWhatEverWithThatValue(value)
}

// In order to be able to set the structure's values,
// a pointer to it has to be passed to it.
_ := reflections.SetField(&s, "FirstField", "new value")

// If you try to set a field's value using the wrong type,
// an error will be returned
err := reflection.SetField(&s, "FirstField", 123)  // err != nil
b1payxdu

b1payxdu3#

使用getAttr,您可以轻松获取和设置。

package main

import (
    "fmt"
    "reflect"
)

func getAttr(obj interface{}, fieldName string) reflect.Value {
    pointToStruct := reflect.ValueOf(obj) // addressable
    curStruct := pointToStruct.Elem()
    if curStruct.Kind() != reflect.Struct {
        panic("not struct")
    }
    curField := curStruct.FieldByName(fieldName) // type: reflect.Value
    if !curField.IsValid() {
        panic("not found:" + fieldName)
    }
    return curField
}

func main() {
    type Point struct {
        X int
        y int  // Set prefix to lowercase if you want to protect it.
        Z string
    }

    p := Point{3, 5, "Z"}
    pX := getAttr(&p, "X")

    // Get test (int)
    fmt.Println(pX.Int()) // 3

    // Set test
    pX.SetInt(30)
    fmt.Println(p.X)  // 30

    // test string
    getAttr(&p, "Z").SetString("Z123")
    fmt.Println(p.Z)  // Z123

    py := getAttr(&p, "y")
    if py.CanSet() { // The necessary condition for CanSet to return true is that the attribute of the struct must have an uppercase prefix
        py.SetInt(50) // It will not execute here because CanSet return false.
    }
    fmt.Println(p.y) // 5
}



上运行👉

参考

vhmi4jdf

vhmi4jdf4#

你可以封送这个结构体,然后将它解组回map[string]interface{},但是,它会将所有的数值转换成float64,所以你必须手动将它转换成int

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

func getProperty(v *Vertex, property string) float64 {
    m, _ := json.Marshal(v)
    var x map[string]interface{}
    _ = json.Unmarshal(m, &x)
    return x[property].(float64)
}
up9lanfz

up9lanfz5#

我想提供一种不使用反射的不同方法:

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    fmt.Println(getProperty(&v, "X"))
}

type Getter func(v *Vertex) int

var VertexAccess = map[string]Getter{
    "X": func(v *Vertex) int { return v.X },
    "Y": func(v *Vertex) int { return v.Y },
}

func getProperty(v *Vertex, property string) int {
    return VertexAccess[property](v)
}

https://go.dev/play/p/2E7LZBWx7yZ
这是一个O(1)Map查找和一个函数调用,应该比反射执行得更好。显然,你需要一些脚手架代码来支持每一个你想要支持的类型。从好的方面来说,你可以很容易地重构你的代码;您的函数getProperty可能是https://martinfowler.com/bliki/TellDontAsk.html的反模式

相关问题