Верно ли, что C # похож на Java? Проработав последние несколько лет на Java и снова вернувшись к C #, я с разочарованием отказался от множества функций. Это все, что есть в последней версии C # (8), но, насколько мне известно, их нет в последней версии Java (14) на момент написания этой статьи. В произвольном порядке:

  • Кортежи
  • Типы функций с большим количеством аргументов
  • Аргументы по умолчанию
  • Именованные аргументы
  • Нулевые операторы объединения
  • Лучшее кастинг
  • Методы расширения
  • Характеристики
  • Функциональные члены, воплощающие выражение
  • Интерполированные и буквальные строки
  • Фильтры исключений
  • Выходные параметры
  • Инициализаторы объектов

... и две вещи, которые есть в Java, которые, как мне хотелось бы, имел C #:

  • Неизменяемые локальные переменные
  • Вещи можно переопределить по умолчанию

Кортежи

Сколько раз вы видели пользовательский класс Pair ‹L, R› в Java? Библиотека Apache Commons определяет пары и тройки ‹L, M, R›, но если вы не добавите их в противном случае, нужно добавить много дополнительных вещей только для этих типов. В C # есть встроенный класс Tuple, который поддерживает до 7 значений. Самая близкая вещь в Java - это библиотека java-tuples, которая дает разные имена каждой части кортежа, например Pair, Triplet, Quartet… и не имеет функций кортежей C #, таких как деструктуризация, объявление методов как возврат кортежей с именами для каждого элемента и многое другое:

Типы функций с большим количеством аргументов

В Java 8 добавлены классы, которые определяют функции, которые могут передаваться как аргументы или создаваться с использованием синтаксиса лямбда-функции или ссылки на метод. Однако есть только несколько встроенных функций: Функции, которые принимают один или два аргумента и ничего не возвращают (Consumer и BiConsumer), и функции, которые принимают один из двух аргументов. аргументы и что-то вернуть (Function и BiFunction). Если вы хотите передать функцию, которая принимает более двух аргументов, вы можете в конечном итоге определить что-то вроде этого, как я сделал довольно много:

В C # есть встроенные типы для функций, которые принимают до 16 аргументов и ничего не возвращают (Action ‹T1, T2…› и Func ‹T1, T2…›) .

Еще одна вещь, которую я действительно разочаровал в типах функций Java, - это то, что они не могут генерировать проверенные исключения. Таким образом, их нельзя использовать с кодом, который генерирует проверенные исключения, которые довольно часто затрудняют или ограничивают их использование, например, при работе с файлами. В C # нет проверенных исключений, поэтому его типы Func / Action могут выполнять произвольные операции.

Аргументы по умолчанию

Определение функции или конструктора в Java и некоторые аргументы имеют разумные значения по умолчанию? Ваш единственный молоток - определить перегрузки, которые вызывают другие подписи с этими значениями по умолчанию. Это быстро выходит из-под контроля, поскольку количество подписей растет как O (2 ^ n) для n необязательных аргументов:

Другой подход, который я использовал для Java, - это определить только одну подпись, но разрешить специальные значения, такие как null для объектов или -1 для чисел, и проверить их в теле метода, чтобы вместо этого использовать другие значения; это сбивает с толку абонентов. В C # есть аргументы по умолчанию, которые очень легко использовать:

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

Именованные аргументы

Вызов метода с несколькими параметрами одного типа? Лучше будьте осторожны, вы получите правильный порядок в Java:

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

Обратите внимание, что аргументы были переданы в другом порядке. Этот код по-прежнему будет правильным, пока имена не изменятся, и в этом случае ваш код не будет компилироваться. В качестве бонуса это часто добавляет ясности при чтении кода вызова; Я обычно включаю имя аргумента при передаче логического флага.

Операторы объединения с нулевым значением

Разыменование нулевых указателей - частый источник ошибок. Избежание этого в Java требует некоторого многословия, если вы не выберете инструмент статического анализа, такой как Проверка на пустоту. Обычно все делается так:

В C # есть операторы объединения со значением NULL для безопасного обхода объектов, которые могут иметь значение NULL (‘?’), И задания значений по умолчанию, когда разрешено иметь значение NULL (‘??’):

Лучшая трансляция

Проверка типа чего-либо и преобразование в Java довольно многословны. Типичный пример - переопределение равенства:

В C # есть операторы, упрощающие эту задачу. Вот пример использования оператора is:

Изменить: Селиба указал, что Java 14 добавляет JEP 305, который поддерживает в основном то же самое, хотя и в статусе предварительного просмотра (поэтому он может быть непостоянным).

Методы расширения

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

Свойства

Свойства C # - это просто удобное сокращение для геттеров и сеттеров, но как только вы привыкнете к ним, старый стиль покажется многословным. В Java:

В C # с использованием свойств:

В этом примере используется сокращение get / set, которое просто выполняет базовые функции, которые вы ожидаете, но любой / любой из них также может иметь реализацию тела. У них также могут быть разные модификаторы доступа, такие как {get; защищенный комплект; }.

Функциональные члены, воплощающие выражения

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

Интерполированные и буквальные строки

Объединение переменных или выражений в строки в Java плохо читается и обычно раздражает:

C # имеет интерполированные строки, очень похожие на F-строки Python:

В C # также есть буквальные строки, которые удобны, например, когда в строке есть escape-символы. Пример из Java, пытающийся поместить символы «\» в строку, для которой требуется два из них для каждой из-за экранирования:

Тот же пример на C # с использованием буквальной строки:

Фильтры исключений

Предположим, вы вызываете метод, который может генерировать исключение, включающее в себя код состояния, и вы хотите перехватить исключение только для определенного кода состояния. В Java это выглядит примерно так:

В C # есть фильтры исключений для таких случаев:

Выходные параметры

При синтаксическом разборе числа из строки обработка ошибок в Java немного затруднительна:

Сложность здесь в том, что Integer.parseInt возвращает целое число, но выдает исключение, если строка не может быть проанализирована как единое целое. Соответствующие библиотечные методы в C # используют параметры out, такие как TryParseInt:

В C # можно определить TryParseInt, чтобы получить результирующий int в качестве параметра out, который назначается в случае успеха, а также вернуть логическое значение, указывающее на успех, а не генерировать исключение, указывающее на сбой. Этот шаблон «Попробуйте» также используется для поиска чего-либо в Словаре с помощью TryGetValue. Возврат bool и присвоение параметра out обеспечивает краткий способ вызова этих методов «Try» в условном операторе.

Инициализаторы объектов

Предположим, вы создаете объект с изменяемыми свойствами и только конструктор без аргументов. Это довольно часто, например, при использовании некоторых фреймворков, таких как ORM. Вот что вы должны сделать в Java для такого объекта:

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

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

Что есть в Java, что должно быть в C #

Неизменяемые локальные переменные

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

По умолчанию значения можно переопределить

Свойства, методы и классы C # нельзя переопределить, если они не отмечены как виртуальные, что может ограничить вашу способность имитировать их поведение в модульных тестах. Это часто означает, что классы, которые вы не контролируете, не могут быть издевательскими, поскольку виртуальные часто опускаются в тех местах, которые вы хотите имитировать. Я был разочарован тем, что, например, не смог создать имитацию большинства объектов из WinForms и DevExpress. По умолчанию методы и классы Java могут быть переопределены, что по умолчанию делает их макетируемыми в модульных тестах.

Вывод

Это ни в коем случае не полный обзор возможностей C # по сравнению с Java; надеюсь, в комментариях будет еще больше примеров того, что я упустил, таких как типы, допускающие значение NULL, и анонимные типы, IEnumerable и лучшие дженерики. Я, конечно, не эксперт в C #, поэтому извиняюсь за детали, которые я упустил или ошибся.

Увидев возможности C # 8 и C # 9 по сравнению с Java, я задался вопросом: а не стагнирует ли язык Java? Что делает его на данный момент более популярным, чем C #? Представление? Импульс? Знакомство? Недоверие к Microsoft?

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