1

我不是要拖钓,但我真的不明白。为什么语言设计者会允许私有方法而不是一些命名约定(参见 Python 中的 __)?
我搜索了答案,通常的论点是:
a)使实现更清晰/避免 IDE 自动完成中的长长的垂直方法列表
b)向世界宣布哪些方法是公共接口,哪些方法可能会改变并且仅用于实现目的
c )可读性

好的,现在,所有这些都可以通过使用 __ 前缀或“private”关键字命名所有私有方法来实现,除了 IDE 的信息(不要将它们放在自动完成中)和其他程序员(除非你真的必须,否则不要使用它)。地狱,甚至可能需要 unsafe-like 关键字来访问私有方法来真正阻止这种情况。
我问这个是因为我使用了一些 c# 代码,并且出于测试目的,我不断将私有方法更改为公共方法,因为许多中间私有方法(例如用于 xml 序列化的字符串生成器)对于调试目的非常有用(例如编写字符串的某些部分记录文件等)。
所以我的问题是:
有没有什么是通过访问限制实现的,但在不限制访问的情况下无法通过命名约定来实现?

4

6 回答 6

10

您提出了几个问题/问题,因此我将分别处理每个问题。

如何测试私有方法?

除了是否应该测试私有方法的辩论/讨论之外,还有一些方法可以做到这一点。

重构
一个广泛的普遍答案是,您可以将行为重构为原始类利用的单独的可测试类。这是有争议的,并不总是适用,具体取决于您的设计或这样做的特权。

InternalsVisibleTo
一个常见的例程是将可测试的逻辑提取到方法中并将其标记为internal. 在您的程序集属性中,您可以添加一个属性[InternalsVisibleTo("MyUnitTestingProject")]。这将允许您从单元测试项目中访问该方法,同时仍然隐藏对所有其他程序集的访问。http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

但是,鉴于您的评论,您无法在工作场所永久更改源代码的结构;您正在更改访问器以进行测试,测试,然后在提交之前将它们更改回来。在这种情况下,有两种选择:

部分测试类
如果您将类标记为partial. 创建第二个部分类文件,该文件将包含您的测试(或私有成员的公共包装器)。然后当需要合并/提交时,只需从项目中删除您的部分类并partial从主类中删除关键字。此外,您可以使用if DEBUG(或其他指令)包装整个测试代码文件,以便它仅在单元测试时可用并且不会影响生产/开发代码。 http://weblogs.asp.net/ralfw/archive/2006/04/14/442836.aspx

public partial class MyClass
{
    private string CreateTempString()
    {
        return "Hello World!";
    }
}

#if DEBUG

public partial class MyClass //in file "MyClass_Accessor.cs"
{
    public string CreateTempString_Accessor()
    {
        return CreateTempString();
    }
}

#endif

反射
你仍然可以通过反射访问私有成员: public class Test { private string PrivateField = "private"; }

Test t = new Test();
var publicFieldInfo = typeof(Test).GetField("PrivateField", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine(publicFieldInfo.GetValue(t)); //outputs "private"

您的单元测试可以通过这种方式提取类中的私有/隐藏数据。事实上,微软提供了两个类来为你做这件事:PrivateObjectPrivateType

鉴于您的内部开发流程限制,这可能是您最好的选择,因为您将能够在主项目库之外管理自己的测试,而无需更改核心代码中的任何内容。

请注意,Silverlight(以及可能的其他 Core-CLR 运行时)在反射期间严格执行非公共访问,因此此选项在这些情况下不适用。

所以,有几种方法可以测试私人成员,我敢肯定还有一些更聪明/不那么聪明的方法潜伏在那里。


是否可以通过使用 __ 前缀命名所有私有方法或引入私有但可访问的访问修饰符来实现所有这些好处?

您(引用其他人)引用的好处是:

  1. 为了使实现更清晰/避免 IDE 自动完成中的长长的垂直方法列表
  2. 向全世界宣布哪些方法是公共接口,哪些方法可能会改变并且仅用于实现目的
  3. 可读性

现在您补充说,这些都可以通过 __通过更改语言规范和/或支持私有但可访问访问修饰符的 IDE 来实现,可能使用一些类似 unsafe 的关键字来阻止这一点。我认为就更改语言和 IDE 的当前功能/行为(可能对 StackOverflow 没有意义)进行辩论是不值得的,因此关注可用的内容:

1) 更简洁的实现和智能感知
Visual Studio IDE(我不能代表 MonoDevelop)确实支持在使用 [EditorBrowsableAttribute] 标记成员时从智能感知中隐藏成员。但这仅在开发人员在其 Visual Studio 选项中启用“隐藏高级成员”选项时才有效。(请注意,当您在同一个程序集中工作时,它不会抑制智能感知中的成员) http://msdn.microsoft.com/en-us/library/system.componentmodel.editorbrowsableattribute.aspx

因此,将公共成员标记为这样会使其(智能感知)表现为内部(不支持 [InternalsVisibleTo])。因此,如果您在同一个程序集中,或者如果您没有Hide Advanced Members启用,您仍然会在智能感知中看到一长串 __ 成员。即使您将其从智能感知中隐藏,它仍然可以根据其当前的访问修饰符完全访问。

2) 公共使用接口/约定
这假定 C#、Visual Basic、F#、C++.NET 和任何 .NET 开发世界中的所有开发人员都将采用相同的 __ 命名约定,并在编译和交换程序集时遵守它开发商之间。也许如果您在 IronPython 中编写脚本,您可以摆脱它,或者如果您的公司内部采用这种方法。但一般来说,这不会发生,.NET 开发人员可能会犹豫利用采用这种约定的库,因为这不是一般的 .NET 文化。

3) 可读性
这与#2 一致,因为“可读性”取决于文化和该领域的开发人员期望什么;这当然是有争议的和主观的。我敢打赌,大多数 C# 开发人员发现严格/强制封装可以显着提高代码可读性,我敢肯定他们中的很大一部分会发现 __ 经常使用会减损这一点。(另一方面,我确信开发人员为私有字段采用 _ 或 __ 前缀并仍然保持私有的情况并不少见)

但是,C# 中的可读性和封装不仅仅是公共/私有访问器。在 C# 中,有 private、public、protected internal、protected 和 internal(我错过了一个吗?)每个都有自己的用途,并为开发人员提供不同的信息。现在我不确定您将如何仅通过 __ 与这些访问器进行通信。建议单下划线是受保护的,双下划线是私有的,肯定会妨碍可读性。


有没有什么是通过访问限制实现的,而在不限制访问的情况下通过命名约定无法实现?

如果你问为什么C# 设计团队会走这条路,那么我想你总有一天要问 Hejlsberg 先生。我知道他们正在创建一种收集 C/C++ 最好部分的语言,并强烈关注面向对象原则的原则。

至于通过访问修饰符强制访问来实现什么:更能保证 API 消费者的正确访问。如果您的类使用MakeTempStringForXMLSerialization将字符串存储为类属性以进行序列化的方法,但出于性能原因放弃了昂贵的检查(因为您,作为开发人员已完成单元测试以确保所有类的字段都将通过公共 API 有效)然后第三方做了一些可爱的垃圾进垃圾出,他们会责怪你和/或供应商的劣质图书馆。这公平吗?不必要; 他们把垃圾放进去,但现实是很多人仍然会责怪供应商。

对于试图了解 API 工作原理的新开发人员来说,这有助于简化他们的体验。是的,开发人员应该阅读文档,但如果公共 API 是直观的(通常应该如此)并且不会暴露大量不应访问的成员,那么它就不会那么混乱,而且他们不太可能意外地喂垃圾进入系统。它还将降低让开发人员轻松有效地使用和利用您的 API 的开销。当涉及到您发布的 API 的任何更新,您希望更改/重构/改进您的内部实现细节时,情况尤其如此。

因此,从商业角度来看,它可以保护他们免受责任和与客户的不良关系,并且更能吸引开发商购买和投资。

现在这一切都可能是这种情况,正如你所说,如果每个人都遵循__不应该在课堂之外访问成员的约定,或者unsafe在你说的地方提供一些标记,“如果你这样做,它会破坏,这不是我的错! " 那么您与 C# .NET 开发不在同一个星球上。C# 提供的访问器提供了该__约定,但确保所有开发人员都遵守它。

有人可能会争辩说,受限访问是一种错觉,因为消费者可以通过反射来解决它(如上所示),因此访问修饰符和 __ (或其他)符号之间实际上没有编程差异。(尽管在 Silverlight/Core-CLR 上,肯定存在程序上的差异!)但是开发人员访问这些私有字段所要做的工作是你给消费者一扇敞开的门,上面写着“不要进去” “(你希望他们能读懂)和一扇带锁的门,他们必须砸下来。

那么它到底提供了什么? 对成员的标准化、强制访问,其中 as__提供对成员的非标准化、非强制访问。此外,__缺乏各种可用访问修饰符提供的描述范围。

更新(2013 年 1 月 2 日)

我知道已经半年了,但我一直在阅读 C# 语言规范,并从第2.4.2 节标识符中发现了这个小宝石,其中指出:

包含两个连续下划线字符 (U+005F) 的标识符保留供实现使用。例如,一个实现可能会提供以两个下划线开头的扩展关键字。

我想不会发生任何不好的事情,如果你这样做,很可能不会发生灾难性的破坏。但这只是规范建议您不要在代码中使用双下划线时应该考虑的另一个方面。

于 2012-07-05T02:14:47.907 回答
4

私有方法存在的原因是为了提供封装

这允许您提供您希望您的对象与之交互的公共合约,但您的内部逻辑和状态以重构不会影响消费者的方式封装。

例如,您可以提供公共命名属性,但决定将状态存储在 Dictionary 中,类似于类型化 DataSet 所做的。

它并不是真正的“安全”功能(因为您总是有反射来覆盖它),而是一种将公共 API 与内部实现分开的方法。没有人应该“依赖”您的内部和私有实现,他们应该只依赖于您的公共(或受保护)实现。

现在,关于单元测试,通常不希望测试内部实现。但是,一种常见的方法是将其声明为内部并通过InternalsVisibleToAttributeChris 提到的那样为测试程序集提供访问权限。

此外,公共、私有和受保护之间的明确区别对于继承非常有用,可用于定义您期望可覆盖的内容和不应该覆盖的内容。

于 2012-07-05T02:35:34.700 回答
1

通常,将字段或方法标记为私有并没有真正意义。它提供了一种人为的安全感。所有的代码都在同一个进程中运行,大概是由彼此友好的人编写的。为什么他们需要访问控制来保护彼此的代码?这听起来像是一个需要解决的文化问题。

文档提供了比私有/受保护/公共更多的信息,一个好的系统无论如何都会有文档。用它来指导你的开发。如果您将一个方法标记为“私有”,而开发人员仍然调用它,那是一个糟糕的开发人员,他最终会以其他方式破坏您的系统。修复那个开发者。

访问控制最终会妨碍开发,而您花时间只是让编译器满意。

当然,如果您严格地谈论 C#,那么您应该坚持将方法标记为私有,因为其他 C# 开发人员会期望它。

于 2012-07-05T02:20:41.630 回答
1

它隐藏了很多内部细节,特别是对于可能真正想要隐藏这些细节的库实现者。

请记住,那里有商业图书馆正在出售。他们在界面中只向用户展示了非常有限的一组选项,并且他们希望将其余所有选项都隐藏起来。

如果您设计的语言不提供此选项,那么您正在制作一种几乎专门用于开源项目(或主要用于脚本)的语言。不过,我对 Python 了解不多,如果知道是否有用 Python 编写的商业闭源库会很有趣。

你提到的理由也足够好。

于 2012-07-05T02:24:46.100 回答
1

如果您要发布具有公共成员的库,则用户可以使用这些成员。您可以使用命名约定来告诉他们不应该这样做,但有些人会这样做。

如果您的“内部”成员是公开的,那么您就不可能保证与旧版本的库的兼容性,除非您保持所有内部结构相同并且只进行非常小的更改。这意味着使用您的库的每个程序都必须使用它编译时所针对的版本(并且很可能通过发布自己的版本而不是使用共享系统版本来做到这一点)。

当您的图书馆有安全更新时,这会成为一个问题。充其量,所有程序都使用不同的共享版本,并且您必须将安全更新反向移植到野外的每个版本。更有可能的是,每个程序都有自己的版本,除非该程序的作者这样做,否则根本不会获得更新。

如果您仔细设计您的公共 API,并为您可能更改的内容设置私有成员,您可以将破坏性更改保持在最低限度。然后所有程序/库都可以使用最新版本的库,即使它们是为早期版本编译的,它们也可以从更新中受益。

于 2012-07-05T02:34:34.127 回答
0

这可能会引发一些有趣的辩论。标记私有方法(和成员变量)的一个主要原因是强制信息隐藏。外部代码不应该知道或关心你的类是如何工作的。您应该可以自由更改您的 impl 而不会破坏其他内容。这是成熟的软件实践。

然而在现实生活中,私有方法可能会妨碍测试和需要发生的不可预见的变化。Python 坚信“我们都是同意的成年人”并且不鼓励使用私有方法。因此,有时我只会使用命名约定和/或注释“不打算供公众使用”。这是 OO 理论与现实问题的另一个问题。

于 2012-07-05T02:19:46.433 回答