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

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

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

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

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

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


当前回答

我以前经常使用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()]处理其余的—似乎它是专门为单元测试而生的。

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

其他回答

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

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

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

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

如果有朋友,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

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

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

接口 嵌套类

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