通过名称访问结构体属性

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

Access struct property by name

问题

这是一个简单的Go程序,但是有问题:

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]
}

错误信息:

prog.go:18: invalid operation: v[property] (index of type *Vertex)

我想要的是通过属性名访问Vertex的X属性。如果我使用v.X是可以的,但是v["X"]不行。

有人能告诉我如何解决这个问题吗?

英文:

Here is a simple go program that is not working :

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]
}

Error:

> prog.go:18: invalid operation: v[property] (index of type *Vertex)

What I want is to access the Vertex X property using its name. If I do v.X it works, but v["X"] doesn't.

Can someone tell me how to make this work ?

答案1

得分: 153

大多数代码不需要这种动态查找。与直接访问相比,这种方法效率低下(编译器知道结构体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类型,将会引发panic。请查阅reflect的文档获取更多详细信息。

英文:

Most code shouldn't need this sort of dynamic lookup. It's inefficient compared to direct access (the compiler knows the offset of the X field in a Vertex structure, it can compile v.X to a single machine instruction, whereas a dynamic lookup will need some sort of hash table implementation or similar). It's also inhibits static typing: the compiler has no way to check that you're not trying to access unknown fields dynamically, and it can't know what the resulting type should be.

But... the language provides a reflect module for the rare times you need this.

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())
}

There's no error checking here, so you'll get a panic if you ask for a field that doesn't exist, or the field isn't of type int. Check the documentation for reflect for more details.

答案2

得分: 22

你现在有一个名为oleiade/reflections的项目,它允许你在结构体值或指针上获取/设置字段。它使得使用reflect变得不那么棘手。

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)
}

// 为了能够设置结构体的值,必须传递一个指向它的指针。
_ := reflections.SetField(&s, "FirstField", "new value")

// 如果你尝试使用错误的类型设置字段的值,将返回一个错误。
err := reflection.SetField(&s, "FirstField", 123)  // err != nil
英文:

You now have the project oleiade/reflections which allows you to get/set fields on struct value or pointers.
It makes using the reflect package less tricky.

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

答案3

得分: 3

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

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) int {
	switch property {
	case "X":
		return v.X
	case "Y":
		return v.Y
	default:
		return 0
	}
}

这是一个O(1)的映射查找和函数调用,应该比反射性能更好。显然,你需要为每种你想支持的类型编写一些支持代码。好处是,你可以轻松重构你的代码;你的函数getProperty是一个潜在的反模式,参考:https://martinfowler.com/bliki/TellDontAsk.html

英文:

I want to offer a different approach that is not using reflection:

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

This is an O(1) map lookup and a function call which should perform better than reflection. Obviously, you need some scaffolding code for every type you want to support. On the plus side, you can refactor your code easily; your function getProperty is a potential anti-pattern to https://martinfowler.com/bliki/TellDontAsk.html

答案4

得分: 2

使用getAttr函数,你可以轻松地获取和设置属性。

package main

import (
	"fmt"
	"reflect"
)

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

func main() {
	type Point struct {
		X int
		y int  // 如果要保护它,请将前缀设置为小写。
		Z string
	}

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

	// 获取测试(int)
	fmt.Println(pX.Int()) // 3

	// 设置测试
	pX.SetInt(30)
	fmt.Println(p.X)  // 30

	// 字符串测试
	getAttr(&p, "Z").SetString("Z123")
	fmt.Println(p.Z)  // Z123

	py := getAttr(&p, "y")
	if py.CanSet() { // CanSet返回true的必要条件是结构体的属性必须具有大写前缀
		py.SetInt(50) // 这里不会执行,因为CanSet返回false。
	}
	fmt.Println(p.y) // 5
}

在 👉 上运行它 通过名称访问结构体属性

参考资料

  • CanSet
  • reflex的一个很好的例子:https://stackoverflow.com/a/6396678
英文:

With getAttr, you can get and set easy.

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
}

Run it on 👉 通过名称访问结构体属性

Reference

答案5

得分: 1

你可以将结构体进行编组,然后将其解组回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)
}
英文:

You can marshal the struct and unmarshal it back to map[string]interface{}. But, it would convert all the number values to float64 so you would have to convert it to int manually.

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)
}

huangapple
  • 本文由 发表于 2013年9月21日 17:11:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/18930910.html
匿名

发表评论

匿名网友

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

确定