英文:
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
是在循环中设置的,该循环遍历结构的字段,因此它将具有不同的类型。基本上,我想要一种方法来检测vField
是int
(即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"不是相同的术语。int
和Provider
的"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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论