Почему C# не поддерживает функции, не являющиеся членами, такие как C++

StackOverflow https://stackoverflow.com/questions/1024171

Вопрос

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

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

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

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

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

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

Есть идеи..?

Это было полезно?

Решение

См. эту публикацию в блоге:

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

(...)

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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 могли ориентироваться на классы.

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

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

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

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

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

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

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

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

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

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

Кроме того, если компилятор встретит такой метод:

public void Foo()
{
    Bar();
}

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

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

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

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

Бесплатные функции очень полезны, если вы комбинируете их с уткой. Весь C ++ STL основан на этом. Поэтому я уверен, что C # представит бесплатные функции, когда им удастся добавить настоящие обобщения.

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

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

Если подумать, контраргументом будет " почему C ++ не поддерживает методы расширения " , а ответ лежит в их целях разработки.

C ++ предоставляет вам функции пространства имен, которые вы можете вызывать для любого объекта, который поможет вам "расширить" этот объект в некотором роде.

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

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

Возьмите следующий код, например:

template<typename T>
unsigned int findCount(vector<T>& vec, T data){
    unsigned int count = 0;
    for(auto val : vec)
        if(val == data) ++count;
    return count;
}

Теперь, если мне понадобилась эта функциональность для какой-то цели в моем классе, я могу просто добавить ее в пространство имен класса и использовать без " polluting " мой класс с этой функцией.

В C # вы можете достичь той же цели с помощью метода расширения:

static class Extensions {
    public static uint FindCount<T>(this List<T> list, T data) {
        uint counts = 0;
        foreach (var item in list)
            if (item.Equals(data)) ++counts;
        return counts;
    }
}

И оба они работают одинаково, что круто. Многие будут спорить о том, что в C ++ отсутствуют методы расширения, многие - о том, что в C # отсутствуют функции пространства имен.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top