C#中的“friend”对应什么?

227

可能有重复:
为什么C#不提供类似C++的“friend”关键字?

我希望在不将私有成员变量暴露给其他类的情况下,让Tester类可以访问类的私有成员变量。

在 C++ 中,我只需将Tester类声明为友元类即可,那么在C#中该如何实现?能否给出一个示例?


3
直接测试私有成员就像它们是公共成员一样是不正确的。通常,人们只测试公共成员。私有成员会在间接测试中被测试到。参考链接:http://stackoverflow.com/a/5662371/62921 - ForceMagic
5
这并不是重复的问题。另一个问题在问为什么缺失,而不是如何避免它。 - DanielV
1
用于测试 - 使用私有访问器 - https://dev59.com/6nVC5IYBdhLWcg3wjyDu#4052474 对于工厂模式 - 将方法标记为“internal”或“protected internal” - https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers - zendu
1
这不是重复的问题。另一个问题是“基于观点的”(这就是为什么一些心怀恶意的人决定关闭那个问题)。我不知道为什么没有帮助的穴居人决定关闭这个问题 - 它既不是重复的,也不是基于观点的。 - Ian Boyd
@IanBoyd,“穴居人”这个词用得有点过了。请友善一点 - Chris
当然,在单元测试中应该测试内部状态。 - Kaitain
5个回答

302

“朋友”一词没有直接的等价物——最接近的是InternalsVisibleTo(它与“朋友”的相似程度不是很高)。我只在测试时使用过此特性,非常方便!

示例:应放置在AssemblyInfo.cs中。

[assembly: InternalsVisibleTo("OtherAssembly")]

19
有趣的一点是,在VB.NET中,“Internal”仍然被称为“Friend”。 - Jeff
1
MSDN表示:“一个程序集中的所有非公共类型对另一个程序集是可见的。” 多么丑陋啊! 谁会使用那种胡言乱语呢? 我更喜欢RobG的答案。 - Elmue
5
@Elmue:MSDN有误。例如,私有嵌套类型是不可见的。而且内部成员只对指定的程序集可见,这并不意味着它会使所有内容都公开。相较于反射,我更喜欢使用这种方式,因为它更易于重构和编译时检查。 - Jon Skeet
好的。我们最好说这两种方法都是丑陋的解决方案。原始问题相当于友元类。对我来说,微软没有实现这一点非常奇怪。我想让一个类在同一个程序集中访问另一个类。我该怎么办? - Elmue
3
把这个类设为内部类就可以了。是的,这种方法比“友元”更加粗略,有时很有用,有时则不太有用。对我个人而言,这并不影响。 - Jon Skeet
显示剩余7条评论

121

最接近的相当于创建一个嵌套类,它将能够访问外部类的私有成员。类似于这样:

class Outer
{
    class Inner
    {
       // This class can access Outer's private members
    }
}

或者如果您更喜欢将内部类放在另一个文件中:

Outer.cs
partial class Outer
{
}


Inner.cs
partial class Outer
{
    class Inner
    {
       // This class can access Outer's private members
    }
}

11
请注意,如果变量不是静态的(或者const),想要访问外部类中的成员,仍然需要引用Outer类的实例。根据这里所接受的答案:https://dev59.com/6k7Sa4cB1Zd3GeqP6cjf - Itison
4
Doh!内部类不允许使用扩展方法。 - Jess
2
我认为这是一个非常有趣的想法。如果我们可以接受“partial”关键字,那么我们可以允许其他类(repository、factory等)访问我们的私有成员。更重要的是,该类被迫明确表明它正在这样做(即将自己包装在它正在访问的类的部分类中)。 - Timo
1
@JohnThoits 我不明白你的方法的意义,具体来说,“这样外部代码就无法滥用嵌套类”和“这样外部类就完全控制了谁可以访问嵌套类”,因为你会在外部类中使用内部类并将其隐藏起来,而不是反过来。所以除非你在外部类中有内部类的引用,否则任何成员都可以被访问。如果外部类有内部类的引用,则访问级别与未嵌套的类相同。你的抽象+继承方法是否改变了这一点? - rustyBucketBay
1
感谢 @JohnThoits 的澄清,我认为我理解了你的抽象类和内部类引用公开方法。但是,如果您有内部类实例的引用,将其作为公共属性放在 class1 中以获取/设置 class2 属性是否足够?fiddle - rustyBucketBay
显示剩余9条评论

48

假设有一个常见的模式。 类工厂制造小部件。 工厂类需要操作内部,因为它是工厂。 两者都在同一个文件中实现,并且根据设计和愿望以及本质,这些紧密耦合的类 - 实际上,Widget只是工厂的一种输出类型。

在C++中,使工厂成为Widget类的友元。

在C#中,我们可以做什么? 唯一好的解决方案是发明一个接口,IWidget,只公开公共方法,并使工厂返回IWidget接口。

这涉及相当多的乏味工作-在接口中再次公开所有通常公开的属性。


2
这是一个好的设计模式,但在单元测试时通常不是一个好的解决方案,因为您需要对正在测试的类的构造有精细的控制,并且该类应该与其他依赖项隔离。如果我的工厂将其他具体依赖项注入到我正在测试的类中,那么我会遇到麻烦。 - andrensairr

16

C#中没有'friend'关键字,但是一个测试私有方法的选项是使用System.Reflection获取方法句柄。这将允许您调用私有方法。

假设有这样一个类定义:

public class Class1
{
    private int CallMe()
    {
        return 1;
    }
}

您可以使用以下代码调用它:

Class1 c = new Class1();
Type class1Type = c.GetType();
MethodInfo callMeMethod = class1Type.GetMethod("CallMe", BindingFlags.Instance | BindingFlags.NonPublic);

int result = (int)callMeMethod.Invoke(c, null);

Console.WriteLine(result);

如果您正在使用Visual Studio Team System,则可以通过右键单击方法并选择“创建单元测试…”来使VS自动在其中生成具有私有访问器的代理类。


4
反射会带来较高的性能开销,并且通常会导致代码难以阅读。如果有选择的话,采用“部分类”方法会更好。 - AFract
4
从.NET 4.5.1开始,反射被重新编写,不再影响性能。在此之前的版本中,情况是这样的。不幸的是,CLR团队没有很好地传达这个事实。尝试测试一些在4.5.1之前编译的大量反射代码,然后在.NET 4.5.1+下重新测试完全相同的代码,你会看到巨大的差异。 - Dave Black

7

如果拥有权限访问的类位于另一个包内并且你要公开的方法标记为 internal 或 internal protected,那么你可以模拟朋友访问。

你需要修改想要共享的程序集,并在 AssemblyInfo.cs 中添加以下设置:

// Expose the internal members to the types in the My.Tester assembly
[assembly: InternalsVisibleTo("My.Tester, PublicKey=" +
"012700000480000094000000060200000024000052534131000400000100010091ab9" +
"ba23e07d4fb7404041ec4d81193cfa9d661e0e24bd2c03182e0e7fc75b265a092a3f8" +
"52c672895e55b95611684ea090e787497b0d11b902b1eccd9bc9ea3c9a56740ecda8e" +
"961c93c3960136eefcdf106955a4eb8fff2a97f66049cd0228854b24709c0c945b499" +
"413d29a2801a39d4c4c30bab653ebc8bf604f5840c88")]

公钥是可选的,这取决于您的需求。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接