Почему С# не предоставляет ключевое слово 'friend' стиля С++?

ключевое слово дружбы С++ позволяет class A назначать class B своим другом. Это позволяет class B получить доступ к private/protected членам class A.

Я никогда не читал ничего о том, почему это было исключено из С# (и VB.NET). Большинство ответов на этот qaru.site/info/21502/..., похоже, говорят, что это полезная часть С++, и есть веские причины для ее использования. По моему опыту, я должен согласиться.

Другим question кажется, что я действительно спрашиваю, как сделать что-то похожее на friend в приложении С#. Хотя ответы обычно вращаются вокруг вложенных классов, это выглядит не так элегантно, как использование ключевого слова friend.

Оригинальная Design Patterns book регулярно использует ее во всех своих примерах.

Итак, вкратце, почему friend отсутствует на С#, и каков способ "лучшей практики" имитировать его на С#?

(Кстати, ключевое слово internal - это не одно и то же, оно позволяет всем классам всей сборки обращаться к членам internal, а friend позволяет дать определенному классу полный доступ к одному другой класс)

Ответ 1

Наличие друзей в программировании более или менее считается "грязным" и простым в использовании. Он нарушает отношения между классами и подрывает некоторые фундаментальные атрибуты языка OO.

Это, как говорится, хорошая функция, и я использовал ее много раз в С++; и хотел бы использовать его в С# тоже. Но я держал пари из-за С# "чистого" OOness (по сравнению с С++ псевдо OOness). MS решила, что, поскольку Java не имеет ключевого слова для друга, С# тоже не должен (просто шутите;))

С серьезной нотой: внутренняя не так хорошо, как друг, но она выполняет свою работу. Помните, что редко вы будете распространять свой код сторонним разработчикам, не имея DLL; так что, пока вы и ваша команда узнаете о внутренних занятиях и их использовании, вы должны быть в порядке.

EDIT Позвольте мне пояснить, как ключевое слово friend подрывает ООП.

Частные и защищенные переменные и методы, возможно, являются одной из наиболее важных составляющих ООП. Идея о том, что объекты могут хранить данные или логику, которые только они могут использовать, позволяет вам написать свою реализацию функциональных возможностей независимо от вашей среды - и что ваша среда не может изменять информацию о состоянии, которую она не подходит для обработки. Используя друга, вы комбинируете реализацию двух классов вместе, что намного хуже, если вы просто связали их интерфейс.

Ответ 2

На боковой ноте. Использование друга заключается не в нарушении инкапсуляции, а, наоборот, в обеспечении его соблюдения. Подобно аксессурам + мутаторам, перегрузкам операторов, наложению наследования, понижению и т.д., Это часто неправильно используется, но это не означает, что ключевое слово не имеет или, что еще хуже, плохой цели.

См. Конрад Рудольф сообщение в другом потоке, или если вы предпочитаете видеть соответствующая запись в FAQ по С++.

Ответ 3

Для информации другая связанная, но не совсем та же самая вещь в .NET: [InternalsVisibleTo], которая позволяет ассемблировать другую сборку (например, сборку unit test), которая (эффективно) имеет "внутреннюю" "доступ к типам/элементам в исходной сборке.

Ответ 4

Вы должны иметь возможность выполнять те же самые вещи, которые "друг" используется в С++, используя интерфейсы на С#. Это требует, чтобы вы явно определяли, какие члены передаются между двумя классами, что является дополнительной работой, но может также облегчить понимание кода.

Если у кого-то есть пример разумного использования "друга", который нельзя моделировать с использованием интерфейсов, пожалуйста, поделитесь им! Я хотел бы лучше понять различия между С++ и С#.

Ответ 5

С friend разработчик С++ имеет точный контроль над тем, кому подвержены частные члены *. Но он вынужден разоблачить каждого из частных членов.

С internal конструктор С# имеет точный контроль над набором частных членов hes exposing. Очевидно, он может выставить только одного частного участника. Но он будет доступен для всех классов сборки.

Как правило, дизайнер хочет выставить только несколько частных методов для выбора нескольких других классов. Например, в шаблоне класса factory может потребоваться, чтобы класс C1 был создан только классом factory CF1. Поэтому класс C1 может иметь защищенный конструктор и класс друга factory CF1.

Как вы можете видеть, у нас есть 2 измерения, по которым может быть нарушено инкапсуляция. friend нарушает его по одному измерению, internal делает это по другому. Какой из них хуже в концепции инкапсуляции? Сложно сказать. Но было бы неплохо иметь как friend, так и internal. Кроме того, хорошим дополнением к этим двум будет ключевое слово третьего типа, которое будет использоваться по принципу "один за другим" (например, internal) и задает целевой класс (например, friend).

* Для краткости я буду использовать "private" вместо "частного и/или защищенного".

- Ник

Ответ 6

Вы можете приблизиться к "другу" С++ с ключевым словом С# "internal" .

Ответ 7

На самом деле это не проблема с С#. Это фундаментальное ограничение в IL. С# ограничивается этим, как и любой другой .Net-язык, который пытается быть поддающимся проверке. Это ограничение также включает управляемые классы, определенные в С++/CLI (Раздел 20.5).

Говоря, я думаю, что у Нельсона есть хорошее объяснение, почему это плохо.

Ответ 8

Друг чрезвычайно полезен при написании unit test.

В то время как это связано с незначительным загрязнением вашего объявления класса, это также напоминание о компиляторе о том, какие тесты действительно могут заботиться о внутреннем состоянии класса.

Очень полезная и чистая идиома, которую я нашел, - это когда у меня есть классы factory, что делает их друзьями из создаваемых элементов, у которых есть защищенный конструктор. Более конкретно, это было, когда у меня был единственный factory, ответственный за создание соответствующих объектов рендеринга для объектов записи отчетов, рендеринга в заданную среду. В этом случае у вас есть одна точка знания о взаимосвязи между классами Report-writer (такие, как блоки изображения, полосы макета, заголовки страниц и т.д.) И их соответствующие объекты рендеринга.

Ответ 9

В С# отсутствует ключевое слово "friend" по той же причине, что и его детерминированное уничтожение. Изменяющиеся соглашения заставляют людей чувствовать себя умными, как будто их новые способы превосходят другие старые способы. Это все о гордости.

Говорить, что "классы друзей плохие" столь же близоруки, как и другие неквалифицированные утверждения типа "не использовать gotos" или "Linux лучше Windows".

Ключевое слово "friend" в сочетании с прокси-классом - отличный способ показать только некоторые части класса конкретным другим классам. Прокси-класс может выступать в качестве надежного барьера для всех других классов. "public" не допускает такого таргетинга, а использование "защищенного" для получения эффекта с наследованием неудобно, если на самом деле нет концептуальных отношений "есть".

Ответ 10

Фактически, С# дает возможность получить такое же поведение в чистом ООП без специальных слов - это частные интерфейсы.

Что касается вопроса Что такое эквивалент друга С#?, он был помечен как дубликат этой статьи, и никто не предлагает действительно хорошую реализацию - я покажу ответ на оба вопрос здесь.

Основная идея взяла здесь: Что такое частный интерфейс?

Скажем, нам нужен какой-то класс, который мог бы управлять экземплярами других классов и вызывать на них специальные методы. Мы не хотим давать возможность использовать эти методы для любых других классов. Это то же самое, что и ключевое слово friend С++ в мире С++.

Я считаю, что хорошим примером в реальной практике может быть модель Full State Machine, где некоторый контроллер обновляет текущий объект состояния и переключается на другой объект состояния, когда это необходимо.

Вы можете:

  • Самый простой и худший способ сделать метод Update() общедоступным - надейтесь все понимают, почему это плохо.
  • Следующий путь - отметить его как внутренний. Это достаточно хорошо, если вы классы в другую сборку, но даже тогда каждый класс в этой сборке может вызывать каждый внутренний метод.
  • Использовать частный/защищенный интерфейс - и я следовал этому пути.

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it 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 impossible to write Controller.IState p = new StateBase();
        //Controller.IState is hidden
        StateBase p = new StateBase();
        //p.Update(); //is not accessible
    }
}

Ну а как насчет наследования?

Нам нужно использовать технику, описанную в Поскольку явные реализации элементов интерфейса не могут быть объявлены виртуальными и маркировать IState как защищенные, чтобы дать возможность выводить из Контроллер тоже.

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

Надеюсь, я расскажу обо всех случаях, и мой ответ был полезен.

Ответ 11

Прекратите делать оправдания для этого ограничения. друг плох, но внутреннее хорошо? они - одно и то же, только тот друг дает вам более точный контроль над тем, кому разрешен доступ, а кто нет.

Это для обеспечения соблюдения парадигмы инкапсуляции? поэтому вам нужно написать методы доступа, и что теперь? как вы должны остановить всех (кроме методов класса B) от вызова этих методов? вы не можете, потому что вы тоже не можете контролировать это из-за отсутствия "друга".

Язык программирования не идеален. С# - один из лучших языков, которые я видел, но глупые оправдания за недостающие функции никому не помогают. В С++ я пропускаю легкую систему событий/делегатов, отражение (+ автоматическое де-сериализация) и foreach, но в С# я пропускаю перегрузку оператора (да, продолжайте говорить мне, что вам это не нужно), параметры по умолчанию, const которые не могут быть обойдены, множественное наследование (да, продолжайте говорить мне, что вам это не нужно, и интерфейсы были достаточной заменой) и возможность решить удалить экземпляр из памяти (нет, это не ужасно плохо, если вы не являетесь Tinkerer)

Ответ 12

Существует InternalsVisibleToAttribute с .Net 3, но я подозреваю, что они добавили его только для тестирования сборок после повышения модульного тестирования. Я не вижу других причин для его использования.

Он работает на уровне сборки, но он выполняет ту работу, где нет внутреннего; то есть, где вы хотите распространять сборку, но хотите, чтобы другая нераспределенная сборка имела привилегированный доступ к ней.

Совершенно справедливо они требуют, чтобы сбор для друзей был сильным ключом, чтобы кто-то не создавал притворяющегося друга рядом с вашей защищенной сборкой.

Ответ 13

Я прочитал много умных комментариев о ключевом слове "friend", и я согласен с тем, что это полезная вещь, но я думаю, что "внутреннее" ключевое слово менее полезно, и оба они все еще плохо для чистого программирования 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) { }
}

Что вы думаете об этом? Я считаю это наиболее распространенным и чисто объектно-ориентированным решением. Вы можете открыть доступ к любому методу, который вы выбрали для любого класса, который вы хотите.

Ответ 14

Я подозреваю, что это как-то связано с моделью компиляции С# - построение IL компиляции JIT, которая во время выполнения. т.е. та же причина, по которой обобщенные свойства С# принципиально отличаются от генераторов С++.

Ответ 15

Если вы работаете с С++, и вы находите свое собственное имя с помощью ключевого слова friend, это очень сильное указание на то, что у вас есть проблема с дизайном, потому что почему heck класс должен получить доступ к закрытым членам другого класса?

Ответ 16

вы можете сохранить его конфиденциальным и использовать функции отражения для вызова. Структура тестирования может сделать это, если вы попросите его проверить частную функцию

Ответ 17

Я регулярно использовал друга, и я не думаю, что это нарушение ООП или признак какого-либо недостатка дизайна. Есть несколько мест, где это наиболее эффективное средство для правильного завершения с наименьшим количеством кода.

Один конкретный пример - создание интерфейсных сборок, которые обеспечивают интерфейс связи для другого программного обеспечения. Как правило, существует несколько классов тяжеловесов, которые обрабатывают сложность параметров протокола и одноранговых узлов и обеспечивают относительно простую модель соединения/чтения/записи/пересылки/разъединения, включающую передачу сообщений и уведомлений между клиентским приложением и сборкой. Эти сообщения/уведомления должны быть завернуты в классы. Обычно атрибуты должны обрабатываться программным обеспечением протокола, так как оно является их создателем, но многие вещи должны оставаться доступными только для чтения во внешнем мире.

Просто глупо объявлять, что это нарушение ООП для класса протокола/ "создателя", чтобы иметь интимный доступ ко всем созданным классам - классу создателя пришлось бить munge каждый бит данных по пути вверх. То, что я нашел наиболее важным, состоит в том, чтобы свести к минимуму все дополнительные строки BS, к которым обычно приводит модель OOP для OOP Sake. Дополнительные спагетти просто делают больше ошибок.

Знают ли люди, что вы можете применить внутреннее ключевое слово на уровне атрибута, свойства и метода? Это не только для объявления класса верхнего уровня (хотя большинство примеров, похоже, показывают это.)

Если у вас есть класс С++, который использует ключевое слово friend, и вы хотите эмулировать его в классе С#: 1. объявить класс класса С# 2. объявите все атрибуты/свойства/методы, которые защищены на С++ и, таким образом, доступны для друзей как внутренние в С# 3. создавать свойства только для чтения для общего доступа ко всем внутренним атрибутам и свойствам

Я согласен, что он не на 100% совпадает с другом, а unit test - очень ценный пример необходимости чего-то вроде друга (как и код регистрации протокола анализатора). Однако внутренняя функция обеспечивает доступ к классам, которые вы хотите подвергнуть воздействию, а [InternalVisibleTo()] обрабатывает остальные - кажется, что он был создан специально для unit test.

Что касается друга "лучше, потому что вы можете прямо контролировать, какие классы имеют доступ" - что, черт возьми, это группа подозрительных классов зла, которые делают в одной сборке в первую очередь? Разделите свои сборки!

Ответ 18

Дружба может быть смоделирована путем разделения интерфейсов и реализаций. Идея такова: "Требовать конкретный экземпляр, но ограничивать доступ к строительству этого экземпляра".

Например

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. Он имеет частный конструктор, а метод factory New() возвращает интерфейс.

Подробнее см. мою статью Друзья и внутренние интерфейсные элементы без каких-либо затрат с кодированием в интерфейсы.

Ответ 19

Некоторые предположили, что вещи могут выйти из-под контроля, используя друга. Я бы согласился, но это не уменьшает его полезности. Я не уверен, что друг неизбежно повредит парадигме OO, чем все публичные члены вашего класса. Разумеется, этот язык позволит вам публиковать всех ваших членов, но это дисциплинированный программист, который избегает такого типа шаблонов проектирования. Подобным образом дисциплинированный программист резервирует использование друга для конкретных случаев, когда это имеет смысл. В некоторых случаях я чувствую, что внутри слишком много. Зачем выставлять класс или метод ко всему в сборке?

У меня есть страница ASP.NET, которая наследует мою собственную базовую страницу, которая, в свою очередь, наследует System.Web.UI.Page. На этой странице у меня есть код, который обрабатывает сообщения об ошибках конечных пользователей для приложения в защищенном методе

ReportError("Uh Oh!");

Теперь у меня есть пользовательский элемент управления, который содержится на странице. Я хочу, чтобы пользовательский элемент управления мог вызвать методы отчетности об ошибках на странице.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Он не может этого сделать, если метод ReportError защищен. Я могу сделать его внутренним, но он подвергается воздействию любого кода в сборке. Я просто хочу, чтобы он отображался в элементах пользовательского интерфейса, которые являются частью текущей страницы (включая дочерние элементы управления). В частности, я хочу, чтобы мой базовый класс управления определял те же методы отчетности об ошибках и просто вызывал методы на базовой странице.

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

Как я вижу, концепция друга больше не подвержена риску, чем открытию публики или созданию публичных методов или свойств для доступа к членам. Если какой-либо друг позволяет еще один уровень детализации в доступности данных и позволяет вам сузить эту доступность, а не расширять ее с помощью внутренних или публичных.

Ответ 20

B.s.d.

Было заявлено, что друзья болят чисто OOness. Который я согласен.

Было также указано, что друзья помогают инкапсулировать, что я также согласен.

Я думаю, что дружбу следует добавить к методологии OO, но не совсем так, как на С++. Я хотел бы иметь некоторые поля/методы, к которым может получить доступ мой класс друзей, но я НЕ хотел бы, чтобы они получили доступ ко всем моим полям/методам. Как и в реальной жизни, я позволю своим друзьям открыть мой личный холодильник, но я бы не позволил им получить доступ к моему банковскому счету.

Можно реализовать это, как следует

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

Это, конечно же, вызовет предупреждение компилятора и повредит intellisense. Но он выполнит эту работу.

С одной стороны, я думаю, что уверенный программист должен сделать блок тестирования без доступа к частным членам. это совсем не подходит, но попробуйте прочитать о TDD. однако, если вы все еще хотите это сделать (имея С++, как друзья), попробуйте что-то вроде

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

чтобы вы записывали весь свой код без указания UNIT_TESTING, и когда вы хотите выполнить тестирование модуля, добавьте #define UNIT_TESTING в первую строку файла (и напишите весь код, который выполняет модульное тестирование в #if UNIT_TESTING). Это должно быть тщательно обработано.

Поскольку я думаю, что модульное тестирование - плохой пример использования друзей, я бы привел пример, почему я думаю, что друзья могут быть хорошими. Предположим, что у вас есть система взлома (класс). При использовании система разрушения изношена и нуждается в ремонте. Теперь вы хотите, чтобы это исправило только лицензированного механика. Чтобы сделать пример менее тривиальным, я бы сказал, что механик использовал бы свою личную отвертку, чтобы исправить это. Вот почему класс механиков должен быть другом класса breakSystem.

Ответ 21

Дружба также может быть смоделирована с помощью "агентов" - некоторых внутренних классов. Рассмотрим следующий пример:

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

Это может быть намного проще, если используется для доступа только к статическим членам. Преимущества для такой реализации заключаются в том, что все типы объявляются во внутренней области классов дружбы и, в отличие от интерфейсов, позволяют доступ к статическим членам.

Ответ 22

Поля, к которым доступны ВСЕ классы, относятся к public.

Поля, для которых НЕ доступны для всех других классов, являются private.

(если поля принадлежат (объявленному внутри) базовым классу, то они вместо protected)

Поля, в которых только их класс владельца могут обращаться к ним, являются private, и не имеют свойств и получают заданные методы.

Поля, к которым могут обращаться только их классы-владельцы и некоторые, к ним относятся private, и каждый из них имеет специальные методы private get и set и public совместно использовать.

Некоторые другие классы, которые могут также обращаться к этим полям, будут иметь несколько полей private типов делегировать и специальные public прямые методы.

Пример:

using System;
class A
{
    private int integer; //In the meantime, it seems that only A can access this integer
    private int GetInteger() //Get method is not public, because we don't want all other classes to use this integer
    {
        return this.integer;
    }
    private void SetInteger(int value) //Set method is not public, because we don't want all other classes to modify this integer
    {
        this.integer = value;
    }
    public void Share(ref B b) //I use the 'ref' keyword, to prevent the 'null' value in this argument, but if you call this method in a constructor of B, or in one of its methods, so you will have to remove the 'ref' keyword, or make overload of the same method without the 'ref' keyword, because 'ref this' is not allowed
    {
        b.DirectGetIntegerAndSetIntegerMethods(this.GetInteger, this.SetInteger);
    }
    public void PrintInteger()
    {
        Console.WriteLine(this.integer);
    }
}
class B //This class can access the 'integer' of A too, i.e. the 'integer' of A is "public" only for B
{
    private Func<int> GetInteger; //Will execute the 'GetInteger' method of A
    private Action<int> SetInteger; //Will execute the 'SetInteger' method of A
    public void DirectGetIntegerAndSetIntegerMethods(Func<int> getInteger, Action<int> setInteger)
    {
        this.GetInteger = getInteger;
        this.SetInteger = setInteger;
    }
    public void Increment()
    {
        this.SetInteger(this.GetInteger() + 1);
    }
}
class Program
{
     static void Main(string[] args)
     {
         A a = new A(); //Created new instance of A, and also new Int32 was initialized inside it and set to its default value 0, but unable to get or set its value, only just print it.
         a.PrintInteger();
         B b = new B(); //Must create new instance of B, in order to change the integer of A to some value. For example, I will use b, to change the integer of a to 3
         a.Share(ref b); //But before the change, I must tell 'a' to share his GetInteger and SetInteger methods to 'b', so 'b' will also be able execute them, through his Func<int> and Action<int> delegates, because GetInteger and SetInteger methods of A are private and cannot be executed directly. 
         for (int i = 0; i < 3; i++)
            b.Increment();
         a.PrintInteger(); //Print the integer of 'a' again after change.
         //Now the output of the console is:
         //0
         //3
     }
}

Вы должны знать, что использование ключевого слова "friend" на С++ позволяет некоторым классам делиться своими частными членами с некоторыми другими классами напрямую.

Из-за этого ключевое слово "friend" не существует в С#, классы не имеют возможности делиться своими частными полями с некоторыми другими классами напрямую, но есть способ имитировать его действительно как я показанный выше.

Знаете ли вы, что ключевое слово "friend" на С++ также позволяет в реализации некоторых функций обращаться к закрытым членам некоторых экземпляров некоторых типов классов?

Ответ да, и я покажу вам, как имитировать это тоже в С#:

Пример:

using System;
using System.Reflection; //New namespace is added
using System.Diagnostics; //Another new namespace is added too

class Person
{
    private readonly StackTrace st = new StackTrace(); //Helper object
    private readonly MethodInfo mi = typeof(Program).GetMethod("Foo"); //The MethodInfo of the method Foo, which will be declared and defined in class Program later, is the friend of the class Person
    //Both objects above are readonly, because they always reference the same objects that they were initialized with.
    private string name_of_his_dog; //Only Person can access this field
    private string GetTheNameOfHisDog() //Not public so that not all methods will be able to get this name
    {
        return this.name_of_his_dog;
    }
    private void SetTheNameOfHisDog(string new_name) //Not public so that not all methods will be able to set this name
    {
        this.name_of_his_dog = new_name;
    }
    public Func<string> ShareTheGetTheNameOfHisDogMethod() //Returns null, if the previous method that called this method is not friend of this class Person
    {
        if (this.st.GetFrame(1).GetMethod() == this.mi)
            return this.GetTheNameOfHisDog;
        return null;
    }
    public Action<string> ShareTheSetTheNameOfHisDogMethod() //Same as above
    {
        if (this.st.GetFrame(1).GetMethod() == this.mi)
            return this.SetTheNameOfHisDog;
        return null;
    }
    public void PrintTheNameOfHisDog()
    {
        Console.WriteLine(this.name_of_his_dog);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Person person = Foo();
        person.PrintTheNameOfHisDog();
        Func<string> getTheNameOfHisDog = person.ShareTheGetTheNameOfHisDogMethod(); //returns null, Main is not friend of Person
        Action<string> setTheNameOfHisDog = person.ShareTheSetTheNameOfHisDogMethod(); //returns null too, for the same reason
       setTheNameOfHisDog("Pointer"); //Runtime Error: Object reference not set to an instance of an object
       Console.WriteLine(getTheNameOfHisDog()); //Same runtime error
       //Output before runtime error:
       //Boxer
       //Boxer
    }
    public static Person Foo() //Only this method can get and set the name of the dog that belongs to the person
    {
        Person person = new Person();
        Func<string> getTheNameOfHisDog = person.ShareTheGetTheNameOfHisDogMethod();
        Action<string> setTheNameOfHisDog = person.ShareTheSetTheNameOfHisDogMethod();
        setTheNameOfHisDog("Boxer");
        Console.WriteLine(getTheNameOfHisDog());
        return person;
    }
}

Я должен признать, что до того, как я разместил этот код, я не знал, как узнать MethodInfo предыдущего метода, который вызвал текущий метод, но ответ Firas Assaad помог мне, благодаря ему тоже.

Как найти метод, который вызвал текущий метод?

Он предложил использовать класс System.Diagnostics.StackTrace

Надеюсь, что у вас есть моя идея, и это помогает и отвечает на ваш вопрос.

Я не нашел этот ответ нигде в Интернете, я думал об этой идее, используя свой мозг.

Ответ 23

Я отвечу только на вопрос "Как".

Здесь так много ответов, но я хотел бы предложить "шаблон дизайна" для достижения этой функции. Я буду использовать простой языковой механизм, который включает в себя:

  • Интерфейсы
  • Вложенный класс

Например, у нас есть 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 = gps;
        Name = name;
        privateData = 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()
        {
            return Stu.GPS;
        }

    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend)
    {
        friend.Register(privateData);
    }

}


public class University
{
    List<IStudentFriend> 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());
        }
    }
}

static void Main(string[] args)
    {
        University Technion = new University();


        Student Alex = new Student("Alex", 98);
        Alex.RegisterFriend(Technion);
        Student Jo = new Student("Jo", 91);
        Jo.RegisterFriend(Technion);

        Technion.PrintAllStudentsGPS();

        Console.ReadLine();
    }