C++的朋友关键字 允许 class A 指定 class B 作为它的朋友。此允许 Class B 访问 private/protected 成员 class A.

我从来没有读过什么为什么这个是左出的C#(和VB.NET).大多数回答这个 早先的计算器的问题 似乎可以说这是一个有用的部分C++有很好的理由去使用它。以我的经验我不得不同意。

另一个 的问题 对我来说似乎是真的要怎么做类似的东西 friend 在C#应用程序。而答案通常围绕着嵌套的课,这似乎并不完全一样优雅的使用 friend 关键词。

原始的 设计模式的书 使用它定期在其整个例子。

因此,在总结,为什么 friend 缺少C#是什么"最佳做法"的方式(方式)的模拟,它在C#?

(通过的方式, internal 关键字是 同样的事情,它允许 所有 课程内的整个组访问 internal 成员的同时 friend 可以让你得到一个某些类 完成的访问恰好一个 其他类)

有帮助吗?

解决方案

让编程中的朋友或多或少被认为是“肮脏的”。而且容易被滥用。它打破了类之间的关系,破坏了OO语言的一些基本属性。

话虽这么说,这是一个很好的功能,我自己在C ++中使用了很多次;并且也希望在C#中使用它。但我打赌因为C#的“纯粹”而且OOness(与C ++的伪OOness相比)MS决定因为Java没有朋友关键字C#也不应该(开玩笑;))

严肃地说:内部不如朋友好,但它确实完成了工作。请记住,您很少会通过DLL将代码分发给第三方开发人员;所以只要你和你的团队知道内部课程及其用途你应该没问题。

编辑让我澄清一下friend关键字如何破坏OOP。

私有和受保护的变量和方法可能是OOP最重要的部分之一。对象可以保存只有他们可以使用的数据或逻辑的想法允许您编写独立于您的环境的功能实现 - 并且您的环境不能改变它不适合处理的状态信息。通过使用朋友,您将两个类的实现结合在一起 - 如果您只是将它们的接口耦合在一起则更糟糕。

其他提示

旁注。 使用朋友不是违反封装,而是相反,它是关于强制执行它。就像访问器+变更器,运算符重载,公共继承,向下转换,一样,它经常被滥用,但这并不意味着关键字没有或者更糟糕的是坏的目的。

请参阅 Konrad Rudolph 消息在另一个帖子中,或者如果您愿意,请参阅 C ++ FAQ中的相关条目

有关信息,.NET中另一个相关但不完全相同的东西是 [InternalsVisibleTo] ,它允许程序集指定另一个程序集(例如单元测试程序集) (有效地)具有“内部”访问原始程序集中的类型/成员。

你应该能够完成与“朋友”相同的事情。通过使用C#中的接口在C ++中使用。它要求您明确定义在两个类之间传递的成员,这是额外的工作,但也可以使代码更容易理解。

如果某人有合理使用“朋友”的例子。无法使用接口模拟,请分享!我想更好地理解C ++和C#之间的区别。

使用 friend ,C ++设计人员可以精确控制私人*成员所面对的人。但是,他被迫揭露每一位私人成员。

使用 internal ,C#设计师可以精确控制他所暴露的私人成员。显然,他只能暴露一个私人成员。但是,它将暴露给程序集中的所有类。

通常,设计者希望仅将少数私有方法暴露给选定的其他几个类。例如,在类工厂模式中,可能希望类C1仅由类工厂CF1实例化。因此,类C1可能具有受保护的构造函数和友元类工厂CF1。

如您所见,我们有2个维度可以破坏封装。 friend 在一个维度上违反了它, internal 在另一个维度上执行。在封装概念中哪一个更糟糕?很难说。但是可以同时使用 friend internal 。此外,对这两者的一个很好的补充是第三类关键字,它将逐个成员地使用(如 internal )并指定目标类(如 friend )。

*为简洁起见,我将使用“私人”而不是“私人和/或受保护的”。

- 尼克

你可以接近C ++“朋友”使用C#关键字“internal”

事实上,C#给可能得到相同的行为在纯粹面向对象的方式没有特别说-这是私人的接口。

尽问题 什么是C#等同的朋友吗? 被标记为重复的这一条并没有一个人也没有提出真正实现良好的-我会告诉回答两个问题在这里。

主要想法是把从这里: 什么是私人接口?

让我们说,我们需要一些类可能的管理情况的另一个课程和一些特殊的方法。我们不想给的可能性,以叫这个方法的任何其他类。这正是同样的事情是什么朋友c++关键词做c++的世界。

我认为很好的例子,在实践中可能是完整的国家机模式,其中一些控制器的更新目前的状态对象,并切换到另一种状态对象时必要的。

你可以:

  • 最简单和最差的方式,使更新()法公希望 每个人都了解为什么这是不好的。
  • 下一个方式是,标记它作为内部。它够好,如果你把你的 类的另一个组件,但即使在那的每一类中,大会 可以打电话给各内部方法。
  • 使用私人/保护的接口-我跟着这个方式。

控制器。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();
    }
}

程序。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作为保护得到的可能性,以从控制器。

控制器。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()");
    }
}

最后的例子如何测试类控器扔继承:ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

希望我涵盖所有情况下和我的答案是有用的。

在编写单元测试时,朋友非常有用。

虽然这会以略微污染您的类声明为代价,但它也是编译器强制提醒的,实际上可能关心的是类的内部状态。

我发现的一个非常有用和干净的习惯是当我有工厂课程时,让他们成为他们创建的具有受保护构造函数的项目的朋友。更具体地说,当我有一个工厂负责为报表编写器对象创建匹配的渲染对象,渲染到给定的环境时。在这种情况下,您只需要了解报表编写器类(图像块,布局带,页眉等)与其匹配的渲染对象之间的关系。

C#缺少“朋友”关键字出于同样的原因,它缺少确定性破坏。变化的惯例使人们感到聪明,好像他们的新方式优于其他人的旧方式。这完全取决于骄傲。

说“朋友类不好”与其他不合格的陈述一样短视,例如“不要使用”。或“Linux优于Windows”。

“朋友”与代理类结合使用的关键字是一种很好的方法,只将类的某些部分暴露给特定的其他类。代理类可以充当对所有其他类的可信障碍。 "公开];不允许任何此类定位,并使用“受保护”如果确实没有概念性的“是一个”,那么获得继承的效果是很尴尬的。关系。

这实际上不是C#的问题。这是IL的一个基本限制。 C#受此限制,任何其他寻求可验证的.Net语言也是如此。此限制还包括在C ++ / CLI中定义的托管类( Spec第20.5节)。

话虽如此,我认为尼尔森对于为什么这是一件坏事有一个很好的解释。

不要为此限制找借口。朋友不好,但内心好吗?它们是同一个东西,只有那个朋友能让你更精确地控制谁可以访问,谁不允许访问。

这是为了强制执行封装范式?所以你必须编写访问器方法,现在呢?你怎么能阻止所有人(除了B类的方法)来调用这些方法?你不能,因为你无法控制,因为失去了“朋友”。

没有编程语言是完美的。 C#是我见过的最好的语言之一,但为缺少的功能做出愚蠢的借口并没有帮助任何人。在C ++中,我想念简单的事件/委托系统,反射(+自动de /序列化)和foreach,但在C#中我错过了操作符重载(是的,继续告诉我你不需要它),默认参数,一个const无法规避,多重继承(是的,继续告诉我你不需要它和接口是一个充分的替代品)以及决定从内存中删除实例的能力(不,这不是非常糟糕,除非你是一个工匠)

自.Net 3以来就有InternalsVisibleToAttribute,但我怀疑他们只是在单元测试兴起后才添加它以迎合测试组件。我看不出使用它的其他许多原因。

它在汇编级别工作,但它完成内部不工作的工作;也就是说,您希望分发程序集但希望其他非分布式程序集具有对它的特权访问权限。

非常正确的是,他们要求朋友集会强有力的关键,以避免有人在受保护的议会旁边创造假装的朋友。

我读过很多明智的意见有关的"朋友"的关键词与我同意什么是有用的事情,但我认为什么是"内部的"关键字是较有用的,他们都仍然很糟糕纯粹面向对象的节目。

我们有什么?(说的"朋友"我也说的"内部")

  • 使用的是"朋友"代码不纯洁的关于oo?
  • 是的,

  • 是不使用"朋友"代码好吗?

  • 没有,我们仍然需要做出一些私人类之间的关系,我们可以做到这一点,只有如果我们破坏我们美丽的封装,因此它也不是好的,我可以说什么它甚至更为邪恶的比使用"朋友"。

使用的朋友,使一些当地的问题,而不是使用它使问题对于码图书馆的用户。

共同利益的解决方案的编程语言,我看到这样的:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

你怎么想?我认为它最常见的&纯粹面向对象的解决方案。你可以打开访问的任何方法你选择任何课你想要的。

我怀疑它与C#编译模型有关 - 在运行时构建IL JIT编译。即:与C#泛型基本上不同于C ++泛型的原因相同。

如果您正在使用C ++并且您发现自己使用的是朋友关键字,那么这是一个非常强烈的指示,您有一个设计问题,因为为什么一个类需要访问其他类的私有成员? / p>

您可以将其保密,并使用反射来调用函数。如果您要求测试私有函数

,测试框架可以执行此操作

我曾经经常使用朋友,我认为这不违反OOP或任何设计缺陷的迹象。有几个地方是最有效的方法,以最少的代码正确结束。

一个具体的例子是创建接口组件,为其他软件提供通信接口。通常,有一些重量级类可以处理协议和对等特性的复杂性,并提供相对简单的连接/读/写/转发/断开模型,包括在客户端应用程序和程序集之间传递消息和通知。这些消息/通知需要包装在类中。这些属性通常需要由协议软件操纵,因为它是他们的创建者,但很多东西必须保持只读对外界。

宣布违反协议/“创建者”的OOP是非常愚蠢的。要对所有创建的类进行亲密访问的类 - 创建者类必须在上升的过程中对每一位数据进行咬合。我发现最重要的是最小化所有BS额外的代码行“OOP for OOP sake”。模特通常导致。额外的意大利面只会造成更多的错误。

人们是否知道您可以在属性,属性和方法级别应用内部关键字?它不仅适用于顶级类声明(尽管大多数示例似乎都表明了这一点。)

如果您有一个使用friend关键字的C ++类,并希望在C#类中模拟它: 1.宣布C#类为public 2.声明在C ++中受保护的所有属性/属性/方法,因此可以作为C#内部的朋友访问 3.为公共访问所有内部属性和属性创建只读属性

我同意它与朋友不是100%相同,单元测试是需要像朋友一样的非常有价值的例子(协议分析器日志代码也是如此)。但是内部提供了你想要曝光的类的曝光,[InternalVisibleTo()]处理其余的 - 似乎它是专门为单元测试而生的。

至于朋友“更好,因为你可以明确地控制哪些类有访问权限” - 一开始是一群可疑的邪恶阶级在同一个集会中做了什么?对程序集进行分区!

可以通过分离接口和实现来模拟友谊。这个想法是:“需要一个具体的实例,但是限制该实例的构造访问权限”。

例如

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 类可以访问它,因为没有其他人可以获得的具体实例朋友类。它有一个私有构造函数,而工厂 New()方法返回一个接口。

请参阅我的文章朋友和内部接口成员免费使用编码到接口以获取详细信息。

有些人认为使用朋友可能会失控。我同意,但这并没有减少它的用处。我不确定那个朋友是否一定会伤害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);
    }
}

正如我所看到的,朋友概念没有比公开内容或创建公共方法或属性来访问成员更具风险。如果任何朋友在数据的可访问性方面允许另一级别的粒度,并允许您缩小该可访问性而不是使用内部或公共扩展它。

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类的朋友。

也可以通过使用“代理”来模拟友谊。 - 一些内部课程。请考虑以下示例:

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.
  }
}

当用于仅访问静态成员时,它可能更简单。 这种实现的好处是所有类型都在友谊类的内部范围内声明,并且与接口不同,它允许访问静态成员。

我将只回答"如何"的问题。

有这么多的答案在这里,但我想提出的"设计图案",以实现这一功能。我将使用简单的语言机构,其中包括:

  • 接口
  • 嵌套类

例如,我们有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();
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top