C++ friend keyword允许class A
将class B
指定为其朋友。这样,Class B
就可以访问private
的{{1}} / protected
个成员。
我从来没有读过任何关于为什么这个被排除在C#(和VB.NET)之外的东西。这个earlier StackOverflow question的大多数答案似乎都说它是C ++的有用部分,并且有充分的理由使用它。根据我的经验,我必须同意。
在我看来,另一个question真的在询问如何在C#应用程序中执行与class A
类似的操作。虽然答案通常围绕嵌套类,但它似乎不如使用friend
关键字那么优雅。
原始Design Patterns book在其示例中经常使用它。
总而言之,为什么C#中缺少friend
,在C#中模拟它的“最佳实践”方式是什么?
(顺便说一句,friend
关键字不相同,它允许整个程序集中的所有类访问internal
成员,而internal
允许您将某个类完全访问提供给一个其他类
答案 0 :(得分:99)
旁注。 使用朋友不是违反封装,而是相反,它是关于强制执行它。就像访问器+变更器,运算符重载,公共继承,向下转换,等一样,它经常被滥用,但这并不意味着关键字没有或者更糟糕的是坏的目的。
在其他帖子中查看Konrad Rudolph的message,或者如果您愿意,请参阅C ++常见问题解答中的the relevant entry。
答案 1 :(得分:62)
让编程中的朋友或多或少地被认为是“肮脏的”并且容易被滥用。它打破了类之间的关系,破坏了OO语言的一些基本属性。
话虽这么说,这是一个很好的功能,我自己在C ++中使用了很多次;并且也希望在C#中使用它。但我打赌因为C#的“纯粹”OOness(与C ++的伪OOness相比)MS决定因为Java没有朋友关键字C#也不应该(开玩笑;))
严肃地说:内部不如朋友好,但它确实完成了工作。请记住,您很少会通过DLL将代码分发给第三方开发人员;所以只要你和你的团队知道内部课程及其用途你应该没问题。
编辑让我澄清一下friend关键字如何破坏OOP。
私有和受保护的变量和方法可能是OOP最重要的部分之一。对象可以保存只有他们可以使用的数据或逻辑的想法允许您编写独立于您的环境的功能实现 - 并且您的环境不能改变它不适合处理的状态信息。通过使用朋友,您将两个类的实现结合在一起 - 如果您只是将它们的接口耦合在一起则更糟糕。
答案 2 :(得分:47)
有关信息,.NET中另一个相关但不完全相同的东西是[InternalsVisibleTo]
,它允许程序集指定另一个程序集(例如单元测试程序集)(有效地)具有“内部“访问原始程序集中的类型/成员。
答案 3 :(得分:13)
你应该能够通过在C#中使用接口来完成C ++中使用“朋友”的相同类型的事情。它要求您明确定义在两个类之间传递的成员,这是额外的工作,但也可以使代码更容易理解。
如果某人有合理使用无法使用界面模拟的“朋友”的例子,请分享!我想更好地理解C ++和C#之间的区别。
答案 4 :(得分:13)
使用friend
,C ++设计者可以精确控制私人*成员所面对的人。但是,他被迫揭露每一位私人成员。
使用internal
,C#设计师可以精确控制他所暴露的私人成员集。显然,他只能暴露一个私人成员。但是,它将暴露给程序集中的所有类。
通常,设计者希望仅将少数私有方法暴露给选定的其他几个类。例如,在类工厂模式中,可能希望类C1仅由类工厂CF1实例化。因此,类C1可能具有受保护的构造函数和友元类工厂CF1。
如您所见,我们有2个维度可以破坏封装。 friend
在一个维度上违反了它,internal
沿着另一个维度进行攻击。在封装概念中哪一个更糟糕?很难说。但是同时提供friend
和internal
会很不错。此外,对这两者的一个很好的补充是第三类关键字,它将逐个成员地使用(如internal
)并指定目标类(如friend
)。
*为简洁起见,我将使用“私人”而非“私人和/或受保护”。
- 尼克
答案 5 :(得分:9)
至于问题What is the C# equivalent of friend?被标记为与本文重复,并且没有人提出真正好的实现 - 我将在这里对这两个问题给出答案。
主要想法是从这里开始:What is a private interface?
让我们说,我们需要一些可以管理另一个类的实例并在其上调用一些特殊方法的类。我们不希望将此方法调用到任何其他类。这与朋友c ++关键字在c ++世界中所做的完全相同。
我认为在实际操作中的好例子可能是Full State Machine模式,其中一些控制器更新当前状态对象并在必要时切换到另一个状态对象。
你可以:
<强> 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
}
}
嗯,继承怎么样?
我们需要使用Since explicit interface member implementations cannot be declared virtual中描述的技术并将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();
}
}
希望我能涵盖所有案例,我的答案很有用。
答案 6 :(得分:9)
您可以使用C#关键字"internal"接近C ++“朋友”。
答案 7 :(得分:8)
在编写单元测试时,朋友非常有用。
虽然这会以略微污染您的类声明为代价,但它也是编译器强制提醒的,实际上可能关心的是类的内部状态。
我发现的一个非常有用和干净的习惯是当我有工厂课程时,让他们成为他们创建的具有受保护构造函数的项目的朋友。更具体地说,当我有一个工厂负责为报表编写器对象创建匹配的渲染对象,渲染到给定的环境时。在这种情况下,您只需要了解报表编写器类(图像块,布局带,页眉等)与其匹配的渲染对象之间的关系。
答案 8 :(得分:8)
C#缺少“friend”关键字,原因与其缺失的确定性破坏相同。变化的惯例使人们感到聪明,好像他们的新方式优于其他人的旧方式。这完全取决于骄傲。
说“朋友类不好”与其他不合格的语句一样短视,比如“不要使用gotos”或“Linux比Windows更好”。
“friend”关键字与代理类相结合,是将类的某些部分公开给特定的其他类的好方法。代理类可以充当对所有其他类的可信障碍。 “public”不允许任何此类定位,如果确实没有概念上的“是一种”关系,那么使用“protected”来获得继承效果会很尴尬。
答案 9 :(得分:7)
这实际上不是C#的问题。这是IL的一个基本限制。 C#受此限制,任何其他寻求可验证的.Net语言也是如此。此限制还包括在C ++ / CLI(Spec section 20.5)中定义的托管类。
话虽如此,我认为尼尔森对于为什么这是一件坏事有一个很好的解释。
答案 10 :(得分:4)
不要为此限制找借口。朋友不好,但内心好吗?它们是同一个东西,只有那个朋友能让你更精确地控制谁可以访问,谁不允许访问。
这是为了强制执行封装范式?所以你必须编写访问器方法,现在呢?你怎么能阻止所有人(除了B类的方法)来调用这些方法?你不能,因为你无法控制这一点,因为失去了“朋友”。
没有编程语言是完美的。 C#是我见过的最好的语言之一,但为缺少的功能做出愚蠢的借口并没有帮助任何人。在C ++中,我想念简单的事件/委托系统,反射(+自动de /序列化)和foreach,但在C#中我错过了操作符重载(是的,继续告诉我你不需要它),默认参数,一个const无法规避,多重继承(是的,继续告诉我你不需要它和接口是一个充分的替代品)和决定从内存中删除一个实例的能力(不,这不是非常糟糕,除非你是一个工匠)
答案 11 :(得分:2)
自.Net 3以来就有InternalsVisibleToAttribute,但我怀疑他们只是在单元测试兴起后才添加它以迎合测试组件。我看不出使用它的其他许多原因。
它在汇编级别工作,但它完成内部不工作的工作;也就是说,您希望分发程序集但希望其他非分布式程序集具有对它的特权访问权限。
非常正确的是,他们要求朋友集会强有力的关键,以避免有人在受保护的议会旁边创造假装的朋友。
答案 12 :(得分:2)
我读过很多关于“朋友”关键字&amp;我同意什么是有用的东西,但我认为什么“内部”关键字不太有用,&amp;它们对于纯OO编程仍然不好。
我们有什么? (说“朋友”我也说“内部”)
是
是不是在使用“朋友”让代码更好?
使用friend会产生一些本地问题,而不是使用它会给代码库用户带来问题。
编程语言的常见解决方案我看起来像这样:
// c++ style
class Foo {
public_for Bar:
void addBar(Bar *bar) { }
public:
private:
protected:
};
// c#
class Foo {
public_for Bar void addBar(Bar bar) { }
}
你怎么看?我认为这是最常见的&amp;纯面向对象的解决方案。您可以打开任何您选择的方法来访问您想要的任何课程。
答案 13 :(得分:1)
您可以将其保密,并使用反射来调用函数。如果您要求测试私有函数
,测试框架可以执行此操作答案 14 :(得分:1)
如果您正在使用C ++并且您发现自己使用的是朋友关键字,那么这是一个非常强烈的指示,您有一个设计问题,因为为什么一个类需要访问其他类的私有成员? / p>
答案 15 :(得分:1)
我曾经经常使用朋友,我认为这不违反OOP或任何设计缺陷的迹象。有几个地方是最有效的方法,以最少的代码正确结束。
一个具体的例子是创建接口组件,为其他软件提供通信接口。通常,有一些重量级类可以处理协议和对等特性的复杂性,并提供相对简单的连接/读/写/转发/断开模型,包括在客户端应用程序和程序集之间传递消息和通知。这些消息/通知需要包装在类中。这些属性通常需要由协议软件操纵,因为它是他们的创建者,但很多东西必须保持只读对外界。
声明对协议/“创建者”类具有对所有已创建类的亲密访问权限违反OOP是非常愚蠢的 - 创建者类必须在路上咬住每一点数据起来。我发现最重要的是最小化所有BS额外的代码行“OOP for OOP sake”模型通常会导致。额外的意大利面只会造成更多的错误。
人们是否知道您可以在属性,属性和方法级别应用内部关键字?它不仅适用于顶级类声明(尽管大多数示例似乎都表明了这一点。)
如果您有一个使用friend关键字的C ++类,并希望在C#类中模拟它: 1.宣布C#类为public 2.声明在C ++中受保护的所有属性/属性/方法,因此可以作为C#内部的朋友访问 3.为公共访问所有内部属性和属性创建只读属性
我同意它与朋友不是100%相同,单元测试是需要像朋友一样的非常有价值的例子(协议分析器日志代码也是如此)。但是内部提供了你想要曝光的类的曝光,[InternalVisibleTo()]处理其余的 - 似乎它是专门为单元测试而生的。
至于朋友“因为你可以明确地控制哪些课程可以访问”而变得更好“ - 一开始是一堆可疑的邪恶课程在同一个集会中做什么?对程序集进行分区!
答案 16 :(得分:1)
可以通过分离接口和实现来模拟友谊。这个想法是:“需要一个具体的实例,但是限制该实例的构造访问”。
例如
interface IFriend { }
class Friend : IFriend
{
public static IFriend New() { return new Friend(); }
private Friend() { }
private void CallTheBody()
{
var body = new Body();
body.ItsMeYourFriend(this);
}
}
class Body
{
public void ItsMeYourFriend(Friend onlyAccess) { }
}
尽管ItsMeYourFriend()
是公共的,Friend
类可以访问它,因为没有其他人可以获得Friend
类的具体实例。它有一个私有构造函数,而工厂New()
方法返回一个接口。
有关详细信息,请参阅我的文章Friends and internal interface members at no cost with coding to interfaces。
答案 17 :(得分:1)
有些人认为使用朋友可能会失控。我同意,但这并没有减少它的用处。我不确定那个朋友是否一定会伤害OO范式,而不是让所有的班级成员都公开。当然,该语言将允许您将所有成员公开,但它是一个纪律严明的程序员,可以避免这种类型的设计模式。同样,一个训练有素的程序员会保留朋友在有意义的情况下的使用。在某些情况下,我觉得内部暴露太多。为什么要将类或方法暴露给程序集中的所有内容?
我有一个ASP.NET页面,它继承了我自己的基页,而后者又继承了System.Web.UI.Page。在这个页面中,我有一些代码可以在受保护的方法中处理应用程序的最终用户错误报告
ReportError("Uh Oh!");
现在,我有一个包含在页面中的用户控件。我希望用户控件能够调用页面中的错误报告方法。
MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");
如果ReportError方法受到保护,则无法执行此操作。我可以将其设置为内部,但它暴露于程序集中的任何代码。我只是希望它暴露给作为当前页面一部分的UI元素(包括子控件)。更具体地说,我希望我的基本控件类定义完全相同的错误报告方法,并简单地调用基页中的方法。
protected void ReportError(string str) {
MyBasePage bp = Page as MyBasePage;
bp.ReportError(str);
}
我相信像朋友这样的东西可能很有用并且在语言中实现而不会使语言变得像“OO”那样,或许作为属性,这样你就可以让类或方法成为特定类或方法的朋友,允许开发人员提供非常具体的访问权限。也许像...(伪代码)
[Friend(B)]
class A {
AMethod() { }
[Friend(C)]
ACMethod() { }
}
class B {
BMethod() { A.AMethod() }
}
class C {
CMethod() { A.ACMethod() }
}
在我之前的例子的情况下,或许有类似下面的内容(一个可以论证语义,但我只是试图了解这个想法):
class BasePage {
[Friend(BaseControl.ReportError(string)]
protected void ReportError(string str) { }
}
class BaseControl {
protected void ReportError(string str) {
MyBasePage bp = Page as MyBasePage;
bp.ReportError(str);
}
}
正如我所看到的,朋友概念没有比公开内容或创建公共方法或属性来访问成员更具风险。如果任何朋友在数据的可访问性方面允许另一级别的粒度,并允许您缩小该可访问性而不是使用内部或公共扩展它。
答案 18 :(得分:1)
我怀疑它与C#编译模型有关 - 在运行时构建IL JIT编译。即:与C#泛型基本上不同于C ++泛型的原因相同。
答案 19 :(得分:0)
B.s.d。
据说,朋友们会伤害纯粹的OOness。我同意。
还有人说朋友帮助封装,我也同意。
我认为友谊应该添加到OO方法中,但不像C ++中那样。我想要一些朋友类可以访问的字段/方法,但我不希望他们访问我的所有字段/方法。在现实生活中,我会让我的朋友访问我的个人冰箱,但我不允许他们访问我的银行账户。
可以按照以下方式实现
class C1
{
private void MyMethod(double x, int i)
{
// some code
}
// the friend class would be able to call myMethod
public void MyMethod(FriendClass F, double x, int i)
{
this.MyMethod(x, i);
}
//my friend class wouldn't have access to this method
private void MyVeryPrivateMethod(string s)
{
// some code
}
}
class FriendClass
{
public void SomeMethod()
{
C1 c = new C1();
c.MyMethod(this, 5.5, 3);
}
}
这当然会产生编译器警告,并会损害智能感知。但它会完成这项工作。
另一方面,我认为一个自信的程序员应该在不访问私人成员的情况下进行测试。这完全超出了范围,但尝试阅读有关TDD的内容。 但是,如果你仍然想这样做(有像朋友一样的c ++)尝试类似
的东西#if UNIT_TESTING
public
#else
private
#endif
double x;
所以你编写所有代码而不定义UNIT_TESTING,当你想进行单元测试时,你将#define UNIT_TESTING添加到文件的第一行(并编写所有在#if UNIT_TESTING下进行单元测试的代码)。这应该小心处理。
由于我认为单元测试是朋友使用的一个不好的例子,我举一个例子,为什么我认为朋友可以做得好。假设你有一个破解系统(类)。使用时,破碎系统会磨损,需要进行翻新。现在,您希望只有获得许可的技工才能修复它。为了使这个例子不那么简单,我会说机械师会使用他的私人(私人)螺丝刀来解决它。这就是为什么机械类应该成为breakSystem类的朋友。
答案 20 :(得分:0)
友谊也可以通过使用“代理人” - 一些内部类来模拟。请考虑以下示例:
public class A // Class that contains private members
{
private class Accessor : B.BAgent // Implement accessor part of agent.
{
private A instance; // A instance for access to non-static members.
static Accessor()
{ // Init static accessors.
B.BAgent.ABuilder = Builder;
B.BAgent.PrivateStaticAccessor = StaticAccessor;
}
// Init non-static accessors.
internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
// Agent constructor for non-static members.
internal Accessor(A instance) { this.instance = instance; }
private static A Builder() { return new A(); }
private static void StaticAccessor() { A.PrivateStatic(); }
}
public A(B friend) { B.Friendship(new A.Accessor(this)); }
private A() { } // Private constructor that should be accessed only from B.
private void SomePrivateMethod() { } // Private method that should be accessible from B.
private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
// Agent for accessing A.
internal abstract class BAgent
{
internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
internal static Action PrivateStaticAccessor;
internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
}
internal static void Friendship(BAgent agent)
{
var a = BAgent.ABuilder(); // Access private constructor.
BAgent.PrivateStaticAccessor(); // Access private static method.
agent.PrivateMethodAccessor(); // Access private non-static member.
}
}
当用于仅访问静态成员时,它可能更简单。 这种实现的好处是所有类型都在友谊类的内部范围内声明,并且与接口不同,它允许访问静态成员。
答案 21 :(得分:0)
我只回答“如何”问题。
这里有很多答案,但是我想提出一种“设计模式”来实现这个功能。我将使用简单的语言机制,其中包括:
例如,我们有2个主要课程:学生和大学。学生有GPA,只有大学允许访问。这是代码:
public interface IStudentFriend
{
Student Stu { get; set; }
double GetGPS();
}
public class Student
{
// this is private member that I expose to friend only
double GPS { get; set; }
public string Name { get; set; }
PrivateData privateData;
public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);
// No one can instantiate this class, but Student
// Calling it is possible via the IStudentFriend interface
class PrivateData : IStudentFriend
{
public Student Stu { get; set; }
public PrivateData(Student stu) => Stu = stu;
public double GetGPS() => Stu.GPS;
}
// This is how I "mark" who is Students "friend"
public void RegisterFriend(University friend) => friend.Register(privateData);
}
public class University
{
var studentsFriends = new List<IStudentFriend>();
public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);
public void PrintAllStudentsGPS()
{
foreach (var stu in studentsFriends)
Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
}
}
public static void Main(string[] args)
{
var Technion = new University();
var Alex = new Student("Alex", 98);
var Jo = new Student("Jo", 91);
Alex.RegisterFriend(Technion);
Jo.RegisterFriend(Technion);
Technion.PrintAllStudentsGPS();
Console.ReadLine();
}