C++ friend keyword允许 class A
将 class B
指定为其朋友 . 这允许 Class B
访问 class A
的 private
/ protected
成员 .
我从来没有读过任何关于为什么它被排除在C#(和VB.NET)之外的东西 . 对此earlier StackOverflow question的大多数答案似乎都说它是C的有用部分,并且有充分的理由使用它 . 根据我的经验,我必须同意 .
在我看来,另一个question真的在询问如何在C#应用程序中执行类似于 friend
的操作 . 虽然答案通常围绕嵌套类,但它看起来并不像使用 friend
关键字那么优雅 .
原始Design Patterns book在其示例中经常使用它 .
总而言之,为什么C#中缺少 friend
,以及在C#中模拟它的"best practice"方式(方法)是什么?
(顺便说一句, internal
关键字不是同一个东西,它允许整个程序集中的所有类访问 internal
成员,而 friend
允许您给某个类完全访问其他一个类)
23 回答
可以通过分离接口和实现来模拟友谊 . 这个想法是:“需要一个具体的实例,但限制该实例的构造访问” .
例如
尽管
ItsMeYourFriend()
是公共的,但只有Friend
类可以访问它,因为没有其他人可以获得Friend
类的具体实例 . 它有一个私有构造函数,而工厂New()
方法返回一个接口 .有关详细信息,请参阅我的文章Friends and internal interface members at no cost with coding to interfaces .
不要为这个限制找借口 . 朋友不好,但内心好吗?它们是同一个东西,只有那个朋友能让你更精确地控制谁可以访问,谁不允许访问 .
这是为了强制封装范式?所以你必须编写访问器方法,现在呢?你怎么能阻止所有人(除了B类的方法)来调用这些方法?你不能,因为你无法控制这一点,因为失去了“朋友” .
没有编程语言是完美的 . C#是我见过的最好的语言之一,但为缺少的功能做出愚蠢的借口并没有帮助任何人 . 在C中,我想念简单的事件/委托系统,反射(自动de /序列化)和foreach,但在C#中我错过了操作符重载(是的,继续告诉我你不需要它),默认参数,一个const无法规避,多重继承(是的,继续告诉我你不需要它,接口是一个充分的替代品)以及决定从内存中删除实例的能力(不,这不是非常糟糕,除非你是一个修补匠)
如果您正在使用C并且您发现自己使用的是朋友关键字,那么这是一个非常强烈的指示,您有一个设计问题,因为为什么一个类需要访问其他类的私有成员?
ALL 类可以访问它们的字段是
public
.NOT 所有其他类可以访问它们的字段是
private
.(如果字段属于(声明在里面) base 类,那么它们是
protected
而不是)only 其所有者类可以访问它们的字段是
private
,并且没有属性并且获取set方法 .只有它们的所有者类和 some 其他类可以访问它们的字段是
private
,并且每个字段都有特殊的private
get和set方法,以及public
共享方法 .其他一些也可以访问这些字段的类将包含一些 delegate 类型的
private
字段和特殊的public
直接方法 .例:
您必须知道使用C的'friend'关键字是允许某些类将 share 的私有成员发送到其他类 directly .
因此,C#中不存在'friend'关键字,所以类无法将其私有字段共享给其他类 directly ,但是如上所示,确实可以模拟它 .
您是否知道C的'friend'关键字还可以允许在某些函数的实现中访问某些类类型的某些实例的私有成员?
答案是肯定的,我将在C#中向您展示如何模拟这个:
例:
我必须承认,在我发布此代码之前,我不知道如何找出调用当前方法的上一个方法的
MethodInfo
,但Firas Assaad的回答对我有帮助,也感谢他 .How can I find the method that called the current method?
他建议使用
System.Diagnostics.StackTrace
类希望你有我的想法,这有助于解答你的问题 .
我没有在互联网上的任何地方找到这个答案,我自己用我的脑思考这个想法 .
有关信息,.NET中另一个相关但不完全相同的东西是
[InternalsVisibleTo]
,它允许程序集指定另一个程序集(例如单元测试程序集),该程序集(有效地)具有"internal"访问类型/成员的权限 . 原装配 .该友谊也可以通过使用“代理人” - 一些内部类来模拟 . 请考虑以下示例:
当用于仅访问静态成员时,它可能更简单 . 这种实现的好处是所有类型都在友谊类的内部范围内声明,并且与接口不同,它允许访问静态成员 .
在旁注 . 使用朋友不是要违反封装,而是相反,它是关于强制执行它 . 像访问者变更器,运算符重载,公共继承,向下转换等,它经常被滥用,但这并不意味着关键字没有或更糟糕的是一个坏的目的 .
请参阅另一个帖子中的Konrad Rudolph的message,或者如果您愿意,请参阅C FAQ中的the relevant entry .
我以前经常使用朋友,我认为这不违反OOP或任何设计缺陷的迹象 . 有几个地方是最有效的方法,以最少的代码正确结束 .
一个具体的例子是创建接口组件,为其他软件提供通信接口 . 通常,有一些重量级类可以处理协议和对等特性的复杂性,并提供相对简单的连接/读/写/转发/断开模型,包括在客户端应用程序和程序集之间传递消息和通知 . 这些消息/通知需要包装在类中 . 这些属性通常需要由协议软件操纵,因为它是它们的创建者,但很多东西必须保持只读对外界 .
声明对协议/“创建者”类具有对所有已创建类的密切访问权限违反OOP是非常愚蠢的 - 创建者类必须在上升过程中咬住每一点数据 . 我发现最重要的是最小化所有BS额外的代码行“OOP for OOP sake”模型通常会导致 . 额外的意大利面只会造成更多的虫子 .
人们是否知道您可以在属性,属性和方法级别应用内部关键字?它不仅仅适用于顶级类声明(尽管大多数示例似乎都表明了这一点 . )
如果您有一个使用friend关键字的C类,并希望在C#类中模拟它:1 . 声明C#类public 2.声明在C中受保护的所有属性/属性/方法,因此可以被朋友访问C#3中的内部 . 为公共访问所有内部属性和属性创建只读属性
我同意它与朋友不是100%相同,单元测试是需要像朋友一样的非常有 Value 的例子(协议分析器日志代码也是如此) . 然而,内部提供了你想要曝光的类的曝光,而[InternalVisibleTo()]处理其余的 - 似乎它是专门为单元测试而生的 .
至于朋友“因为你可以明确地控制哪些课程可以访问而变得更好” - 那么一堆嫌疑邪恶的课程首先在同一个集会中做什么?分区你的程序集!
通过
friend
,C设计师可以精确控制私人*成员所接触的对象 . 但是,他被迫揭露每一位私人成员 .使用
internal
,C#设计师可以精确控制他所暴露的私人成员 . 显然,他只能暴露一个私人成员 . 但是,它将暴露给程序集中的所有类 .通常,设计者希望仅将少数私有方法暴露给选定的其他几个类 . 例如,在类工厂模式中,可能希望类C1仅由类工厂CF1实例化 . 因此,类C1可以具有受保护的构造函数和朋友类工厂CF1 .
如您所见,我们有2个维度可以破坏封装 .
friend
在一个维度上违反了它,internal
沿着另一个维度进行攻击 . 在封装概念中哪一个更糟糕?很难说 . 但是让friend
和internal
都可用会很好 . 此外,对这两个关键字的一个很好的补充是第三类关键字,它将逐个成员地使用(如internal
)并指定目标类(如friend
) .*为简洁起见,我将使用"private"而不是"private and/or protected" .
B.s.d.
据说,朋友们会伤害纯粹的OOness . 我同意 .
还有人说朋友帮助封装,我也同意 .
我认为友谊应该加入OO方法论,但不像C语言那样 . 我想要一些我朋友类可以访问的字段/方法,但我不希望他们访问我的所有字段/方法 . 在现实生活中,我会让我的朋友访问我的个人冰箱,但我不允许他们访问我的银行账户 .
人们可以按照以下方式实施
这当然会产生编译器警告,并会损害智能感知 . 但它会完成这项工作 .
我想是旁注一个自信的程序员应该在不访问私人成员的情况下完成测试单元 . 这完全超出了范围,但尝试阅读有关TDD的内容 . 但是,如果你仍然想要这样做(有像朋友一样)尝试类似的东西
因此,您可以在不定义UNIT_TESTING的情况下编写所有代码,并且当您想要进行单元测试时,将#define UNIT_TESTING添加到文件的第一行(并编写在#if UNIT_TESTING下执行单元测试的所有代码) . 应该谨慎处理 .
由于我认为单元测试是朋友使用的一个不好的例子,我举一个例子,为什么我认为朋友可以很好 . 假设你有一个破解系统(类) . 使用时,破碎系统会磨损,需要进行翻新 . 现在,您希望只有获得许可的技工才能修复它 . 为了使这个例子不那么简单,我会说机械师会使用他的个人(私人)螺丝刀来解决它 . 这就是为什么机械类应该是breakSystem类的朋友 .
我已经阅读了许多关于“朋友”关键字的智能评论,我同意它有用的东西,但我认为“内部”关键字不太有用,而且它们对纯粹的OO编程仍然不好 .
我们有什么? (说“朋友”我也说“内部”)
正在使用"friend"使代码不那么纯粹的oo?
是的;
是不是在使用“朋友”使代码更好?
不,我们仍然需要在类之间 Build 一些私人关系,我们只有在打破我们美丽的封装时才能做到这一点,所以它也不是很好,我可以说它比使用"friend"更邪恶 .
使用friend会产生一些本地问题,而不是使用它会给代码库用户带来问题 .
编程语言的常见解决方案我看起来像这样:
你怎么看待这件事?我认为这是最常见和纯粹的面向对象的解决方案 . 您可以打开访问您选择的任何方法的任何方法 .
我怀疑它与C#编译模型有关 - 在运行时构建IL JIT编译 . 即:C#泛型与C泛型基本不同的原因相同 .
在编程中有朋友或多或少被认为是“肮脏的”并且容易被滥用 . 它打破了类之间的关系,破坏了OO语言的一些基本属性 .
话虽如此,这是一个很好的功能,我自己在C中使用了很多次;并且也想在C#中使用它 . 但我打赌因为C#的“纯粹”OOness(与C的伪OOness相比)MS决定因为Java没有朋友关键字C#也不应该(开玩笑;))
严肃地说:内部不如朋友好,但确实可以完成工作 . 请记住,您很少会通过DLL将代码分发给第三方开发人员;所以只要你和你的团队知道内部课程及其用途你应该没问题 .
EDIT 让我澄清一下friend关键字如何破坏OOP .
私有和受保护的变量和方法可能是OOP最重要的部分之一 . 对象可以保存只有他们可以使用的数据或逻辑的想法允许您编写独立于您的环境的功能实现 - 并且您的环境不能改变它不适合处理的状态信息 . 通过使用朋友,你将两个类的实现结合在一起 - 如果你只是耦合它们的接口,那就更糟了 .
在编写单元测试时,朋友非常有用 .
虽然这会以略微污染您的类声明为代价,但它也是编译器强制提醒的,这些测试实际上可能关心类的内部状态 .
我发现一个非常有用和干净的习惯是当我有工厂课程,让他们成为他们创建的具有受保护构造函数的项目的朋友 . 更具体地说,当我有一个工厂负责为报表编写器对象创建匹配的渲染对象,渲染到给定的环境时 . 在这种情况下,您只需要了解报表编写器类(图像块,布局带,页眉等)与其匹配的渲染对象之间的关系 .
由于缺少确定性破坏,C#缺少“friend”关键字 . 变化的惯例使人们感到聪明,好像他们的新方式优于其他人的旧方式 . 一切都与骄傲有关 .
说“朋友类不好”就像其他不合格的语句一样短视,比如“不要使用gotos”或“Linux比Windows更好” .
"friend"关键字与代理类相结合是一种很好的方式,只将类的某些部分暴露给特定的其他类 . 代理类可以充当对所有其他类的可信障碍 . "public"不允许任何此类定位,如果确实没有概念"is a",使用"protected"来获取继承效果会很尴尬关系 .
这实际上不是C#的问题 . 这是IL的一个基本限制 . C#受此限制,任何其他寻求可验证的.Net语言也是如此 . 此限制还包括在C / CLI(Spec section 20.5)中定义的托管类 .
话虽如此,我认为尼尔森对于为什么这是一件坏事有一个很好的解释 .
自.Net 3以来就有InternalsVisibleToAttribute,但我怀疑他们只是在单元测试兴起后才添加它以迎合测试组件 . 我看不出使用它的其他许多原因 .
它在汇编级别工作,但它完成内部不工作的工作;也就是说,您希望分发程序集但希望其他非分布式程序集具有对它的特权访问权限 .
非常正确的是,他们要求朋友组装强大的键,以避免有人在受保护的组件旁边创建一个假装的朋友 .
实际上,C#提供了在没有特殊字的情况下以纯OOP方式获得相同行为的可能性 - 它是私有接口 .
至于问题What is the C# equivalent of friend?被标记为与本文重复,没有人提出真正好的实现 - 我将在这里对这两个问题给出答案 .
主要想法是从这里开始:What is a private interface?
比方说,我们需要一些可以管理另一个类的实例并在其上调用一些特殊方法的类 . 我们不希望将此方法调用到任何其他类 . 这与朋友c关键字在c世界中所做的完全相同 .
我认为在实际操作中的好例子可能是Full State Machine模式,其中一些控制器更新当前状态对象并在必要时切换到另一个状态对象 .
你可以:
使Update()方法公开的最简单和最糟糕的方法 - 希望每个人都明白为什么它是坏的 .
下一步是将其标记为内部 . 如果将类放到另一个程序集中就足够了,但即使这样,该程序集中的每个类都可以调用每个内部方法 .
使用私有/受保护的接口 - 我遵循这种方式 .
Controller.cs
Program.cs
那么,继承呢?
我们需要使用Since explicit interface member implementations cannot be declared virtual中描述的技术并将IState标记为受保护,以便也可以从Controller派生 .
Controller.cs
PlayerIdleState.cs
最后举例说明如何测试类Controller抛出继承: ControllerTest.cs
希望我能涵盖所有案例,我的答案很有用 .
您可以使用C#关键字"internal"接近C "friend" .
你可以将它保密,并使用反射来调用函数 . 如果要求它测试私有函数,测试框架可以执行此操作
有些人认为使用朋友可能会失控 . 我同意,但这并没有减少它的用处 . 我不确定那个朋友是否一定会伤害OO范式,而不是让所有的 class 成员都公开 . 当然,该语言将允许您将所有成员公开,但它是一个纪律严明的程序员,可以避免这种类型的设计模式 . 同样,一个训练有素的程序员会在有意义的情况下保留朋友的使用 . 在某些情况下,我觉得内部暴露太多 . 为什么要将类或方法暴露给程序集中的所有内容?
我有一个ASP.NET页面,它继承了我自己的基页,而后者又继承了System.Web.UI.Page . 在这个页面中,我有一些代码可以在受保护的方法中处理应用程序的最终用户错误报告
现在,我有一个包含在页面中的用户控件 . 我希望用户控件能够调用页面中的错误报告方法 .
如果ReportError方法受到保护,则无法执行此操作 . 我可以将其设置为内部,但它会暴露给程序集中的任何代码 . 我只是想将它暴露给属于当前页面的UI元素(包括子控件) . 更具体地说,我希望我的基本控件类定义完全相同的错误报告方法,并简单地调用基页中的方法 .
我相信像朋友这样的东西可能是有用的并且在语言中实现而不会使语言变得像“OO”那样,或许作为属性,这样你就可以让类或方法成为特定类或方法的朋友,允许开发人员提供非常好的具体访问 . 也许像......(伪代码)
在我之前的例子的情况下,可能有类似下面的内容(一个可以论证语义,但我只是试图了解这个想法):
在我看来,朋友概念没有比公开内容,或创建公共方法或属性来访问成员更具风险 . 如果任何朋友在数据的可访问性方面允许另一级别的粒度,并允许您缩小该可访问性而不是使用内部或公共扩展它 .
I will answer only "How" question.
这里有很多答案,但是我想提出一种“设计模式”来实现这个功能 . 我会用简单的语言机制,包括:
接口
嵌套类
例如,我们有两个主要类别:学生和大学 . 学生有GPA,只有大学允许访问 . 这是代码:
您应该能够通过使用C#中的接口来完成C中用于“朋友”的相同类型的事物 . 它要求您明确定义在两个类之间传递的成员,这是额外的工作,但也可以使代码更容易理解 .
如果某人有合理使用无法使用接口模拟的“朋友”的例子,请分享!我想更好地理解C和C#之间的差异 .