c++的friend关键字允许类a将类B指定为它的friend。这允许类B访问类A的私有/受保护成员。

我从来没有读过任何关于为什么c#(和VB.NET)中不包含这个的东西。大多数关于StackOverflow问题的答案似乎都在说它是c++的一个有用的部分,并且有很好的理由使用它。以我的经验,我不得不同意。

对我来说,另一个问题似乎是在问如何在c#应用程序中做类似于friend的事情。虽然答案通常围绕嵌套类,但它似乎没有使用friend关键字那么优雅。

最初的《设计模式》一书在其示例中经常使用它。

总之,为什么在c#中没有friend,在c#中模拟friend的“最佳实践”方法是什么?

(顺便说一下,internal关键字不是一回事,它允许整个程序集中的所有类访问内部成员,而friend允许你给某个类完全访问另一个类)


你可以用c#关键字“internal”来接近c++的“朋友”。


我怀疑这与c#编译模型有关——在运行时构建IL和JIT编译。也就是说:c#泛型与c++泛型有本质区别的原因是一样的。


通过使用c#中的接口,您应该能够完成在c++中使用“friend”的相同种类的事情。它要求您显式地定义在两个类之间传递的成员,这是额外的工作,但也可能使代码更容易理解。

如果有人有一个合理使用“朋友”的例子,不能用界面模拟,请分享!我想更好地理解c++和c#之间的区别。


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

也就是说,这是一个很好的特性,我自己在c++中使用过很多次;并且也想在c#中使用它。但我敢打赌,因为c#的“纯”OOness(与c++的伪OOness相比),MS决定因为Java没有朋友,关键字c#也不应该(开玩笑;))

严肃地说:内部关系不如朋友关系好,但它确实能完成工作。请记住,您很少会不通过DLL将代码分发给第三方开发人员;因此,只要您和您的团队了解内部类及其使用,就应该没问题。

让我来澄清一下friend关键字是如何破坏OOP的。

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


如果你正在使用c++,你发现你自己使用friend关键字,这是一个非常强烈的迹象,你有一个设计问题,因为为什么一个类需要访问其他类的私有成员?


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


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

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


顺便说一句。 使用friend并不是违反封装,相反,它是加强封装。就像访问器+突变器、操作符重载、公共继承、向下强制转换等一样,它经常被误用,但这并不意味着关键字没有,或者更糟,没有一个坏的目的。

请参阅Konrad Rudolph在另一个线程中的消息,或者如果您愿意,请参阅c++常见问题解答中的相关条目。


Friend在编写单元测试时非常有用。

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

A very useful and clean idiom I've found is when I have factory classes, making them friends of the items they create which have a protected constructor. More specifically, this was when I had a single factory responsible for creating matching rendering objects for report writer objects, rendering to a given environment. In this case you have a single point of knowledge about the relationship between the report-writer classes (things like picture blocks, layout bands, page headers etc.) and their matching rendering objects.


c#缺少“friend”关键字的原因与缺少确定性销毁的原因相同。改变传统让人们觉得自己很聪明,好像他们的新方式比别人的旧方式更优越。这都是关于骄傲。

说“朋友类不好”就像“不要用gotos”或“Linux比Windows好”一样是短视的。

“friend”关键字与代理类相结合是只向特定的其他类公开类的某些部分的好方法。代理类可以作为对所有其他类的可信屏障。“public”不允许任何这样的目标,如果真的没有概念上的“is a”关系,使用“protected”来获得继承的效果是很尴尬的。


您可以保持它为私有,并使用反射来调用函数。测试框架可以做到这一点,如果你要求它测试一个私有函数


不要再为这种限制找借口了。朋友是坏的,但内在是好的?它们是一样的东西,只是那个朋友让你更精确地控制谁可以访问谁不能访问。

这是强制封装范式吗?所以你必须写访问器方法,然后呢?你应该如何阻止所有人(除了类B的方法)调用这些方法?你不能,因为你也控制不了,因为你失去了“朋友”。

No programming language is perfect. C# is one of the best languages I've seen, but making silly excuses for missing features doesn't help anyone. In C++, I miss the easy event/delegate system, reflection (+automatic de/serialization) and foreach, but in C# I miss operator overloading (yeah, keep telling me that you didn't need it), default parameters, a const that cannot be circumvented, multiple inheritance (yeah, keep telling me that you didn't need it and interfaces were a sufficient replacement) and the ability to decide to delete an instance from memory (no, this is not horribly bad unless you are a tinkerer)


如果有朋友,c++设计人员可以精确控制私有*成员暴露给谁。但是,他被迫暴露了每一个私人成员。

对于内部,c#设计器可以精确控制他所公开的私有成员集。显然,他只能暴露单个私有成员。但是,它将暴露给程序集中的所有类。

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

As you can see, we have 2 dimensions along which encapsulation can be breached. friend breaches it along one dimension, internal does it along the other. Which one is a worse breach in the encapsulation concept? Hard to say. But it would be nice to have both friend and internal available. Furthermore, a good addition to these two would be the 3rd type of keyword, which would be used on member-by-member basis (like internal) and specifies the target class (like friend). * For brevity I will use "private" instead of "private and/or protected". - Nick


我以前经常使用friend,我不认为它违反了OOP,也不认为它有任何设计缺陷。在一些地方,它是用最少的代码达到正确目的的最有效的方法。

One concrete example is when creating interface assemblies that provide a communications interface to some other software. Generally there are a few heavyweight classes that handle the complexity of the protocol and peer peculiarities, and provide a relatively simple connect/read/write/forward/disconnect model involving passing messages and notifications between the client app and the assembly. Those messages / notifications need to be wrapped in classes. The attributes generally need to be manipulated by the protocol software as it is their creator, but a lot of stuff has to remain read-only to the outside world.

声明协议/“创建者”类对所有创建的类的亲密访问是违反OOP的,这是非常愚蠢的——创建者类不得不在上升的过程中对每一点数据进行修改。我发现最重要的是尽量减少“为了面向对象而面向对象”模型通常导致的所有多余的代码行。额外的意大利面只会产生更多的虫子。

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

如果你有一个使用friend关键字的c++类,并且想在一个c#类中模拟它: 1. 将c#类声明为public 2. 将c++中受保护的所有属性/属性/方法声明为c#内部的 3.为所有内部属性和属性的公共访问创建只读属性

我同意它并不完全等同于好友,单元测试是一个非常有价值的例子,说明了需要好友之类的东西(就像协议分析器日志代码一样)。然而,internal提供了您想要公开的类的公开,[InternalVisibleTo()]处理其余的—似乎它是专门为单元测试而生的。

至于朋友“更好,因为你可以显式地控制哪些类可以访问”——一堆可疑的邪恶类在同一个程序集中做什么呢?对程序集进行分区!


从。net 3开始就有了InternalsVisibleToAttribute,但我怀疑他们只是在单元测试兴起后才添加它来迎合测试程序集。我想不出还有什么其他理由来使用它。

它在组装层工作但它能完成内部不能完成的工作;也就是说,您希望分发一个程序集,但希望另一个非分布式程序集具有对该程序集的特权访问。

很正确地,他们要求好友程序集是强键的,以避免有人在你受保护的程序集旁边创建一个假装的好友。


我读过许多关于“朋友”关键字的聪明评论,我同意它是有用的东西,但我认为“内部”关键字没有那么有用,而且它们对纯OO编程来说都很糟糕。

我们有什么?(说到“朋友”,我也说到“内部”)

使用“friend”是否会使代码对于oo不那么纯粹? 是的, 不使用“friend”会使代码更好吗? 不,我们仍然需要在类之间建立一些私人关系,而且只有打破了我们美丽的封装才能做到,所以这也不好,我可以说它比使用“朋友”更邪恶。

使用friend会产生一些局部问题,不使用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) { }
}

你觉得怎么样?我认为这是最常见的纯面向对象的解决方案。你可以对你想要的任何类打开任何方法的访问。


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

例如

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()方法返回一个接口。

有关详细信息,请参阅我的文章《朋友和内部接口成员,免费为接口编码》。


B.s.d。

有人说,朋友伤害纯粹的孤独。我同意这一点。

还有人说,朋友会帮助封装,我也同意这一点。

我认为友谊应该添加到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下编写所有进行单元测试的代码)。这应该小心处理。

因为我认为单元测试对于朋友的使用是一个不好的例子,所以我想举一个例子来说明为什么我认为朋友可以是好的。假设你有一个破碎的系统(类)。随着使用,破碎系统磨损,需要翻新。现在,你希望只有有执照的技工才能修理它。为了让这个例子不那么琐碎,我想说机械师会用他的私人螺丝刀来修理它。这就是为什么机制类应该是breakingSystem类的朋友。


Some have suggested that things can get out of control by using friend. I would agree, but that doesn't lessen its usefulness. I'm not certain that friend necessarily hurts the OO paradigm any more than making all your class members public. Certainly the language will allow you to make all your members public, but it is a disciplined programmer that avoids that type of design pattern. Likewise a disciplined programmer would reserve the use of friend for specific cases where it makes sense. I feel internal exposes too much in some cases. Why expose a class or method to everything in the assembly?

我有一个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);
}

我相信像friend这样的东西可以很有用,并且可以在语言中实现,而不会使语言变得不那么“面向对象”,比如作为属性,这样你就可以让类或方法成为特定类或方法的朋友,允许开发人员提供非常特定的访问。也许就像……(伪代码)

[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);
    }
}

在我看来,朋友概念并不比将事物公开,或创建公共方法或属性来访问成员更有风险。如果朋友允许数据可访问性的另一个粒度级别,并允许你缩小可访问性,而不是用内部或公共来扩大它。


这种友谊也可以通过使用“代理”——一些内部类来模拟。考虑下面的例子:

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

如果只用于访问静态成员,则会简单得多。 这种实现的好处是,所有类型都在友谊类的内部作用域中声明,并且与接口不同,它允许访问静态成员。


我只回答“如何”的问题。

这里有很多答案,但我想提出一种“设计模式”来实现这一功能。我将使用简单的语言机制,包括:

接口 嵌套类

例如,我们有两个主要的类:学生和大学。学生的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();
}

事实上,c#提供了在纯面向对象的方式下获得相同行为的可能性,没有特殊的词语——它的私有接口。

至于这个问题,c#中朋友的对等物是什么?这篇文章被标记为重复,没有人提出真正好的实现-我将在这里给出这两个问题的答案。

主要思想是从这里开始的:什么是私有接口?

比方说,我们需要一些类来管理另一个类的实例,并对它们调用一些特殊的方法。我们不想给任何其他类调用这个方法的可能性。这与friend c++关键字在c++世界中所做的事情完全相同。

我认为在实际实践中的一个很好的例子是全状态机模式,其中一些控制器更新当前状态对象并在必要时切换到另一个状态对象。

你可以:

使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 Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.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();
    }
}

希望我涵盖了所有情况,我的回答是有用的。