在Go中,有没有办法从另一个包中访问结构的私有字段? [英] In Go, is there any way to access private fields of a struct from another package?

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

问题描述

 包foo 

类型Foo struct {
x int
y * Foo
}

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

 包栏

import../foo

func change_foo(f * Foo){
fy = nil
}

有没有办法将 bar 声明为某种朋友包或其他任何方式能够从 bar 访问 foo.Foo 的私人成员,但仍然保留所有其他包的私有成员(可能在不安全)?

解决方案

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

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

但是,尝试使用y.Set或以其他方式设置字段将导致代码恐慌,你试图在包之外设置一个未导出的字段。



简而言之:如果你需要,未导出的字段应该是未导出的改变他们要么把需要改变它的东西放在同一个包中,要么暴露/导出一些安全的方式来改变它。



也就是说,为了完全回答这个问题,你可以做到这一点

  func change_foo(f * Foo){
//由于结构按内存顺序组织,我们可以按照字段大小提前指针
//直到我们到达所需的成员。对于y,我们前进了8
//因为它是64位机器上int的大小,而intx在Foo的表示中是第一个
//。
//
//如果你想改变x,你根本不会推进指针,只需
//就需要将ptrTof转换为类型(* int)
ptrTof:= unsafe.Pointer(f)
ptrTof = unsafe.Pointer(uintptr(ptrTof)+ uintptr(8))//或者4,如果这是32位

ptrToy:=(** Foo)(ptrTof)
* ptrToy = nil //或* ptrToy =& Foo {}或任何您想要的

}

这真是一个非常糟糕的主意。它不是可移植的,如果int的大小发生了变化,它将会失败,如果你重新排列Foo中的字段顺序,改变它们的类型或者它们的大小,或者在预先存在的字段之前添加新的字段,这个函数会快速地改变在没有告诉你的情况下对随机乱码数据进行新的表示。我也认为它可能会打破此块的垃圾回收。



请注意,如果您需要从包外部更改字段,请写入功能以从包或出口它。



编辑:这是一个更安全的方式来做到这一点:

  func change_foo(f * Foo){
//注意,只需要执行reflect.ValueOf(* f)将不起作用,需要执行
pointerVal:= reflect.ValueOf( f)
val:= reflect.Indirect(pointerVal)
$ b $ member:= val.FieldByName(y)
ptrToY:= unsafe.Pointer(member.UnsafeAddr() )
realPtrToY:=(** Foo)(ptrToY)
* realPtrToY = nil //或& Foo {}或任何

}

这样更安全,因为它总能找到正确的命名字段,但它仍然不友好,可能很慢,而且我不确定如果它与垃圾收集混乱。它也不会警告你,如果你做了一些奇怪的事情(你可以通过添加一些检查来使这个代码更安全一些,但我不会感到麻烦,这使得这个要点足够好)。

另外请记住,FieldByName易受软件包开发人员更改变量名称的影响。作为一名软件包开发人员,我可以告诉你,我绝对没有关于更改用户应该不知道的事情名称的疑虑。你可以使用Field,但是你很容易被开发人员改变字段的顺序而没有任何警告,这也是我对此没有任何疑虑的。请记住,这种反映和不安全的组合是......不安全的,与正常的名称更改不同,这不会导致编译时错误。相反,程序会突然惊慌失措,或者做一些奇怪而未定义的事情,因为它得到了错误的领域,这意味着即使你是名称变更的软件包开发人员,你仍然可能不记得你做过这些诀窍,并花费一段时间为什么你的测试突然中断,因为编译器没有抱怨。 Edit2:自从您提到White Box测试以来,请注意,如果您在您的目录中命名一个文件,那么您是否提到这是一个糟糕的主意?
$ b < whatever> _test.go
除非您使用 go test ,否则它将不会编译,所以如果您想要进行白盒测试,顶部声明软件包< yourpackage> 这将允许您访问未导出的字段,如果您想进行黑盒测试,那么您可以使用 package<你的包> _test



如果你需要同时对两个包进行白盒测试,我认为你可能会被卡住,需要重新考虑你的设计。


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
}

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())
}

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

}

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.

Edit: 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).

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: 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.

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

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