Почему C # не разрешает функции, не являющиеся членами, такие как C ++ - PullRequest
64 голосов
/ 21 июня 2009

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

Вот что говорит стандарт C ++ / CLI:

[Примечание: функции, не являющиеся членами, обрабатываются CLI как члены некоторого безымянного класса; однако в исходном коде C ++ / CLI такие функции не могут быть явно определены с этим именем класса. конечная нота]

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

Итак, мой вопрос: почему бы C # не реализовать что-то подобное? Или вы думаете, что не должно быть функций, не являющихся членами, и каждый метод должен принадлежать некоторому классу?

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

Есть мысли ...?

Ответы [ 12 ]

86 голосов
/ 22 июня 2009

Смотрите эту запись в блоге:

http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx

(...)

Меня спрашивают "почему в C # не реализована функция X?" все время. Ответ всегда один и тот же: потому что никто никогда не проектировал, не определял, не реализовывал, не тестировал, не документировал и не поставлял эту функцию. Все шесть из этих вещей необходимы для реализации какой-либо функции. Все они стоят огромных затрат времени, сил и денег. Функции недешевы, и мы очень стараемся, чтобы мы поставляли только те функции, которые дают наилучшие возможные преимущества нашим пользователям, учитывая наш ограниченный бюджет времени, усилий и денег.

Я понимаю, что такой общий ответ, вероятно, не относится к конкретному вопросу.

В данном конкретном случае очевидная польза для пользователя в прошлом была недостаточно велика, чтобы оправдать возможные осложнения для языка. Устанавливая ограничения в отношении того, как различные языковые сущности вкладываются друг в друга, мы (1) ограничиваем легальные программы в общем, легко понимаемом стиле и (2) делаем возможным определение правил «поиска идентификаторов», которые являются понятными, определяемыми, реализуемыми, тестируемыми и документируемый.

Ограничивая тела метода всегда быть внутри структуры или класса, мы облегчаем рассуждение о значении неквалифицированного идентификатора, используемого в контексте вызова; такая вещь всегда является вызываемым членом текущего типа (или базового типа).

(...)

и эта последующая публикация:

http://blogs.msdn.com/ericlippert/archive/2009/06/24/it-already-is-a-scripting-language.aspx

(...)

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

(выделение из исходного текста)

38 голосов
/ 21 июня 2009

C # не позволяет этого, потому что Java не позволяет этого.

Я могу придумать несколько причин, почему разработчики Java, вероятно, не допустили этого

  • Java был разработан, чтобы быть простым. Они пытались создать язык без случайных ярлыков, чтобы у вас, как правило, был только один простой способ сделать все, даже если бы другие подходы были чище или более краткими. Они хотели минимизировать кривую обучения, и изучение «класс может содержать методы» проще, чем «класс может содержать методы, а функции могут существовать вне классов».
  • Внешне это выглядит менее объектно-ориентированным. (Все, что не является частью объекта, очевидно, не может быть объектно-ориентированным? Может ли это? Конечно, C ++ говорит, что да, но C ++ не участвовал в этом решении)

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

В C ++, где разрешены функции, не являющиеся членами, их часто предпочитают по нескольким причинам:

  • Это помогает инкапсуляции. Чем меньше методов имеют доступ к закрытым членам класса, тем проще будет этот класс для рефакторинга или обслуживания. Инкапсуляция является важной частью ООП.
  • Код можно использовать намного проще, если он не является частью класса. Например, стандартная библиотека C ++ определяет std::find или std :: sort` как функции, не являющиеся членами, чтобы их можно было повторно использовать в любых типах последовательностей, будь то массивы, множества, связанные списки или (для std :: найди, как минимум) потоки. Повторное использование кода также является важной частью ООП.
  • Это дает нам лучшую развязку. Функция find не должна знать о классе LinkedList, чтобы иметь возможность работать с ним. Если бы он был определен как функция-член, он был бы членом класса LinkedList, в основном объединяя две концепции в один большой большой двоичный объект.
  • расширяемость. Если вы согласны с тем, что интерфейс класса - это не только «все его открытые члены», но также «все функции, не являющиеся членами класса, которые работают с классом», тогда становится возможным расширить интерфейс класса без необходимости редактировать или даже перекомпилировать сам класс.

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

На самом деле, C #, похоже, понял то же самое, гораздо позже. Как вы думаете, почему были добавлены методы расширения? Они являются попыткой достичь вышеуказанного, сохраняя при этом простой Java-подобный синтаксис. Лямбды также являются интересными примерами, поскольку они также являются по существу небольшими функциями, которые определяются свободно, а не как члены какого-либо конкретного класса. Так что да, концепция функций, не являющихся членами, полезна, и дизайнеры C # осознали то же самое. Они только что попытались проникнуть в концепцию через заднюю дверь.

http://www.ddj.com/cpp/184401197 и http://www.gotw.ca/publications/mill02.htm - две статьи, написанные экспертами C ++ по этому вопросу.

11 голосов
/ 21 июня 2009

Функции, не являющиеся членами, - хорошая вещь , потому что они улучшают инкапсуляцию и уменьшают связь между типами. Большинство современных языков программирования, таких как Haskell и F #, поддерживают бесплатные функции.

9 голосов
/ 21 июня 2009

В чем преимущество , а не , помещая каждый метод в именованный класс? Почему функция, не являющаяся членом, "загрязняет" интерфейс класса? Если вы не хотите, чтобы это было частью публичного API класса, либо не делайте его публичным, либо не помещайте его в этот класс. Вы всегда можете создать другой класс.

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

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

3 голосов
/ 21 июня 2009
  • Наличие всего кода в классах обеспечивает более мощный набор возможностей отражения.
  • Позволяет использовать статические инициализаторы, которые могут инициализировать данные, необходимые статическим методам в классе.
  • Он предотвращает конфликты имен между методами, явно заключая их в модуль, который не может быть добавлен другим модулем компиляции.
3 голосов
/ 21 июня 2009

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

Вполне возможно, что в будущей версии C # будет добавлена ​​возможность писать директиву using, которая позволяет получить доступ к статическим членам класса без указания имени класса:

using System.Linq.Enumerable; // Enumerable is a static class

...

IEnumerable<int> range = Range(1, 10); // finds Enumerable.Range

Тогда не нужно будет менять CLS и существующие библиотеки.

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

2 голосов
/ 21 июня 2009

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

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

1 голос
/ 21 июня 2009

Помните кое-что: C ++ - намного более сложный язык, чем C #. И хотя они могут быть похожи синтаксически, семантически они очень разные звери. Вы бы не подумали, что было бы ужасно трудно сделать подобное изменение, но я мог видеть, как это могло быть. У ANTLR есть хорошая вики-страница под названием Что делает проблему с языком трудной? Это хорошо, чтобы проконсультироваться по таким вопросам. В этом случае:

Контекстно-зависимый лексер? Вы не можете решить, с каким символом вокабуля соответствовать, если не знаете, какое предложение вы анализируете.

Теперь вместо того, чтобы беспокоиться о функциях, определенных в классах, мы должны беспокоиться о функциях, определенных вне классов. Концептуально нет большой разницы. Но с точки зрения лексизации и синтаксического анализа кода, теперь у вас есть дополнительная проблема: «если функция находится вне класса, она принадлежит этому безымянному классу. Однако, если она находится внутри класса, то она принадлежит этому». класс. "

Кроме того, если компилятор встречает метод, подобный этому:

public void Foo()
{
    Bar();
}

... теперь он должен ответить на вопрос "находится ли Bar внутри этого класса или это глобальный класс?"

Прямые или внешние ссылки? Т.е. нужно несколько проходов? Паскаль имеет «прямую» ссылку для обработки внутрифайловых ссылок на процедуры, но ссылки на процедуры в других файлах с помощью предложений USES и т. Д. Требуют специальной обработки.

Это еще одна вещь, которая вызывает проблемы. Помните, что C # не требует предварительных объявлений. Компилятор сделает один проход, чтобы определить, какие классы названы и какие функции эти классы содержат. Теперь вам нужно позаботиться о поиске классов и функций, где функции могут находиться как внутри, так и снаружи класса. Об этом не нужно беспокоиться парсеру C ++, поскольку он разбирает все по порядку.

Теперь не поймите меня неправильно, вероятно, это можно сделать в C #, и я бы, вероятно, использовал такую ​​функцию. Но стоит ли вообще преодолевать эти препятствия, когда вы можете просто ввести имя класса перед статическим методом?

1 голос
/ 21 июня 2009

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

Например, некоторые вещи, для которых вы можете их использовать, могут быть обработаны с помощью Методы расширения

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

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

0 голосов
/ 27 октября 2018

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

...