Andriy Shyrokoryadov

.Net developer, data scientist

.Net Framework, .Net Core, .Net Standard - вопрос №24 на собеседование C# / .NET

Текст к видео ".Net Framework, .Net Core, .Net Standard" на канале YouTube

Для лучшего понимания данной тематики я попытаюсь объяснить данный материал, используя как можно меньше технических терминов с одной стороны, а с другой стороны – я постараюсь привести как можно больше примеров из реальной жизни.

Предположим, у нас есть некоторое приложение – пусть это будет обычное консольное приложение, которое выполняет простейшие математические операции над числами: сложение и вычитание, умножение и деление. В рамках данного примера, по сути, не важно, что делает наше приложение, важно, что у нас оно есть. Давайте подумаем, где мы можем использовать это приложение. Первое что приходит на ум – это наш персональный компьютер. Я ввел поисковый запрос ‘% доли рынка операционных систем’ и получил результат 87% для систем Windows. Скорей всего ваш компьютер использует операционную систему Windows и приложение, которое у нас есть, будет работать в данной операционной системе. Однако, что с остальными операционными системами?

Если вы не предпринимали каких-либо конкретных действий, чтобы ваше приложение работало в других операционных системах, таких как Linux или MacOs, то работать в этих системах оно скорей всего не будет. Однако язык C# может использоваться для написания приложений не только для Windows. Здесь мы можем сделать следующий вывод – существует нечто, что позволяет нам написать приложение, которое будет работать и в операционной системе Linux, и в MacOs, и, конечно, в Windows. Я расширю этот вывод и скажу, что это «нечто» позволяет также писать приложения в языке C#, которые будут работать со стандартными 32-х и 64-х разрядными процессорами, а также с процессорами типа ARM. Например процессор типа ARM использует в компьютерах Raspberry Pi. Если наше приложение может работать на нескольких операционных системах, то обычно говорят, что такое приложение называется кроссплатформенным. Запомним это понятие.

Возможно, вы уже слышали такое понятие как «микросервисы» или «микросервисная архитектура». Это обширная тема, ради которой можно было бы даже снять видео, но на данный момент давайте просто договоримся, что «микросервис» — это небольшое приложение, которое имеет четкую зону ответственности. Например, у нас может быть сервис управления данными пользователей, или сервис для работы с заказами клиентов. Приложение может состоять из различного количества микросервисов, которые взаимодействуют между собой. Данные сервисы, то есть приложения, могут размещаться на различных серверах с различными операционными система. Было бы неплохо, если бы у нас была возможность перенести сервис с сервера с операционной системой Windows на сервер с операционной системой Linux без дополнительных трудозатрат. Если у нас есть возможность написать кроссплатформенное приложение, и мы знаем, что наша система будет использовать микросервисную архитектуру, то было бы неплохо бы знать, как написать наши микросервисы, чтобы они были кроссплатформенными.

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

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

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

  • возможность создавать кроссплатформенные приложения;
  • приложения – микросервисы;
  • поддержка контейнеризации;
  • масштабируемость и эффективность приложения;

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

Что на данный момент мы знаем и умеем? Мы знаем о существовании среды разработки .Net Framework, которая не совсем нам подходит. О .Net Framework снято отдельное видео на моем канале – ссылка будет в правом верхнем углу. Что нам конкретно не подходит в .Net Framework:

  • используя .Net Framework, мы можем написать приложение только для Windows
  • мы можем написать микросервисы в .Net Framework, но такие сервисы могут быть установлены на серверах только с операционной системой Windows;
  • можно ли использовать приложения написанные в .Net Framework с контейнерами Docker – да, можно, но это будет специальная версия Docker для Windows, то есть сервер должен быть на операционной системе Windows;
  • в .Net Framework, в зависимости от подходов к программированию, также можно писать масштабируемые и эффективные приложения.

В нашей ситуации нам поможет платформа разработки, выпущенная компанией Microsoft в 2016 году, которая носит название .Net Core. На момент выпуска данного видео последней версией платформы .Net Core была версия 3.1. Данная платформа соответствует всем нашим требованиям:

  • кроссплатформенность
  • поддержка микросервисов
  • поддержка контейнеризации
  • масштабируемость и эффективность
  • поддержка командной строки

Возникает обоснованный вопрос – если .Net Core такой замечательный, то почему мы не пишем все наши приложения с использованием этой платформы. Дело в том, что .Net Core, также как и .Net Framework, имеет свои слабые стороны – не всё можно написать, используя .Net Core. Так что же нельзя написать в .Net Core:

  • приложения WindowsForms и WPF;
  • услуги WCF не поддерживаются в .Net Core;
  • не все библиотеки для .Net Framework имеют свои аналоги для .Net Core;
  • библиотеки для .Net Core могут отличаться от библиотек .Net Framework, например Entity Framework Core это не тоже самое что Entity Framework;
  • если у вас есть необходимость работы с определенными функциями операционной системы Windows, то .Net Core – это не самый лучший выбор, так как .Net Core – это платформа, которая не зависит от конкретных операционных систем.

Мне кажется, на данным этапе уже понятно, какая разница между .Net Core и .Net Framework, в каких ситуациях необходимо использовать ту или иную платформу. Однако, у нас осталось еще одно понятие: .Net Standard – что это и как это используется.

Объяснение, как всегда, начнем из далека. Предположим, у нас есть некая библиотека в форме файла DLL. У нас есть 3 версии этой библиотеки, для простоты назовём их версия 1, версия 2 и версия 3. В каждой из этих версий есть класс Calculator с методом Add. Разница между версиями заключается в том, что для версии 1 метод Add принимает 1 аргумент, для версия 2 – 2 аргумента и для версии 3 – 3 аргумента. Это будет выглядеть следующим образом:

    class Calculator1 //version 1
    {
        public int Add(int x) => x;        
    }

    class Calculator2 //version 2
    {
        public int Add(int x) => x;
        public int Add(int x, int y) => x + y;
    }

    class Calculator3 //version 3
    {
        public int Add(int x) => x;
        public int Add(int x, int y) => x + y;
        public int Add(int x, int y, int z) => x + y + z;
    }

У нас есть приложение (назовем его приложение А), которое использует нашу библиотеку в версии 3, то есть использует метод Add с 3 аргументами. Если мы попробуем добавить ссылку на библиотеки в версии 2 или 1, то мы получим ошибку компилятора, которая будет звучать так: «Метод Add не имеет перегрузки, которая принимает 3 аргумента». То есть, чтобы наш код работал нам нужна как минимум версия 3 нашей библиотеки.

Также наше приложение А работает с другим приложением (назовём его приложение Б), которое использует библиотеку Calculator, но в версии 2, там используется метод Add с двумя аргументами. Теперь давайте подумаем – если бы мы хотели как-то объединить приложение А и приложение Б, так какую версию библиотеки Calculator необходимо использовать? Мы должны использовать версию 3, потому что она покрывает потребности приложения А в методе Add с 3 аргументами и покрывает потребности приложения Б в методе Add с 2 аргументами. То есть пользователям нашего приложения мы можем сказать: если Вы хотите использовать наше приложение А совместно с приложение Б, то вы должны использовать библиотеку Calculator в версии 3.

Библиотека Calculator предоставляет нам определенный функционал, который определяется версией. Также .Net Standard, который имеет определенные версии предоставляет набор функционала в зависимости от выбранной версии. То есть .Net Standard это определенная спецификация функционала.

В предыдущем примере мы можем назвать приложение А, приложением .Net Framework, а приложение Б, приложением написанным в .Net Core. Если мы хотим делить код между 2 этими предложениями, то данный код должен быть написан в виде библиотеки .Net Standard. Такая библиотека может быть использована как в приложение .Net Framework, так и в приложении .Net Core. Каждая версия .Net Standard определяет с какой версией .Net Framework и с какой версией .Net Core она может использоваться. Например, .Net Standard версии 1.6 может работать с .Net Framework версии 4.6.1 и .Net Core версии 1.0.

Подводя итог всему вышесказанному – если вы планируете написать код, который может использоваться как в приложениях .Net Framework, так и в приложениях .Net Core, то вы должны писать код, как библиотеку .Net Standard подбирая версию таким образом, чтобы она работала с версиями с .Net Framework и .Net Core для которых вы пишете это приложение. Вы, наверное, спросите, а разве такие ситуации бывают на практике. Да, бывают. Например, часть микроуслуг написана как приложения .Net Framework и работают на серверах с операционной системой Windows, а часть, скорей всего более новых услуг, работает как услуги .Net Core на серверах с операционной системой Linux. Для всех этих услуг могут существовать общие библиотеки, в виде библиотеки .Net Standard соответствующей версии.