Подтвердить что ты не робот

Почему частные виртуальные методы незаконны в С#?

Исходя из фона С++, это стало для меня неожиданностью. В С++ хорошая практика сделать виртуальные функции частными. Из http://www.gotw.ca/publications/mill18.htm: "Руководство № 2: Предпочтение делать виртуальные функции частными".

Я также цитирую блог Эрика Липперта, Knights-knaves-protected-and-internal:

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

Я понимаю, что в С# вы не сможете переопределить частный виртуальный метод в производном (но не вложенном) классе. Почему это так? В С++ спецификатор доступа не имеет никакого отношения к тому, можно ли переопределить функцию или нет.

4b9b3361

Ответ 1

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

Первый вопрос: "Почему частные виртуальные методы незаконны в С#?"

Вот аргументы против функции "частные виртуальные методы":

  • private virtual полезен только тогда, когда у вас есть вложенный производный класс. Это полезный шаблон, но гораздо менее распространенный, чем не-вложенная ситуация с производными классами.

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

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

  • Можно реализовать этот шаблон уже с существующими частями:

    abstract class C
    {
        private int CF() { whatever; }
        private Func<int> f;
        public C() { f = CF; } 
        private int F() { return f(); }
        private class D : C
        {
            private int DF() { whatever; }
            public D() { f = DF; }
        }
    

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

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

Аргументы для:

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

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

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

Второй вопрос: "Почему в С# вы не можете переопределить частный виртуальный метод в производном не-вложенном классе?"

Существует несколько причин.

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

  • Поскольку это имеет серьезные последствия для безопасности. Помните, что на С++ вы почти всегда компилируете код в приложение все сразу. У вас есть исходный код для всего; во многом по существу "внутренне" с точки зрения С++. В С# это совсем не так. Сторонние сборки могут легко получать публичные типы из библиотек и создавать новые расширения для этих классов, которые затем могут быть легко использованы вместо экземпляров базового класса. Поскольку виртуальные методы эффективно изменяют поведение класса, любой код, который зависит от соображений безопасности от инвариантов этого класса, должен быть тщательно разработан так, чтобы они не зависели от инвариантов, гарантированных базовым классом. Ограничение доступности виртуальных методов помогает гарантировать сохранение инвариантов этих методов.

  • Так как это дает другую форму проблемы хрупкого базового класса. С# был тщательно разработан, чтобы быть менее восприимчивым к проблеме хрупкого базового класса, чем другие языки OO. Если недопустимый виртуальный метод может быть переопределен в производном классе, тогда эта частная деталь реализации базового класса становится изменением при изменении. Провайдеры базовых классов должны иметь право изменять свои внутренние детали, не беспокоясь о том, что они сломали производные классы, которые зависят от них; в идеале должен поддерживаться только публичный документированный интерфейс к типу при изменении данных реализации.

Ответ 2

Поскольку частные методы могут ТОЛЬКО получать доступ из класса, определяющего их, поэтому частный виртуальный метод будет бесполезен. Вы хотите защитить виртуальный метод. Доступ к защищенному методу может получить класс, определяющий его и любые подклассы.

EDIT:

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

Ответ 3

Я предполагаю, что причина в том, что internal virtual делает почти то же самое, что и private virtual и несколько менее запутанна для тех, кто не знаком с идиомой private virtual.

В то время как только внутренние классы могут переопределять методы private virtual, только классность в вашей сборке может переопределять методы internal virtual.

Ответ 4

В С# (и в CLI, насколько я видел), "private" имеет довольно четкое и недвусмысленное значение: "доступно только в этом классе". Концепция виртуальных виртуальных винтов, которые все, не говоря уже о том, что пространства имен немного минного поля. Почему я должен заботиться о том, что вы назвали методом, который я даже не вижу, и получить предупреждение компилятора о том, что вы выбрали имя, которое вы уже зацепили за него?

Ответ 5

Поскольку у С# нет механизма для предоставления наследования public/private/protected, это то, что вы на самом деле после.

Даже в С++ частные члены не могут получить доступ к производным классам, но они могут ограничить видимость базового класса, указав наследование наследования:

class Derived : /*->>*/private/*<--*/ Base {
}

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

IMHO С# обеспечивает более сильное отношение IS-A посредством наследования с одним базовым классом, поэтому имеет смысл, что если автомобиль имеет двигатель, подкласс BMW не должен скрыть его.

С++ поддерживает множественное наследование, которое является менее строгим отношением IS-A - оно почти похоже на отношения HAS-A, где вы можете задействовать несколько несвязанных классов. Из-за способности вводить несколько базовых классов вы хотите более жесткий контроль над видимостью всех из них.

Ответ 6

Позвольте мне пояснить: С# не является С++.

С# спроектирован через несколько десятилетий после С++ и построен с использованием продвинутых идей на протяжении многих лет. По моему скромному мнению, С# четко определен и, наконец, обрабатывает ориентацию объекта правильным образом (imho). Он включает в себя инструкцию internal по какой-либо причине и не позволяет вам "виртуализировать" и переопределять частные методы. По какой-то причине.

Все описанные выше проблемы (внутренние классы, переопределяющие методы private virtual, используя абстрактный шаблон factory таким образом и т.д.), могут быть легко записаны другим способом с использованием интерфейсов и оператора internal. Сказав это, я должен сказать, что это довольно деликатный вопрос, нравится ли вам С++-путь или способ С#.

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

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