在Go语言中使用反射来分配给类型定义。

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

Assigning to type definition using reflection in Go

问题

我已经设置了一个名为Provider的类型,它定义了一个整数:

type Provider int

func (enum *Provider) Scan(raw interface{}) error {
    *enum = Provider(int(raw))
}

如果我创建一个带有Provider字段的对象Foo,像这样:

type Foo struct {
    Code Provider
    Value string
}

foo := &Foo{
    Code:  Provider(0),
    Value: "derp",
}

var scnr sql.Scanner
scannerType := reflect.TypeOf(&scnr).Elem()

tType := reflect.TypeOf(foo)
tField := tType.Field(0)
fmt.Printf("Field %s, of type %s, kind %s\n", 
    tField.Name, tField.Type, tField.Type.Kind())

当我运行这段代码时,我得到的结果是字段的类型是Provider,其Kind是int。然而,我无法使用反射将int赋值给Provider,因为这会失败:

getInteger() interface{} {
    return 1
}

fValue := reflect.ValueOf(foo).Elem()
vField := fValue.Field(0)
vField.Set(reflect.ValueOf(getInteger())) // panic: reflect.Set: value of type int is not assignable to type Provider

通常的解决方法是使用reflect.ValueOf(Provider(getInteger().(int))),但是这不起作用,因为vField是在循环中设置的,该循环遍历结构的字段,因此它将具有不同的类型。基本上,我想要一种方法来检测vFieldint(即Provider的定义),而不是int本身,这样我就可以使用反射将整数值转换为Provider并将其设置。

有没有办法让这个工作?

英文:

I have setup a type called Provider that defines an integer:

type Provider int

func (enum *Provider) Scan(raw interface{}) error {
    *enum = Provider(int(raw))
}

If I create an object, Foo, with a Provider field, like this:

type Foo struct {
    Code Provider
    Value string
}

foo := &Foo {
    Code:  Provider(0),
    Value: "derp",
}

var scnr sql.Scanner
scannerType := reflect.TypeOf(&scnr).Elem()

tType := reflect.TypeOf(foo)
tField := tType.Field(0)
fmt.Printf("Field %s, of type %s, kind %s\n", 
    tField.Name, tField.Type, tField.Type.Kind())

When I run this code, I get that the field is of type Provider and its Kind is int. However, I cannot assign an int to a Provider using reflection because this will fail:


getInteger() interface{} {
    return 1
}

fValue := reflect.ValueOf(foo).Elem()
vField := fValue.Field(0)
vField.Set(reflect.ValueOf(getInteger())) // panic: reflect.Set: value of type int is not assignable to type Provider

The normal solution to this would be to do reflect.ValueOf(Provider(getInteger().(int))), however, this won't work because vField is set inside of a loop that iterates over a structure's fields and therefore will have a different type. Essentially, I would like a way to detect that vField is a definition of int (ie. Provider) rather than an int, itself; so I could use reflection to cast the integer value to Provider when I set it.

Is there a way I can make this work?

答案1

得分: 4

"kind"和"type"不是相同的术语。intProvider的"kind"都是int,因为Provider的底层类型是int。但是,类型Provider并不等同于int。你所拥有的是一个类型定义,而不是类型别名!这两者是不同的概念。详细信息请参考这里

Foo.Code字段的类型是Provider,你只能将类型为Provider的值赋给它:

vField.Set(reflect.ValueOf(Provider(1)))

这样可以正常工作。

需要注意的是,无论是否使用反射,你都不能将int赋值给Foo.Code

var i int = 3
var foo Foo
foo.Code = i // 错误!

上述代码在编译时会报错:

错误:无法将类型为int的变量i分配给类型为Provider的变量

可能会让人困惑的是,使用常量是可以的:

var foo Foo
foo.Code = 3 // 正常工作!

这是因为3是一个无类型常量,可以表示为Provider类型的值,所以常量会被转换为Provider类型。

英文:

The kind and type are not identical terms. Kind of both int and Provider is int because Provider has int as its underlying type. But the type Provider is not identical to int. What you have is a type definition, but not a type alias! Those are 2 different things. See https://stackoverflow.com/questions/64988768/confused-about-behavior-of-type-aliases/64988906#64988906 for details.

The type of Foo.Code field is Provider, you can only assign a value of type Provider to it:

vField.Set(reflect.ValueOf(Provider(1)))

This works.

Note that reflection or not, you can't assign int to Foo.Code:

var i int = 3
var foo Foo
foo.Code = i // Error!

The above has compile time error:
> error: cannot use i (variable of type int) as type Provider in assignment

What may be confusing is that using a constant works:

var foo Foo
foo.Code = 3 // Works!

This is because 3 is an untyped constant representable by a value of type Provider, so the constant will be converted to Provider.

答案2

得分: 2

我最终使用Convert函数与Assignable函数结合使用来进行检查:


// 获取要分配的值及其类型
vValue := reflect.ValueOf(getInteger())
tValue := vValue.Type()

// 检查字段是否可以分配该值;如果可以,则进行分配
// 否则,检查是否可以进行转换分配
vType := vField.Type()
if tValue.AssignableTo(vType) {
	vField.Set(vValue)
} else if vValue.CanConvert(vType) {
	vField.Set(vValue.Convert(vType))
}

这解决了我的问题。

英文:

I ended up using the Convert function in conjunction with the Assignable function to do my check:


// Get value to assign and its type
vValue := reflect.ValueOf(getInteger())
tValue := vValue.Type()

// Check if the field can be assigned the value; if it can then do so
// Otherwise, check if a conversion can be assigned
vType := vField.Type()
if tValue.AssignableTo(vType) {
	vField.Set(vValue)
} else if vValue.CanConvert(vType) {
	vField.Set(vValue.Convert(vType))
}

This fixed my issue.

huangapple
  • 本文由 发表于 2022年8月16日 17:49:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/73372033.html
匿名

发表评论

匿名网友

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

确定