Почему C# не разрешает функции, не являющиеся членами, такие как C++
C# не позволит писать функции, не являющиеся членами, и каждый метод должен быть частью класса. Я думал, что это ограничение на всех языках CLI. Но я ошибся и обнаружил, что C++/CLI поддерживает функции, не являющиеся членами. Когда он скомпилирован, компилятор сделает метод членом какого-то безымянного класса.
Вот что говорит стандарт C++/CLI:
[Примечание: функции, не являющиеся членами, рассматриваются CLI как члены некоторого безымянного класса; однако в исходном коде C++/CLI такие функции не могут быть явно определены с этим именем класса. конечная нота]
Кодирование функций, не являющихся членами, в метаданных не определено. [Примечание: это не вызывает проблем взаимодействия, потому что такие функции не могут иметь публичную видимость. конечная нота]
Поэтому мой вопрос: почему бы C# не реализовать что-то подобное? Или вы думаете, что не должно быть функций, не являющихся членами, и каждый метод должен принадлежать некоторому классу?
Мое мнение - иметь поддержку функций, не являющихся членами, и это помогает избежать загрязнения интерфейса класса.
Какие-нибудь мысли..?
14 ответов
Смотрите это сообщение в блоге:
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++ не участвовал в этом решении)
Как я уже сказал в комментариях, я думаю, что это хороший вопрос, и есть много случаев, когда функции, не являющиеся членами, были бы предпочтительнее. (эта часть в основном является ответом на все остальные ответы, говорящие "тебе это не нужно")
В 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, большинство программистов легко смирились с тем, что любой метод является членом класса. Я не создаю каких-либо значительных препятствий и не делаю концепцию метода более узкой, что облегчает язык.
Однако, действительно, класс выводит объект, а объект выводит состояние, поэтому концепция класса, содержащая только статические методы, выглядит немного абсурдной.
Послушайте, другим языкам программирования сложно определить внутреннюю природу экземпляра функции с точки зрения компилятора. В Паскале и C экземпляры в основном определены как нечто, что может обрабатываться только как указатель. Тем более, что чтение / запись в исполняемые позиции кода - это то, против чего категорически противятся 7 из 9 профессоров информатики. Как член класса, никому не нужно заботиться о том, как обращаться с его проявлением, потому что тип этого проявления является производным от свойства класса. Можно создать что-то, что точно обрабатывается как глобальная функция: лямбда-функция, присвоенная переменной:
Func<int,int> myFunc = delegate(int var1)
{
Console.WriteLine("{0}",var1*2);
return var1*3;
};
. И ее можно просто вызвать как глобальную функцию по имени переменной. Если это так, разница будет заключаться в реализации нового типа объекта на самом нижнем уровне с таким же поведением, как и у другого. Опытные программисты считают это плохой практикой, и, возможно, из-за этого от нее отказались.
Помните кое-что: C++ - намного более сложный язык, чем C#. И хотя они могут быть похожи синтаксически, семантически они очень разные звери. Вы бы не подумали, что было бы ужасно трудно сделать такое изменение, но я мог видеть, как это могло быть. У ANTLR есть хорошая вики-страница " Что делает проблему с языком сложной? это хорошо, чтобы проконсультироваться по таким вопросам. В этом случае:
Контекстно-зависимый лексер? Вы не можете решить, с каким символом вокабуля соответствовать, если не знаете, какое предложение вы анализируете.
Теперь вместо того, чтобы беспокоиться о функциях, определенных в классах, мы должны беспокоиться о функциях, определенных вне классов. Концептуально нет большой разницы. Но с точки зрения лексизации и разбора кода, теперь у вас есть дополнительная проблема: "если функция находится вне класса, она принадлежит этому безымянному классу. Однако, если она находится внутри класса, то она принадлежит этому". учебный класс."
Кроме того, если компилятор сталкивается с методом, подобным этому:
public void Foo()
{
Bar();
}
... теперь он должен ответить на вопрос "находится ли Bar внутри этого класса или это глобальный класс?"
Прямые или внешние ссылки? Т.е. нужно несколько проходов? Паскаль имеет "прямую" ссылку для обработки внутрифайловых ссылок на процедуры, но ссылки на процедуры в других файлах с помощью предложений USES и т. Д. Требуют специальной обработки.
Это еще одна вещь, которая вызывает проблемы. Помните, что C# не требует предварительных объявлений. Компилятор сделает один проход, чтобы определить, какие классы названы и какие функции эти классы содержат. Теперь вам нужно беспокоиться о поиске классов и функций, где функции могут находиться как внутри, так и за пределами класса. Об этом не нужно беспокоиться парсеру C++, так как он разбирает все по порядку.
Не поймите меня неправильно, вероятно, это можно сделать в C#, и я бы, вероятно, использовал такую функцию. Но стоит ли вообще преодолевать эти препятствия, когда вы можете просто ввести имя класса перед статическим методом?
Я думаю, что вам действительно нужно уточнить, что вы хотели бы создать, не являющиеся членами статические методы для достижения.
Например, некоторые вещи, для которых вы можете их использовать, могут быть обработаны с помощью расширенных методов.
Другое типичное использование (класса, который содержит только статические методы) в библиотеке. В этом случае мало вреда при создании класса в сборке, которая полностью состоит из статических методов. Он держит их вместе, избегает именных коллизий. В конце концов, в Math есть статические методы, которые служат той же цели.
Кроме того, вам не обязательно сравнивать объектную модель C++ с C#. C++ в значительной степени (но не идеально) совместим с C, у которого вообще не было системы классов - поэтому C++ должен был поддерживать эту программную идиому из наследия C, а не для какого-либо конкретного императива дизайна.
Csharp не имеет функции, не являющейся членом, потому что он скопировал или вдохновился философией java, согласно которой только ООП являются решением всех проблем, и он позволяет решать проблемы только с использованием объектно-ориентированного подхода.
Функции, не являющиеся членами, - очень важная функция, если мы действительно хотим заниматься универсальным программированием. Они более пригодны для повторного использования по сравнению с помещением их в класс.
CSharp должен придумать ExtensionMethods из-за отсутствия функций, не являющихся членами.
Поскольку сейчас языки программирования движутся к парадигме функционального программирования, это, кажется, лучший способ подойти и решить проблему, и это будущее. CSharp следует переосмыслить это.
Хотя это правда, вам нужен класс (например, статический класс с именем FreeFunctions
) чтобы держать такие функции, вы можете разместить using static FreeFunctions;
в верхней части любого файла, который нуждается в функциях из него, без необходимости засорять ваш код FreeFunctions.
классификаторы. Я не уверен, есть ли на самом деле случай, когда это явно уступает тому, чтобы не требовать, чтобы определения функций содержались в классе.
Бесплатные функции очень полезны, если вы комбинируете их с уткой. Весь C++ STL основан на этом. Поэтому я уверен, что C# представит бесплатные функции, когда им удастся добавить настоящие обобщения.
Как и в экономике, языковой дизайн также связан с психологией. Если вы создаете аппетит для истинных обобщений с помощью бесплатных функций в C#, а не доставляете, то вы убили бы C#. Тогда все разработчики C# перешли бы на C++, и никто не хочет, чтобы это произошло, не сообщество C# и, конечно, не те, кто инвестировал в C++.
Если подумать, контраргументом будет "почему 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;
}
Теперь, если мне нужна эта функциональность для какой-то цели в моем классе, я могу просто добавить ее в пространство имен класса и использовать ее, не "загрязняя" мой класс этой функцией.
В 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++. Что не так много в любом случае.