有什么方法可以从另一个包访问结构的私有字段吗? [英] Is there any way to access private fields of a struct from another package?

查看:86
本文介绍了有什么方法可以从另一个包访问结构的私有字段吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个包含私有字段的包中有一个结构:

I have a struct in one package that has private fields:

package foo

type Foo struct {
    x int
    y *Foo
}

另一个软件包(例如,白盒测试软件包)需要访问它们:

And another package (for example, a white-box testing package) needs access to them:

package bar

import "../foo"

func change_foo(f *Foo) {
    f.y = nil
}

是否可以将bar声明为某种朋友"包,或者可以通过其他任何方式从bar访问foo.Foo的私有成员,但仍然对其他所有成员保持私有状态包(也许是unsafe中的内容)?

Is there a way to declare bar to be a sort of "friend" package or any other way to be able to access foo.Foo's private members from bar, but still keep them private for all other packages (perhaps something in unsafe)?

推荐答案

一种使用反射来读取未导出成员的方法

There is a way to read unexported members using reflect

func read_foo(f *Foo) {
    v := reflect.ValueOf(*f)
    y := v.FieldByName("y")
    fmt.Println(y.Interface())
}

但是,尝试使用y.Set或以其他方式设置反射字段将导致代码恐慌,因为您试图在软件包外部设置未导出的字段.

However, trying to use y.Set, or otherwise set the field with reflect will result in the code panicking that you're trying to set an unexported field outside the package.

简而言之:出于某种原因,应该取消导出未导出的字段,如果您需要更改它们,可以将需要对其进行更改的内容放在同一包中,或者公开/导出一些安全的更改方法.

In short: unexported fields should be unexported for a reason, if you need to alter them either put the thing that needs to alter it in the same package, or expose/export some safe way to alter it.

也就是说,为了完全回答问题,您可以这样做

That said, in the interest of fully answering the question, you can do this

func change_foo(f *Foo) {
    // Since structs are organized in memory order, we can advance the pointer
    // by field size until we're at the desired member. For y, we advance by 8
    // since it's the size of an int on a 64-bit machine and the int "x" is first
    // in the representation of Foo.
    //
    // If you wanted to alter x, you wouldn't advance the pointer at all, and simply
    // would need to convert ptrTof to the type (*int)
    ptrTof := unsafe.Pointer(f)
    ptrTof = unsafe.Pointer(uintptr(ptrTof) + uintptr(8)) // Or 4, if this is 32-bit

    ptrToy := (**Foo)(ptrTof)
    *ptrToy = nil // or *ptrToy = &Foo{} or whatever you want

}

这是一个非常非常糟糕的主意.它不是可移植的,如果进行大小更改,它将失败;如果您重新排列Foo中的字段顺序,更改其类型或大小,或在现有字段之前添加新字段,此功能将极大地更改无需告知您即可随意处理乱码数据的新表示形式.我还认为这可能会破坏此块的垃圾回收.

This is a really, really bad idea. It's not portable, if int ever changes in size it will fail, if you ever rearrange the order of the fields in Foo, change their types, or their sizes, or add new fields before the pre-existing ones this function will merrily change the new representation to random gibberish data without telling you. I also think it might break garbage collection for this block.

请,如果您需要从程序包外部更改字段,请编写功能以从程序包内部进行更改或将其导出.

Please, if you need to alter a field from outside the package either write the functionality to change it from within the package or export it.

这是一种较为安全的方法:

Here's a slightly safer way to do it:

func change_foo(f *Foo) {
    // Note, simply doing reflect.ValueOf(*f) won't work, need to do this
    pointerVal := reflect.ValueOf(f)
    val := reflect.Indirect(pointerVal)

    member := val.FieldByName("y")
    ptrToY := unsafe.Pointer(member.UnsafeAddr())
    realPtrToY := (**Foo)(ptrToY)
    *realPtrToY = nil // or &Foo{} or whatever

}

这是更安全的方法,因为它将始终找到正确的命名字段,但是它仍然不友好,可能很慢,而且我不确定它是否与垃圾回收弄乱了.如果您做的很奇怪,它也不会警告您(您可以通过添加一些检查使此代码更安全些,但我不会打扰,这可以使主旨足够好).

This is safer, as it will always find the correct named field, but it's still unfriendly, probably slow, and I'm not sure if it messes with garbage collection. It will also fail to warn you if you're doing something weird (you could make this code a little safer by adding a few checks, but I won't bother, this gets the gist across well enough).

还请记住,FieldByName容易受到程序包开发人员更改变量名称的影响.作为程序包开发人员,我可以告诉您,对于更改用户不应该知道的名称,我绝对不满.您可以使用Field,但是您很容易受到开发人员更改字段顺序的影响,而不会发出警告,这也是我对此毫无疑虑的事情.请记住,反射和不安全的这种组合是...不安全的,与普通名称更改不同,这不会给您带来编译时错误.取而代之的是,由于程序输入错误,该程序将突然感到恐慌或执行一些奇怪且未定义的操作,这意味着即使您是更改名称的程序包开发人员,您仍可能不记得您在任何地方都做了此技巧并花了一些时间来跟踪解释为什么测试突然中断是因为编译器没有抱怨.我是否提到这是个坏主意?

Also keep in mind that FieldByName is susceptible to the package developer changing the name of the variable. As a package developer, I can tell you that I have absolutely no qualms about changing the names of things users should be unaware of. You could use Field, but then you're susceptible to the developer changing the order of the fields with no warning, which is something I also have no qualms about doing. Keep in mind that this combination of reflect and unsafe is... unsafe, unlike normal name changes this won't give you a compile time error. Instead, the program will just suddenly panic or do something weird and undefined because it got the wrong field, meaning even if YOU are the package developer that did the name change, you still may not remember everywhere you did this trick and spend a while tracking down why your tests suddenly broke because the compiler doesn't complain. Did I mention that this is a bad idea?

Edit2:既然提到了白盒测试,请注意,如果您在目录<whatever>_test.go中命名文件,除非使用go test,否则它将不会编译,因此,如果要进行白盒测试,请在顶部声明package <yourpackage>,这将使您可以访问未导出的字段,如果要进行黑盒测试,则可以使用package <yourpackage>_test.

Since you mention White Box testing, note that if you name a file in your directory <whatever>_test.go it won't compile unless you use go test, so if you want to do white box testing, at the top declare package <yourpackage> which will give you access to unexported fields, and if you want to do black box testing then you use package <yourpackage>_test.

但是,如果您需要同时对两个软件包进行白盒测试,我认为您可能会陷入困境,可能需要重新考虑您的设计.

If you need to white box test two packages at the same time, however, I think you may be stuck and may need to rethink your design.

这篇关于有什么方法可以从另一个包访问结构的私有字段吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆