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

Общий взгляд Языки C# и Java появились в разное время. Язык Java был создан задолго до появления C#. Под названием Oak Java был разработан компанией Sun Microsystems в 1990 г., а в 1995 была выпущена первая бета-версия Java. Создание C# было анонсировано в 2000 году, а в 2002 году вышла первая версия платформы .NET, поддерживающей C#. Таким образом, если Java создавался, опираясь в большей степени на опыт языков Objective C и C, то для C# такой опорой являлись C++ и сам Java. И, несмотря на своё название, C# оказался ближе к Java, чем к C++.

С точки зрения разработчика языки Java и C# очень похожи. Оба языка являются строго типизированными, объектно-ориентированными. Оба вобрали в себя многое из синтаксиса C++, но в отличие от C++, проще в освоении для начинающих. Оба позаимствовали из C набор основных ключевых слов и служебных символов, в том числе фигурные скобки для выделения блоков. Оба языка реализуют управление памятью с помощью сборки мусора. Оба языка сопровождаются богатыми коллекциями библиотек. Но есть в языках также свои особенности и различия, сильные и слабые стороны. C# учёл многие недостатки Java и исправил их в своей реализации. Но и Java не стоит на месте, развиваясь параллельно с C#.

Кик Рэдек из Microsoft считает С# более сложным языком, чем Java. По его мнению, «язык Java был построен таким образом, чтобы уберечь разработчика от стрельбы себе в ногу» (), а «С# был построен так, чтобы дать разработчику пистолет, но оставить его на предохранителе» ().

Язык

Синтаксис обозначения начала/конца блока кода фигурными скобками; обозначения, ассоциативность и приоритет большинства встроенных операций (присвоение, арифметические, логические, побитовые операции, операции инкремента/декремента, тернарная условная операция «?:»); синтаксис описания и использования переменных и функций (порядок «тип имя», использование модификаторов, обязательность скобок для функций, описание формальных параметров); синтаксис всех основных конструкций: условного оператора, циклов, оператора множественного выбора;

Синтаксических различий также достаточно.

Синтаксис Java C# Импорт статических имён (import static) позволяет отдельно импортировать некоторые или все статические методы и переменные класса и использовать их имена без квалификации в импортирующем модуле Начиная с C# 6.0 это было введено (например (using static System.Math)). Оператор switch Аргумент оператора switch должен относиться либо к целочисленному, либо к перечислимому типу. Начиная с версии Java 7 в операторе switch стало возможно использовать строковые литералы и это различие с C# было устранено. Поддерживаются как константные типы, так и строковые. В C# 7 появилась поддержка ссылочных типов и null. Также возможно задавать дополнительные условия для блока case с помощью ключевого слова when. В отличие от Java, прямого перехода к следующему блоку case нет. Для перехода к следующему блоку case, нужно использовать оператор goto. Оператор перехода goto от использования goto сознательно отказались, однако существует механизм, позволяющий выйти на внешний цикл из вложенного, пометив его меткой и используя операторы break, continue вместе с меткой (continue <метка>;) goto сохранился, его обычное использование — передача управления на разные метки case в операторе switch и выход из вложенного цикла Константы констант как таковых нет, вместо них используются статические переменные класса с модификатором final — эффект от их использования точно такой же отдельное понятие именованной типизированной константы и ключевое слово const Точность вычислений с плавающей точкой Java содержит конструкцию strictfp, гарантирующую одинаковые результаты операций с плавающей точкой на всех платформах. C# полагается на реализацию, гарантии строго одинаковых результатов вычислений нет. Отключение проверок В Java все динамические проверки включаются/выключаются только на уровне пакета C# содержит конструкции checked и unchecked, позволяющие локально включать и выключать динамическую проверку арифметического переполнения.

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

И в Java, и в C# есть сильные и слабые ссылки на объекты. Оба языка поддерживают методы-финализаторы. Из-за неопределённости момента удаления объекта финализаторы не могут использоваться для освобождения системных ресурсов, занятых объектом, что вынуждает создавать дополнительные методы для «очистки» объекта и вызывать их явно.

C# содержит в стандартной библиотеке интерфейс IDisposable и специальную конструкцию using, гарантирующую своевременный вызов метода очистки:

// DisposableClass реализует интерфейс IDisposable и описывает его метод Dispose class DisposableClass : IDisposable { public void Dispose()

// ... Здесь освобождаются занятые экземпляром ресурсы

using (DisposableClass obj = new DisposableClass(...)) { // ... Код, использующий объект obj } // ... Здесь для объекта obj гарантированно уже вызван метод Dispose

В Java подобной конструкции нет и очистка объектов может быть выполнена только вручную:

class AnyClass { void clear() { // ... Здесь находится код очистки

AnyClass obj = new AnyClass(...); try { // ... код, использующий объект obj } finally { obj.clear(); // - явный вызов метода очистки объекта по завершении его использования }

В Java 7 добавлена конструкция «try-with-resources», обеспечивающая автоматическую очистку полностью аналогично C#:

try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); }

При выходе из блока try все объекты, которым присвоено значение в его заголовке (круглых скобках перед блоком операторов), будут очищены. Обязательное условие — классы этих объектов должны реализовывать системный интерфейс java.lang.AutoCloseable.

Java позволяет зарегистрировать слушателя (listener), который будет получать сообщения, когда ссылка подвергается сборке мусора, что даёт улучшение производительности WeakHashMap.

C# (точнее, среда CLR) позволяет отменить выполнение финализатора для данного объекта методом GC.SuppressFinalize(obj) (напр., соединение SQL на файловом потоке). Это бывает полезным, поскольку финализация считается относительно дорогой операцией при сборке мусора, и объект с финализатором «живёт» дольше.

Объектные средства Оба языка — объектно-ориентированные, с синтаксисом, унаследованным от C++, но значительно переработанным. Код и данные могут описываться только внутри классов.

Инкапсуляция В Java модификатор protected в описании, помимо доступа из классов-потомков, разрешает доступ из всех классов, входящих в тот же пакет, что и класс-владелец.

В C# для объектов, которые должны быть видны в пределах сборки (примерный аналог пакета Java) введён отдельный модификатор internal (аналог default в Java), а protected сохраняет свой изначальный смысл, взятый из C++ — доступ только из классов-потомков. Допускается комбинировать internal и protected — тогда получится область доступа, соответствующая protected в Java.

Внутренние классы Оба языка позволяют определить класс внутри класса.

В Java внутренние классы используются для эмуляции замыканий. Внутренние классы Java имеют доступ к нестатическим членам родительского класса, то есть «знают о this»; кроме того, внутри методов можно определять локальные классы, имеющие доступ по чтению к локальным переменным, и безымянные (анонимные) локальные классы, которые фактически позволяют создавать экземпляры объектов и интерфейсов, перекрывающие методы своего класса, непосредственно в месте их использования. На этом механизме в Java-программах может строиться обработка событий (событие генерирует вызов метода, в исходном классе-обработчике являющегося абстрактным; там, где нужен конкретный обработчик события, программист создаёт экземпляр локального анонимного класса — наследника базового класса-обработчика и непосредственно использует его). Таким образом, исчезает необходимость в специальном типе и синтаксической поддержке для событий, но сам код, создающий обработчики, несколько более сложен для понимания. В частности, сложнее становятся области видимости переменных.

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

В Java начиная с 8 версии также появились лямбда-выражения.

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

В Java 8 появился оператор default, позволяющий определить реализацию методов интерфейса «по умолчанию». Таким образом, реализующий интерфейс класс избавляется от обязанности реализовывать default-методы, но может перекрыть их.

Аналогичная возможность появилась в C# 9.0 — в рамках описания интерфейса разработчик может указать тела для любых методов. Точно так же, как и в Java, при реализации интерфейса разработчик может перекрыть default реализацию, но не обязан этого делать.

В Java примитивные типы (byte, int, double, float, boolean и пр.) передаются по значению, а для остальных (объектные) по значению передаётся ссылка на объект.

В C# в дополнение к примитивным типам передаются по значению структуры (struct) (т. н. значимые типы), остальные типы передаются по ссылке (т. н. ссылочные типы). C# также поддерживает явное описание передачи параметров по ссылке (ключевые слова ref, in и out). При использовании out компилятор контролирует наличие в методе присваивания значения. Также поддерживается возврат значений из методов по ссылке при помощи конструкции ref typename.

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

В C# поддерживаются несколько специальных подвидов синтаксиса при описании тел методов:

— блоки итераторов: методы, возвращающие IEnumerable<T> или IEnumerator<T>, могут императивно описывать последовательность возвращаемых значений при помощи конструкций yield return и yield break.

— асинхронные методы: методы, возвращающие Task/ValueTask/Task<T>/ValueTask<T> и помеченные ключевым словом async, могут в своих телах использовать конструкцию await при вызове других методов, возвращающих Task/ValueTask/Task<T>/ValueTask<T>. Это позволяет реализовывать кооперативную многозадачность, так как конструкция await прерывает выполнение текущего метода до готовности запрошенного значения и передаёт управление шедулеру, который может приступить к выполнению следующей готовой задачи без передачи управления ядру ОС.