3

我最近一直在用 Go 编写一些审计流程。其中大部分只是将任意构造分开的反射。存在一个可用于对象相等的接口。并非所有被撕裂的东西都实现了这个接口,并且可以在任意类型之间进行比较(对象相等并不意味着类型相等)。

因为被比较的东西不需要是相同的类型,所以在某些情况下,一方面是指针,另一方面是值(*string vs string 或 *ChickenBox vs BoxOfChicken)。当我遇到指针/接口值时,我只是简单地取消引用它们,因此完成时的比较很简单。

我的问题是,一旦我取消了指针/接口的引用,我似乎无法对父指针/接口实现的接口键入断言。

这是一个示例(游乐场: https: //play.golang.org/p/O_dvyW07fu),它演示了这一点:

package main

import (
    "fmt"
    "reflect"
)

// ID interface
type IHasID interface {
    GetID() string
    SetID(id string)
}

// implementing type
type HasID struct {
    ID string `bson:"_id,omitempty"`
}

func (be *HasID) GetID() string {
    return be.ID
}
func (be *HasID) SetID(id string) {
    be.ID = id
}

// static comparison reflect.Type of IHasID
var idType = reflect.TypeOf((*IHasID)(nil)).Elem()

func main() {
    testy := struct {
        HasID
        Somefield int
    }{
        HasID{
            ID: "testymctest",
        },
        4,
    }

    id, _ := GetIDFrom(reflect.ValueOf(testy))
    fmt.Printf("ID: %v\n", id)
}

// check if the reflect.Value implements IHasID
func ImplementsID(x reflect.Value) bool {
    switch x.Kind() {
    case reflect.Interface, reflect.Ptr:
        if x.IsValid() && !x.IsNil() {
            return ImplementsID(x.Elem())
        }
    case reflect.Struct:
        if x.IsValid() {
            return reflect.PtrTo(x.Type()).Implements(idType)
        }
    }
    return false
}

// check and get the reflect.Value's ID
func GetIDFrom(x reflect.Value) (string, bool) {
    switch x.Kind() {
    case reflect.Ptr, reflect.Interface:
        if x.IsValid() && !x.IsNil() {
            return GetIDFrom(x.Elem())
        }
    case reflect.Struct:
        if x.IsValid() && ImplementsID(x) {
            // not using val,ok syntax to demo issue, if it gets here it should
            // implement ID since we did the check in the if statement above
            val := x.Interface().(IHasID)
            return val.GetID(), true
        }
    }
    return "", false
}

我对此的理解是,界面分为两部分(https://blog.golang.org/laws-of-reflection)。在 reflect 中,这由 reflect.Type(广义方法)和 reflect.Value(广义数据)表示。在演示中,我们成功评估了传递的 reflect.Value 的 reflect.Type 实现了第 67 行的接口:

if x.IsValid() && ImplementsID(x) {

这是因为在第 53 行,我们可以使用 reflect.PtrTo 重建我们与接口的关系:

return reflect.PtrTo(x.Type()).Implements(idType)

正是这个过程(在 reflect.Type 方面)我无法在 reflect.Value 方面复制。所以在第 70 行,我们简单地拥有底层结构(没有方法):

val := x.Interface().(IHasID)

这失败了,我一直找不到任何语法可以让我做在 reflect.Type 方面似乎可行的事情;回到针对满足断言接口的该结构定义的方法(我认为 reflect.Type 只是针对缓存的接口存储工作并且结构方法在这一点上已经丢失给我是合理的)。

我有一个可行的解决方案,它只是在做任何工作之前强制指针/接口的存在,但这感觉就像我错过了一些东西。鉴于 reflect.Value 的 reflect.Type 可以成功找到接口,在 reflect.Value 方面我可以做类似的事情吗?

4

1 回答 1

2

GetID方法需要一个指针接收器,但您正在对结构值进行操作。由于该值不可寻址,因此您不能调用该GetID方法,也不能调用Addr()以获取指向该值的指针。

这与无法编译的原因相同:https: //play.golang.org/p/ZPQnVXtx01

hid := HasID{ID: "testID"}
_ = IHasID(hid)

调用时需要使用指向原始结构值的指针GetIDFrom

id, _ := GetIDFrom(reflect.ValueOf(&testy))

然后,您应该首先检查指针是否实现了接口,然后再取消引用Elem()以检查结构类型。

于 2016-08-04T15:41:30.080 回答