如何获取一个被封装为接口的变量的指针?

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

How to get a pointer to a variable that's masked as an interface?

问题

我不愿意深入探讨下面情况的原理。它涉及到对一个序列化对象进行解组,该对象可以是一组固定类型中的任何一个,但你不知道具体是哪种类型。

我有以下类型:

type I interface {
	Do()
}

type someI struct{}
func (i *someI) Do() {}

type otherI struct{}
func (i *otherI) Do() {}

所以,有两个结构体它们的指针都实现了接口I

现在我有一个方法,想要返回类型为I的值:

func GetSomeI(marshalled []byte) (I, error) {
	var obj interface{}
	// 下面的方法会神奇地将someI或otherI的实例放入obj中。
	magicUnmarshall(marshalled, obj)
	// 现在的问题是我们不能返回obj,因为原始结构体并没有实现I接口。

	// 一种解决方法是使用类型断言:
	switch obj.(type) {
	case someI:
		i := obj.(someI)
		return &i, nil
	case otherI:
		i := obj.(otherI)
		return &i, nil
	default:
		return nil, errors.New("marshalled object was not of type I")
	}

	// 但是现在考虑一下有很多不同的I接口实现的情况。
	// 我们更希望有一种通用的方法来获取obj的引用。
}
英文:

I prefer not to dive into the rationale of the situation below. It has to do with unmarshaling an serialized object that can be any of a fixed set of types, but you don't know which type.

I have the following types:
<!-- language-all: lang-go -->

type I interface {
	Do()
}

type someI struct {}
func (i *someI) Do() {}

type otherI struct {}
func (i *otherI) Do() {}

So, two structs of which the pointers implement interface I.

Now I have this method that wants to return a value of type I:

func GetSomeI(marshalled []byte) (I, error) {
	var obj interface{}
	// The following method magically puts an instance
	// of either someI or otherI into obj.
	magicUnmarshall(marshalled, obj)
	// The problem now is that we cannot return obj,
	// because the raw structs don&#39;t implement I.

	// One solution would be to do a type switch like this:
	switch obj.(type) {
	case someI:
		i := obj.(someI)
		return &amp;i, nil
	case otherI:
		i := obj.(otherI)
		return &amp;i, nil
	default:
		return nil, errors.New(&quot;marschalled object was not of type I&quot;)
	}

	// But now consider the case that there are quite some
	// different implementations of I.
	// We would prefer to have a general way of getting
	// a reference to obj.
}

答案1

得分: 3

要判断一个包裹在interface{}中的值是否实现了某个其他接口(I),你可以简单地使用类型断言

请注意,你必须传递你想要解包结果的变量的地址。

为了演示,让我们使用以下的magicUnmarshal()函数:

func magicUnmarshal(what int, obj interface{}) {
    v := reflect.ValueOf(obj).Elem()
    switch what {
    case 0:
        v.Set(reflect.ValueOf(&someI{}))
    case 1:
        v.Set(reflect.ValueOf(&otherI{}))
    case 2:
        v.Set(reflect.ValueOf("just a string"))
    case 3:
        v.Set(reflect.ValueOf(someI{}))
    case 4:
        v.Set(reflect.ValueOf(otherI{}))
    }
}

请注意,case 3case 4返回的是非指针类型。

你的GetSomeI()实现可以是:

func GetSomeI(what int) (I, error) {
    var obj interface{}
    magicUnmarshal(what, &obj)

    // 尝试原值:
    if i, ok := obj.(I); ok {
        return i, nil
    }

    // 没有成功。尝试值的指针:
    v := reflect.Indirect(reflect.New(reflect.TypeOf(obj)))
    v.Set(reflect.ValueOf(obj))
    pobj := v.Addr().Interface()
    if i, ok := pobj.(I); ok {
        return i, nil
    }

    return nil, fmt.Errorf("%T does not implement I!", obj)
}

首先,GetSomeI()测试从magicUnmarshal()得到的值是否实现了I,如果是,则直接使用。如果不是,我们使用反射构造一个新的值,并获取其地址(一个指向值的指针),然后进行测试。如果该指针实现了I,我们返回它。

进行测试:

func main() {
    for what := 0; what < 5; what++ {
        i, err := GetSomeI(what)
        fmt.Printf("%T %v\n", i, err)
    }
}

输出结果是(在Go Playground上尝试):

*main.someI <nil>
*main.otherI <nil>
<nil> string does not implement I!
*main.someI <nil>
*main.otherI <nil>
英文:

To tell if a value wrapped in an interface{} implements some other interface (I), you may simply use a type assertion.

Note that you must pass the address of the variable you want results unmarshaled to.

For demonstration purposes, let's use the following magicUnmarshal() function:

func magicUnmarshal(what int, obj interface{}) {
	v := reflect.ValueOf(obj).Elem()
	switch what {
	case 0:
		v.Set(reflect.ValueOf(&amp;someI{}))
	case 1:
		v.Set(reflect.ValueOf(&amp;otherI{}))
	case 2:
		v.Set(reflect.ValueOf(&quot;just a string&quot;))
	case 3:
		v.Set(reflect.ValueOf(someI{}))
	case 4:
		v.Set(reflect.ValueOf(otherI{}))
	}
}

Note that case 3 and case 4 are returning non-pointers.

Your GetSomeI() implementation can be:

func GetSomeI(what int) (I, error) {
	var obj interface{}
	magicUnmarshal(what, &amp;obj)

	// Try the value as-is:
	if i, ok := obj.(I); ok {
		return i, nil
	}

	// No success. Try a pointer to the value:
	v := reflect.Indirect(reflect.New(reflect.TypeOf(obj)))
	v.Set(reflect.ValueOf(obj))
	pobj := v.Addr().Interface()
	if i, ok := pobj.(I); ok {
		return i, nil
	}

	return nil, fmt.Errorf(&quot;%T does not implement I!&quot;, obj)
}

First GeSomeI() tests if the value got form magicUnmarshal() implements I, and if so, it is used as-is. If not, we construct a new using reflection, and get its address (a pointer to a value), and we test that. If that pointer implements I, we return it.

Testing it:

func main() {
	for what := 0; what &lt; 5; what++ {
		i, err := GetSomeI(what)
		fmt.Printf(&quot;%T %v\n&quot;, i, err)
	}
}

And the output is (try it on the Go Playground):

*main.someI &lt;nil&gt;
*main.otherI &lt;nil&gt;
&lt;nil&gt; string does not implement I!
*main.someI &lt;nil&gt;
*main.otherI &lt;nil&gt;

huangapple
  • 本文由 发表于 2016年12月16日 19:38:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/41183561.html
匿名

发表评论

匿名网友

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

确定