Обход C # для общих классов друзей - PullRequest
4 голосов
/ 27 марта 2019

Итак, у меня есть сценарий использования, похожий на этот , но с некоторыми дополнительными особенностями, которые, как мне кажется, требуют нового вопроса.( связанных вопросов , для справки)

Я пишу структуру данных, которая реализует цикл .Базовый дизайн выглядит примерно так:

public class Cycle<T>
{
    public Node<T> Origin { get; private set; }
    public int Count { get; private set; }
}

public class Node<T>
{
    public Cycle<T> Cycle { get; private set; }
    public Node<T> Next { get; private set; }
    public Node<T> Previous { get; private set; }
    public T Value { get; set; }
}

Однако я хочу реализовать все из следующих вариантов поведения:

  1. Обновить Cycle.Count как узлывставлены / удалены
  2. Разрешить "пустым" Cycle (то есть Origin = null) создать новый Node для Origin
  3. Запретить создание нового Nodeобъекты вне случаев 1 & 2
  4. Избегайте предоставления методов, которые не должны вызываться другими классами, кроме этих двух

В C ++ я бы просто сделал каждый классfriend другого.Но в C # я не вижу способа заставить его работать.

Я знаю, что могу выполнить все, кроме # 3, если вложу Node в Cycle и выставлю только один конструктор дляNode, вот так:

public class Cycle<T>
{
    public Node Origin { get; private set; }
    public int Count { get; private set; }

    public class Node
    {
        public Cycle<T> Cycle { get; private set; }
        public Node Next { get; private set; }
        public Node Previous { get; private set; }
        public T Value { get; set; }

        internal Node<T>(Cycle<T> cycle)
        {
            if (cycle.Origin != null)
                throw new InvalidOperationException();
            else
            {
                Cycle = cycle;
                Next = this;
                Previous = this;
                cycle.Origin = this;
                cycle.Count = 1;
            }
        }
    }
}

Но, как вы можете видеть, я могу пройти только до internal, поэтому мне все еще нужно проверить целостность данных или нарушить инкапсуляцию.

У меня есть одна"умная" идея, но это своего рода черная магия ответ:

public abstract class Cycle<T>
{
    public Node Origin { get; private set; }
    public int Count { get; private set; }

    public sealed class Node
    {
        private CycleInternal _cycle;
        public Cycle<T> Cycle { get { return _cycle; } }
        public Node Next { get; private set; }
        public Node Previous { get; private set; }
        public T Value { get; set; }

        // this constructor can be called by CycleInternal, but not other classes!
        private Node(CycleInternal cycle)
        {
            Cycle = cycle;
            Next = this;
            Previous = this;
            cycle.Origin = this;
            cycle.Count = 1;
        }

        private sealed class CycleInternal :  Cycle<T>
        {
            // this constructor can be called by Node, but not other classes!
            public CycleInternal() {}
        }
    }
}

В этом случае меня беспокоит то, что что-то ещемог наследовать от цикла;Можно ли предотвратить это, сделав конструктор для Cycle частнымИли я просто параноик?

1 Ответ

6 голосов
/ 28 марта 2019

internal выставляет его другим классам в той же сборке, что я не хочу делать

Мой совет: преодолеть этот страх и пометить его internal,

Я слышал этот запрос о возможности - что C # реализует семантику друзей в стиле C ++ - много-много раз.Эта функция обычно мотивируется тем, что «если я разрешу любому классу в моей сборке праздновать внутреннее состояние, мои коллеги будут злоупотреблять этой привилегией».

Но ваши коллеги уже имеют возможность злоупотреблять этой привилегией.потому что ваши коллеги уже могут добавлять друзей (в C ++) или создавать внутренние методы, которые являются задними дверями (в C #).

Система доступности C # просто непредназначен для защиты вас от сценариев, в которых люди, имеющие доступ для записи к исходному коду, враждебны вашим интересам.Точно так же, как private означает «это деталь реализации этого класса», а protected означает «это деталь реализации этой иерархии», internal означает «это деталь реализации этой сборки».несут ответственность за правильную работу этой сборки. Нельзя доверять разумному использованию своих полномочий, найдите лучших коллег. Если вашим типам необходим доступ к внутренним деталям друг друга для правильной работы сборки в целом, - это то, что internal предназначен для , поэтому используйте его.

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

Чтобы ответить на ваш конкретный вопросstions:

я беспокоюсь, что что-то еще может унаследовать от Cycle;Можно ли предотвратить это, сделав конструктор для Cycle частным

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

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

В сторону: Обычно я делаю так, чтобы создавать "классы дел", такие как:

abstract class ImmutableStack<T>
{
  private ImmutableStack() { }
  private sealed class EmptyStack : ImmutableStack<T> { ... }
  private sealed class NormalStack : ImmutableStack<T> { ... }

и так далее.Это хороший способ сделать детали реализации типа распределенными по нескольким классам, но все же инкапсулированными в один класс.

Я просто параноик?

Это звучит для меня как да.

...