在C++朋友关键字允许class A
指定class B
作为其朋友.这允许Class B
访问private
/的protected
成员class A
.
我从来没有读过任何关于为什么这个被排除在C#(和VB.NET)之外的东西.早期StackOverflow问题的大多数答案似乎都说它是C++的一个有用部分,并且有充分的理由使用它.根据我的经验,我必须同意.
在我看来,另一个问题是如何friend
在C#应用程序中执行类似的操作.虽然答案通常围绕嵌套类,但它看起来并不像使用friend
关键字那么优雅.
最初的设计模式书在其示例中定期使用它.
总而言之,为什么friend
缺少C#,以及在C#中模拟它的"最佳实践"方式(或方法)是什么?
(顺便说一句,该internal
关键字不一样的东西,它可以让所有的整个组件来访问内部的阶级internal
的成员,同时friend
允许你给某一类的完全访问权限,以正好一个其他类)
在旁注.使用朋友不是违反封装,而是相反,它是关于强制执行它.与访问者+变更器,运算符重载,公共继承,向下转换等一样,它经常被误用,但这并不意味着关键字没有或者更糟糕的是坏的目的.
请参阅另一个主题中的Konrad Rudolph的消息,或者如果您愿意,请参阅C++ FAQ中的相关条目.
让编程中的朋友或多或少地被认为是"肮脏的"并且容易被滥用.它打破了类之间的关系,破坏了OO语言的一些基本属性.
话虽如此,这是一个很好的功能,我自己在C++中使用了很多次; 并且也希望在C#中使用它.但我打赌因为C#的"纯粹"OOness(与C++的伪OOness相比)MS决定因为Java没有朋友关键字C#也不应该(开玩笑;))
严肃地说:内部不如朋友好,但确实完成了工作.请记住,您很少会通过DLL将代码分发给第三方开发人员; 因此,只要您和您的团队了解内部课程及其使用情况,您就应该没问题.
编辑让我澄清一下friend关键字如何破坏OOP.
私有和受保护的变量和方法可能是OOP最重要的部分之一.对象可以保存只有他们可以使用的数据或逻辑的想法允许您编写独立于您的环境的功能实现 - 并且您的环境不能改变它不适合处理的状态信息.通过使用朋友,您将两个类的实现耦合在一起 - 如果您只是将它们的接口耦合在一起则更糟糕.
有关信息,.NET中的另一个相关但不完全相同的东西是[InternalsVisibleTo]
,它允许程序集指定另一个程序集(例如单元测试程序集),它(有效地)具有对内部类型/成员的"内部"访问权限.原装配.
您应该能够通过使用C#中的接口来完成C++中使用"朋友"的相同类型的事物.它要求您明确定义在两个类之间传递的成员,这是额外的工作,但也可以使代码更容易理解.
如果某人有合理使用无法使用接口模拟的"朋友"的例子,请分享!我想更好地理解C++和C#之间的差异.
使用friend
C++设计器可以精确控制私有*成员所接触的对象.但是,他被迫揭露每一个私人成员.
使用internal
C#设计师可以精确控制他所暴露的私人成员.显然,他只能暴露一个私人成员.但是,它将暴露给程序集中的所有类.
通常,设计者希望仅将少数私有方法暴露给选定的其他几个类.例如,在类工厂模式中,可能希望类C1仅由类工厂CF1实例化.因此,类C1可以具有受保护的构造函数和朋友类工厂CF1.
如您所见,我们有2个维度可以破坏封装. friend
沿着一个维度突破它,沿着另一个维度internal
.在封装概念中哪一个更糟糕?很难说.但是,拥有这两个friend
并且internal
可用是很好的.此外,对这两者的一个很好的补充是第三种类型的关键字,它将在逐个成员的基础上使用(如internal
)并指定目标类(如friend
).
*为简洁起见,我将使用"私人"而非"私人和/或受保护".
- 尼克
实际上,C#提供了以纯OOP方式获得相同行为的可能性而没有特殊的单词 - 它是私有接口.
至于问题什么是C#相当于朋友?被标记为与本文重复,没有人提出真正好的实现 - 我将在这里显示两个问题的答案.
主要想法是从这里开始:什么是私人接口?
比方说,我们需要一些可以管理另一个类的实例并在其上调用一些特殊方法的类.我们不希望将此方法调用到任何其他类.这与朋友c ++关键字在c ++世界中所做的完全相同.
我认为在实际操作中的好例子可能是Full State Machine模式,其中一些控制器更新当前状态对象并在必要时切换到另一个状态对象.
你可以:
使Update()方法公开的最简单和最糟糕的方法 - 希望每个人都理解为什么它是坏的.
下一步是将其标记为内部.如果将类放到另一个程序集中就足够了,但即使这样,该程序集中的每个类都可以调用每个内部方法.
使用私有/受保护的接口 - 我按照这种方式.
Controller.cs
public class Controller { private interface IState { void Update(); } public class StateBase : IState { void IState.Update() { } } public Controller() { //it's only way call Update is to cast obj to IState IState obj = new StateBase(); obj.Update(); } }
Program.cs中
class Program { static void Main(string[] args) { //it's impossible to write Controller.IState p = new StateBase(); //Controller.IState is hidden StateBase p = new StateBase(); //p.Update(); //is not accessible } }
那么,继承呢?
我们需要使用描述的技术,因为显式接口成员实现不能声明为虚拟,并将IState标记为受保护,以便也可以从Controller派生.
Controller.cs
public class Controller { protected interface IState { void Update(); } public class StateBase : IState { void IState.Update() { OnUpdate(); } protected virtual void OnUpdate() { Console.WriteLine("StateBase.OnUpdate()"); } } public Controller() { IState obj = new PlayerIdleState(); obj.Update(); } }
PlayerIdleState.cs
public class PlayerIdleState: Controller.StateBase { protected override void OnUpdate() { base.OnUpdate(); Console.WriteLine("PlayerIdleState.OnUpdate()"); } }
最后举例说明如何测试类Controller抛出继承: ControllerTest.cs
class ControllerTest: Controller { public ControllerTest() { IState testObj = new PlayerIdleState(); testObj.Update(); } }
希望我涵盖所有案例,我的答案很有用.
您可以使用C#关键字"internal"接近C++"friend ".
在编写单元测试时,朋友非常有用.
虽然这是以略微污染您的类声明为代价的,但它也是编译器强制提醒的,这些测试实际上可能关心类的内部状态.
我发现一个非常有用和干净的习惯是当我有工厂课程时,让他们成为他们创建的具有受保护构造函数的项目的朋友.更具体地说,当我有一个工厂负责为报表编写器对象创建匹配的渲染对象,渲染到给定的环境时.在这种情况下,您只需要了解报表编写器类(图像块,布局带,页眉等)与其匹配的渲染对象之间的关系.
C#缺少"friend"关键字,原因与其缺失的确定性破坏相同.变化的惯例使人们感到聪明,好像他们的新方式优于其他人的旧方式.一切都与骄傲有关.
说"朋友类不好"就像其他不合格的语句一样短视,比如"不要使用gotos"或"Linux比Windows更好".
"friend"关键字与代理类相结合是一种很好的方式,只将类的某些部分暴露给特定的其他类.代理类可以充当对所有其他类的可信障碍."public"不允许任何此类定位,并且如果确实没有概念上的"是"关系,则使用"protected"来获得继承效果会很尴尬.
这实际上不是C#的问题.这是IL的一个基本限制.C#受此限制,任何其他寻求可验证的.Net语言也是如此.此限制还包括在C++/CLI中定义的托管类(规范部分20.5).
话虽如此,我认为尼尔森有一个很好的解释,为什么这是一件坏事.