×

Эволюция .NET-стека: что изменилось за последние несколько лет

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

Все более популярным становится .NET Core, новые проекты стартуют на кросс-платформенном ASP.NET Core, в использование входит C# 8. В языке C# остались все фичи предыдущих версий для обратной совместимости, но это не значит, что они рекомендуемые. Эта статья для людей, уже имеющих опыт в коммерческой разработке на .NET-стеке и желающих проапгрейдить знания в связи с последними релизами технологий от Microsoft. А также для тех, кто годами сидит на старых версиях ASP.NET/C# и хочет быть в курсе, что нового в мире .NET-технологий.

В статье я изложу большинство нововведений, ориентируясь на веб-стек.

Open Source .NET

Много лет .NET оставался закрытой системой, однако последние пять лет Microsoft держит курс на открытие исходных кодов своих фреймворков. Последнее время ощутимый вклад в развитие ASP.NET Core, F#, VS Code внесло именно комьюнити (судя по количеству пулл-реквестов):

Также некоторые крупные корпорации платят своим специалистам за контрибьютинг в экосистему .NET, например Samsung.

.NET Core

Предпосылками к созданию альтернативной реализации .NET фреймворка стало отсутствие гибкой модульности, портабельности и кросс-платформенности. Рост популярности контейнеризации и повсеместное использование Linux не могли обойти Microsoft стороной, так как одним из плюсов Java перед .NET была как раз возможность хостинга под Linux. Оригинальный .NET Framework был неконкурентоспособен на этом рынке. Также .NET Core — часть open-source-наследия с возможностью для комьюнити влиять на разработку. По-моему мнению, это было абсолютно верным шагом к адаптации технологий от Microsoft к современным тенденциям.

Основное архитектурное отличие .NET Core от .NET Framework — это модульность и портабельность.

Под модульностью я понимаю меньшую связанность компонент и дробление на большее количество пакетов, из которых состоят библиотеки классов .NET (Core FX).Теперь все части приложения являются nuget-пакетами, включая CLR и JIT.

Под портабельностью — возможность поставлять код вместе со средой исполнения нужной версии в одном приложении (Core CLR).

В .NET Core был представлен кросс-платформенный CLI (command line interface) для выполнения типичных задач, управления зависимостями, пакетами, проектом. Например, такой командой мы можем завернуть приложение в единый исполняемый файл, который будет содержать и приложение, и среду выполнения:

dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true

Вот хорошая статья про архитектуру .NET Core и способы развертывания.

Предпосылкой для появления .NET Core была платформа DNX, кросс-платформенная реализация .NET Runtime, которая впоследствии стала .NET Core 1. Кстати, dotnet CLI ранее назывался DNX.

Начиная с Visual Studio 2017, уже есть поддержка контейнеризации приложения из коробки.Visual Studio за нас создаст Dockerfile: скачает необходимый образ, запустит приложение. Подробнее можно посмотреть в этом видео. Подробнее о контейнеризации — тут.

Чтобы вручную завернуть приложение в контейнер, необходимо установить Docker, скачать необходимый образ-основу, создать Dockerfile и описать процесс копирования файлов, проброс портов из контейнера и старта приложения. Как это сделать руками, можно почитать в этой статье.

Все приготовления, описанные выше, можно сделать парой кликов из коробки с помощью GUI Visual Studio.

Что такое .NET Standard

В экосистеме .NET существует восемь реализаций .NET-фреймворка под разные платформы:

  • .NET Core;
  • .NET Framework;
  • Mono;
  • Xamarin.iOS;
  • Xamarin.Mac;
  • Xamarin.Android;
  • Universal Windows Platform;
  • Unity.

.NET Standard — унифицированное версионирование набора поддерживаемых API.

Каждая реализация имеет свою собственную историю релизов и развития. Чтобы понимать, какой пакет может быть совместим и с какой версией, было решено стандартизировать наборы предоставляемых API в спецификации .NET Standard. Для лучшего понимания можно посмотреть интерактивную таблицу.

Можно провести хорошую аналогию со спецификацией HTML5, которую каждый браузер реализует по-своему.

Очень подробно о реальных историях работы и решениях проблем совместимости с .NET Standard вы можете почитать тут.

C# 6, 7, 8

За последние четыре года развития C# (C# 6, C# 7.1, 7.2, 7.3, C# 8) изменения больше всего коснулись механики работы довольно стандартных вещей:

  • методов;
  • ветвлений (любые условные операторы);
  • переменных;
  • классов.

Всего я насчитал 54 новые фичи:

Группировка по количеству новых фич в каждом релизе по сфере изменений

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

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

Упрощенный синтаксис кортежей (C# 7)

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

Далее, начиная с .NET 4.0, популярность приобрел обобщенный класс Tuple, позволяющий передавать наборы значений разных типов в одном экземпляре, однако проблема в том, что неудобно работать со свойствами. Для переноса значений в обобщенном классе Tuple элементы кортежа будут называться Item1, Item2 вместо реальных имен свойств.

Проблема была решена в C# 7, что позволило в лаконичной манере передавать наборы значений из методов (кортежи) и объявлять их в качестве переменных, например:

(int, int) GetMinMax(int a, int b)
{
   return (Math.Min(a, b), Math.Max(a, b));
}

      (int min, int max) = GetMinMax(4, 5);

Console.WriteLine(max);
Console.WriteLine(min);

Null-conditional operators (C# 6)

Следующая конструкция позволяет получить имя, если класс person != null. Если же он == null, в переменной first окажется null:

var first = person?.FirstName; 

Очень сильно уменьшает количество кода в случае частых проверок на null.

String interpolation (C# 6)

Синтаксис более удобной шаблонизации строк показан в сочетании со стрелочной (лябмда-синтаксис) функцией (C# 6):

public class UserInfo
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
   public string FullName => $"{FirstName} {LastName}";
}

The nameof expression (C# 6)

Преобразует имя переменной в строку. Решает проблему хранения лишних строковых констант и хардкода строк:

void NameOfDemo(string lastName)
{
   if (string.IsNullOrEmpty(lastName))
      throw new ArgumentException(
 			message: "Cannot be blank",
 			paramName: nameof(lastName));
}

Local functions (C# 7)

Локальные функции решают следующие две проблемы:

  1. В случае необходимости еще раз использовать кусок кода внутри метода, причем нигде больше в приложении он использоваться не будет.
  2. Ранее мы могли объявить такой кусок кода двумя способами: в виде приватного метода на том же уровне или с помощью анонимного метода внутри.

Анонимные методы применяют механику делегатов. Иногда такой подход уместен для размещения кода, переиспользуемого только внутри этого метода.

С появлением делегатов их синтаксис выглядел так:

 public delegate int DelegateInt(int a, int b);

   DelegateInt func = delegate (int a, int b)
   {
        return a + b;
    };

  var result = func(1, 1);

Затем синтаксис делегатов упростили с помощью лямбд и добавили стандартные делегаты Func & Action:

Func<int,int,int> func = (a, b) => a + b;
 
var result = func(1, 1);

Сейчас же Visual Studio 2019 предложит вам отрефакторить это выражение до локальной функции с использованием lambda-body-синтаксиса:

int func(int a, int b) => a + b;

var result = func(1, 1);

Причем механика работы локальных функций немного отличается от механики работы анонимных методов:

  • Локальную функцию можно определить в конце метода, переменную делегата только перед использованием.
  • В случае рекурсивного вызова для делегата придется заранее определить имя переменной и присвоить пустое (default) значение, чтобы иметь возможность вызывать анонимный метод из него же.
  • Локальная функция не является делегатом и не преобразуется в делегат во время использования, следовательно, не будет потреблять ресурсы в управляемой куче. При работе замыкания локальных функций не будет выделена лишняя память в куче, а будут использоваться структуры. Это поможет снизить потребляемые ресурсы. Если вам важен перфоманс, про реверс-инжинеринг того, как работают анонимные методы в сравнении с локальными функциями, можно почитать тут.
  • В локальных функциях можно использовать синтаксис yield return для создания перечислителей (итераторов), в отличие от лямбда-выражений.

В целом локальные функции и делегаты имеют разные сферы применения. Локальные функции более уместны для выделения заново используемого куска кода внутри конкретного метода.

Readonly members (C# 8)

Новый модификатор доступа readonly для методов позволяет ограничивать их возможность изменять значения переменных. В случае наличия такого кода он не дает скомпилировать проект. Это помогает следить за иммутабельностью структур и классов.

public class DistanceDemo
        {
            public double X { get; set; }
            public double Y { get; set; }
            public double Distance { get; set; }
            public readonly override string ToString() =>
             $"({X}, {Y}) is {Distance} from the origin";
        }

Null-coalescing assignment (C# 8)

Позволяет объединить присвоение значения с проверкой на null. В следующем примере переменной numbers будет присвоено значение, только если она содержит null:

List<int> numbers = null;
numbers ??= new List<int>();

Опять-таки убираем условные конструкции и сокращаем количество кода.

Nullable reference types (C# 8)

Это достаточно значительное нововведение в C# 8. С каждой новой версией C# — разработчики пытаются сделать программирование более лаконичным, предсказуемым и безопасным, помогая с помощью языковых средств избегать возможных типичных ошибок. Зачем делать проверки на null по всему приложению, если можно запретить на уровне компилятора устанавливать null? :)

Новый режим работы можно включить и для проекта в целом, и для какого-то отдельного куска кода, используя специальные директивы. В зоне действия новой фичи все Reference-переменные могут по умолчанию запрещать присваивать себе null, и чтобы иметь возможность присвоить null, нужно будет объявить эту возможность явно, например:

string? Name;

Также был добавлен новый оператор предотвращения вывода warning’ов на потенциально небезопасную операцию.

Этот код создаст warning:

(null as Car).Number

Тут warning не будет:

(null as Car)!.Number

Более детальное описание этой довольной объемной фичи можно найти в этой статье. О том, как Entity Framework Core работает с этой фичей, можно прочитать тут.

Patterns, Patterns, Patterns (C# 7-8)

В последних релизах C# реализовали много фич, которые упрощают условные операторы и делают их более лаконичными.

Кроме pattern matching из C# 7, позволяющего включать логику, завязанную на определение типа аргумента в оператор switch/case:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
    int sum = 0;
    foreach (var i in sequence)
    {
        switch (i)
        {
            case 0:
                break;
            case IEnumerable<int> childSequence:
            {
                foreach(var item in childSequence)
                    sum += (item > 0) ? item : 0;
                break;
            }
            case int n when n > 0:
                sum += n;
                break;
            case null:
                throw new NullReferenceException("Null found in sequence");
            default:
                throw new InvalidOperationException("Unrecognized type");
        }
    }
    return sum;
}

В C# 8 также появилось много вариаций нового синтаксиса switch-case. Все они интуитивно понятны, так что не буду писать много комментариев:

1. Было:

public static RGBColor FromRainbowClassic(Rainbow colorBand)
{
    switch (colorBand)
    {
        case Rainbow.Red:
            return new RGBColor(0xFF, 0x00, 0x00);
        case Rainbow.Green:
            return new RGBColor(0x00, 0xFF, 0x00);
        case Rainbow.Blue:
            return new RGBColor(0x00, 0x00, 0xFF);
        default:
            throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
    };
}

Стало:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
        Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

2. Проверка на совпадение отдельных свойств объекта:

public static decimal ComputeTax(Address location, decimal salePrice) =>
    location switch
    {
        { State: "WA" } => salePrice * 0.06M,
        { State: "MN" } => salePrice * 0.75M,
        { State: "MI" } => salePrice * 0.05M,
        _ => 0M
    };

3. Совпадение нескольких скалярных значений:

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        (_, _) => "tie"
    };

4. Позиционный паттерн на примере кода, который определяет позицию точки в евклидовой системе координат, а именно в каком из 4 квадрантов находится точка:

static Quadrant GetQuadrant(Point point) => point switch
{
    (0, 0) => Quadrant.Origin,
    var (x, y) when x > 0 && y > 0 => Quadrant.One,
    var (x, y) when x < 0 && y > 0 => Quadrant.Two,
    var (x, y) when x < 0 && y < 0 => Quadrant.Three,
    var (x, y) when x > 0 && y < 0 => Quadrant.Four,
    var (_, _) => Quadrant.OnBorder,
    _ => Quadrant.Unknown
};

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

Default interface implementation (C# 8)

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

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

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

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

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

Пример такого решения вы можете найти тут, изучив вначале код из папки starter/customer-relationship, а затем реализацию члена в интерфейсе finished/customer-relationship.

В общем, злоупотреблять этой фичей в дизайне своих приложений не стоит: она была предназначена для создателей API, для решения конкретных проблем.

Почитать о C# 8 можно на MSDN. Вот ещё неплохая статья на русском с некоторыми тонкостями использования новых фич.

ASP.NET Core

Если вы работали с ASP.NET MVC/Web API, многие вещи в ASP.NET Core для вас будут в новинку. Фреймворк стал более гибким и более очевидным. Центральное место, которое изменилось, — конфигурирование хостинга и конвейера обработки запросов.

В ASP.NET Core, по аналогии с другими бэкенд-экосистемами, было введено понятие middleware. Его можно определить как:

Функции промежуточной обработки (middleware) — это функции, имеющие доступ к объекту запроса , объекту ответа и к следующей функции промежуточной обработки в цикле «запрос-ответ» приложения. Могут выполнять работу как до перехода на следующую функцию, так и после, в обратном порядке, также решать — нужно ли продоложать цепочку обработки запроса. Подробнее тут.

Концептуальное изображение конвейера обработки запросов

В ASP.NET приложение конфигурируется в основном с помощью Web.Config, и точкой входа в приложение является global.asax.

В ASP.NET Core точка входа в приложение — Program.cs, где конфигурируется, как приложение будет хоститься и с какими параметрами и на каком сервере. В ASP.NET Core хостинг приложения отвязан от самого веб-движка и нет жесткой привязки к IIS, что дает больше гибкости. Также, продолжая концепцию ASP.NET Core с self-contained приложениями, мы можем сделать приложение self-hosted. Также есть опциональный файл Startup.cs, в котором мы пишем код управления жизненным циклом запроса, подключением middleware и регистрацией сервисов во встроенном IoC контейнере. Он опциональный, потому что все это мы можем реализовать и в Program.cs. Еще есть AppSettings.json, который содержит наши параметры приложения в зависимости от выбранной билд-конфигурации.

Конфигурирование проекта стало гораздо более прозрачным. Теперь нужно не искать узлы в огромном XML, а, скорее, писать личный конвейер обработки запросов на более интуитивно-понятном API на C# через конфигурирование кодом.

Теперь MVC и Web API висят на одной шине обработки запроса. Также разработчики позиционируют ASP.NET Core как высокопроизводительный серверный фреймворк, более быструю альтернативу старому ASP.NET.

Кстати, в ASP.NET Core для обработки запросов теперь используется ThreadPool, и нет SynchronizationContext по умолчанию вместо потоков IIS. Такое изменение может привести к серьезным проблемам — дедлокам на продакшене при большой нагрузке, в случае миграции решения с ASP.NET на ASP.NET Core, если у вас имеются специфические варианты реализации работы с тасками и асинхронностью. Подробнее можно почитать тут.

Entity Framework Core

Полностью переписанный Entity Framework Core — альтернатива Entity Framework 6.x, так как имеет почти такое же API, однако есть некоторые возможности, недоступные в EF Core, и наоборот. Полное сравнение можно почитать тут.

Самые значимые отличия в EF и EF Core — отсутствие в EF Core подходов Model First & Database First, использующих файл маппинга концептуальной и реальной модели — EDMX. Опыт показал, что излишние визуальная и концептуальная составляющие делают большие приложения слишком неудобными и медленными для разработки. Нововведением в EF Core стала поддержка нереляционных хранилищ. Посмотреть пример использования можно тут.

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

Я думаю, что, начиная с версии 2.1, в которой наконец-то добавили серверную группировку и еще кучу разных фич, EF Core станет вполне production-ready фреймворком.

Асинхронный паттерн разработки

Сейчас асинхронный паттерн построения приложений становится де-факто стандартом. Асинхронность != многопоточность. Асинхронность — это о том, как эти самые потоки использовать более выгодно, повышая загруженность каждого, тем самым делая наше приложение более экономным, а следовательно, более производительным. Сам паттерн асинхронной работы менялся с выходами новых версий .NET, и уже в .NET 4 принял современный вид.

Начиная с .NET 4, рекомендованный подход — TAP (Task-based asynchronous pattern). В .NET 4.5 / C# 5 он был расширен оператором async/await, позволяющим писать асинхронный код в синхронной манере.

История развития паттернов асинхронного написания кода в C#:

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

Blazor

Клиентский C# в браузере стал реальностью. Не так давно Blazor был включен в релиз ASP.NET Core 3, и это своего рода революция в разработке UI-части приложений.

Предпосылки:

  • Asm.js появился в 2013-м как способ написания высокопроизводительного кода для веб-браузеров. Вскоре поддержка asm.js была включена в Chrome & Mozilla.
  • В конце 2017-го идея asm.js была развита в релиз Web Assembly (Wasm) года в браузере Chrome. В wasm компилируется код на языках более высокого уровня: C/C++/Rust.
  • Вскоре Microsoft попробовала собрать Mono под платформу wasm. Как результат, получаем реализацию .NET-фреймворка в браузере. Исполняя код, написанный на чистом C#, который работает с IL-инструкциями Mono под wasm-интерфейсом.

Это позволило писать код на C# для виртуальной машины (CLR) в браузере, которая работает под wasm. Фреймворк реализован в паттерне MVVM и немного напоминает компоненты Vue.js, содержащие и разметку и код модели. Также есть возможность вызывать чистый нативный JS.

Так это работает на концептуальном уровне

Технология постепенно развивается. Недавно ко мне пришла вакансия, в описании которой одним из необходимых скиллов был Blazor. Люди делают что-то вроде CAD-системы.

Из интересных пакетов под Blazor можно отметить:

Также рекомендую хорошую статью с «Хабра» про Blazor.

Туллинг

Все чаще люди переходят на средства разработки от JetBrains. Сам считаю их довольно качественным софтом с хорошим UX:

  • Rider — альтернативная IDE для .NET-стека, имеет родной ReSharper как элемент среды. В Visual Studio при установке ReSharper получаем тормоза за счет того, что, кроме анализа кода в ReShaerper, сама Visual занимается анализом кода, и сам плагин работает через интерфейсы, выставляемые VS, что дает излишнюю работу и двойной расход ресурсов. Еще важный момент. ReSharper работает in process, сама же Visual Studio реализована как приложение x86 (32 bit), что накладывает ограничения на размер потребляемой оперативной памяти на поток, где находится студия.
  • DataGrip — альтернатива MS SQL Management Studio + HuntingDog, имеет встроенный быстрый поиск по объектам баз данных, включая полнотекстовый поиск по хранимым процедурам/функциям, кучу удобств для работы с таблицами, удобный редактор кода с автокомплитом. Отлично подходит под цели Database Developers, однако для администрирования серверов придется использовать либо текстовые команды, либо GUI от нативного MSSMS. DataGrip содержит поддержку кучи драйверов для всех популярных баз данных.
  • Дополнительные средства от JetBrains:
    • dotPeek — альтернатива ILSpy & Reflector;
    • dotMemory — средство для анализа потребляемой памяти, частично повторяет функциональность Performance Monitor и WinDbg, только в более user-friendly манере;
    • dotTrace — перфоманс профайлер;
    • dotCover — средство запуска unit-тестов, поиск проблем с покрытием кода.

Но также довольно неплохо развилась Visual Studio. Последняя версия на сегодняшний день — 2019, имеет более минималистичный интерфейс, были улучшены средства статического анализа кода, рефакторинга, навигации по коду.

Выводы

За последние 4-5 лет Microsoft сделала огромную работу по адаптации своих технологий к общим трендам и подходам, давно популярным в остальных технологических стеках. Сейчас все новые проекты, что я знаю, пишутся на стеке ASP.NET Core + EF Core, причем базы данных MS SQL Server и Windows не всегда нужны. По личным ощущениям вполне возможно, что .NET экосистема скоро перестанет ассоциироваться с MS SQL Server. Если это решение на микросервисах, то чаще применяют PostgreSQL или MySQL из-за дешевизны и ненужности заточки под сложную работу с данными или репортинг.

По всем вопросам, пожеланиям, предложениям пишите мне на Facebook.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному8
LinkedIn

Схожі статті




75 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Thx for this article, but IMHO better to use common English terms and if possible, write the whole article in English. Because sometimes it’s a bit hard to understand e. g. „Перечислитель” => Iterator, etc

Досі вважаю оголошення змінних через var поганим стилем.

Старина Рихтер вообще советует CLR-тип объявлять, типа «String», вместо ключевого слова... так что, решать Вам )

Ещё добавляя что это нужно в том случае когда над проектом работают программисты, которые пишут не на одном языке

А какая разица ? string же это ключевое слово, которое компилятор все равно в этот тип развернет ?

Для шарпа со стрингом разницы нет, разницу лучше всего видно на инте, где у шарпа это всегда инт32, а в некоторых языках это инт16

Я думаю, это пройдет. У меня прошло когда я почитал naming conventions ASP.NET Core. Просто дело привычки. Если уметь в именование переменных, то становится понятно что она содержит. R#/Rider, например, предлагает несколько вариантов имени на основе типа. Например:

var response = await client.PostAsJsonAsync("/credit", command);
Какая разница какого типа response?

Можно сказать, что ревьюеру непонятно скомпилится ли код

var account = await response.Content.ReadAsAsync<Account>();
Но тогда возникает вопрос, нужно ли ревьюить код, который до этого не был скомпилен, прогнан через статический анализатор/code conventions и протестирован автотестами? А если все это было выполнено, то можно смело делать вывод, что у response есть свойство Content, у которого есть метод ReadAsAsync.

Согласен, по-моему с автовыводом типов + автовыводом полей анонимных классов эти все проболемы ушли, в var умеет IntelliSense.

Реально, не представляю когда может быть вреден var. Если в блокноте пишешь C#, что мало вероятно.

То, с чем сталкивался, — это передача таски в метод, который принимает object с последующей сериализацией в JSON, например. Т.е. когда забыл await. Но это очень редкие случаи и в большинстве случаев ловятся простыми тестами.

Анонимными классами он наверное тоже не пользуется, а то без var и невозможно. Под каждый Select Linq лепит отдельный тип.

var неудобен при просмотре кода глазами, из-за него непонятен тип объекта, приходится наводить мышкой и смотреть в тултипе.

А для чего тебе знать тип объекта когда смотришь на код?

Популярный ответ на это: «чтобы знать какие методы и свойства есть у объекта», есть люди, которые зачем-то выучивают содержимое существующих классов, и они оч страдают когда классов становится пару сотен :)

Сам таким был. Дело в том, что это не нужно при чтении кода, достаточно знать что хранит переменная, о чем должно говорить ее имя. А при работе с кодом есть IntelliSense или аналоги.

Знач ты особенный, обычно такие люди не меняются и ничего кроме простых тасок делать не могут, много таких знаю

Когда работашь несколько лет на одном проекте, то становится просто понять что умеет класс по его типу, поэтому и кажется, что явное указание типа упрощает понимание. После смены нескольких проектов, особенно с большими code-base, это проходит.

Я тут, только что увидел жену Скотта Ханселмана. Сначала я подумал это какой-то photoshop прикол на David Fowler, а потом осознал, что он не шутит.

pbs.twimg.com/...​Taj?format=png&name=small

В локальных функциях можно использовать синтаксис yield return для создания перечислений, в отличие от лямбда-выражений.

Перечислителей, скорее?

Дякую за статтю, класно розписано. Дійсно MS плідно працює над .NET останні кілька років, і це радує

Жаль, что мало кто вспомнит про десктоп и UWP. Microsoft не сильно работает над этим направлением, даже в статье ничего об этом не написано.

Дали возможность собирать HelloWorld.exe весом в 85MB.

Я тут писал про родной мне веб-стек. Да, в десктоп-девелопменте тоже есть прогресс, но увы, я им не занимаюсь.

Microsoft не сильно работает над этим направлением

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

Плюс, к примеру, перенос System.Drawing в пакет открыл предыдущему продукту, над которым я работал, дверь хостинга в никсовом контейнере, а не только в виндовом.

.NET Core — няша, дождался 2.1 в своё время и кайфую (до него был сырой). А вот добавление функциональщины в Шарп — зло. Собственно, ремарки «это для специфического кейса» или «не стоит злоупотреблять» понятно, чем закончатся в итоге — больше говнокода. Вот эти конкретно поползновения Шарп, боюсь, до добра не доведут.

А вот добавление функциональщины в Шарп — зло

Наоборот, код получается лаконичнее. Многие так же боялись лямбд в C# 3.5 (2007-й год) — «функциональщина». В C# 8 хотели добавить typeclasses, но, видимо, перенесли в C# 9.

Как пел один известный исполнитель:
И самое страшное, что может случиться: стану пида F#-программистом.

Наоборот, код получается лаконичнее.

В идеальном случае, фактически у меньшей части девелоперов. Язык должен подталкивать писать красивый код, а не предоставлять чрезмерное количество инструментов там, где этого не требуется. Выхлоп несоизмерим с негативным влиянием на неокрепшие умы, коих всегда больше. Вот у нас уже есть кортежи, и можно не думать, а зачем мне новый класс — фактически уже говнокод, просто массово одобренный а-ля ура, для 2-3 значений больше не нужно писать новую структуру (лень заморачиваться с ООП в конкретном кейсе, ну супер же просто покращення бгг).

Многие так же боялись лямбд в C# 3.5 (2007-й год) — «функциональщина».

Без понятия, что думали про лямбды в 3.5 и 2007й год, я тогда ещё Шарп не знал. Тем более, что расписываюсь лично за себя — большинство как раз радуется новой фиче на каждый чих.

В C# 8 хотели добавить typeclasses, но, видимо, перенесли в C# 9.

В этом ничего плохого не вижу, только хорошее — оно неопасно в том аспекте, который меня беспокоит.

Именно, зачем создавать новый класс, если нужно вернуть два параметра вместо одного? Особенно, если это не является частью публичного контракта. А говнокодить можно и на Java. Хотя она тоже уже начала немного развиваться.

Именно, зачем создавать новый класс,

Затем, что ООП, не?

если нужно вернуть два параметра вместо одного?

А с каких пор в кортежах можно вернуть всего два параметра?) И кто определяет достаточное количество, чтобы нужно или не нужно было возвращать новую структуру? Вы же сами здесь и расписываетесь, что ради возвращения 2х параметров ввели аж целую фичу языка, которая ломает ООП — супер решение.

А говнокодить можно и на Java. Хотя она тоже уже начала немного развиваться.

В том-то и дело, что чем сбалансированнее набор фич, тем сложнее говнокодить. Поэтому про Джаву просто мимо, хотя я её и не люблю). Это уже две крайности, когда или не было ничего, что принято в приличном обществе бгг, или по фиче языка на каждый чих, куда сейчас полез Шарп.

Затем, что ООП, не?

Хмм, ООП — это не про tuple vs. class для возвращаемого значения. ООП — это про передачу сообщений между независимыми процессами (объектами).

А с каких пор в кортежах можно вернуть всего два параметра?) И кто определяет достаточное количество, чтобы нужно или не нужно было возвращать новую структуру? Вы же сами здесь и расписываетесь, что ради возвращения 2х параметров ввели аж целую фичу языка, которая ломает ООП — супер решение.

Мне очень любопытно какая логическая цепочка привела к таким занимательным заключениям. Уверен, она сломана.

И кто определяет достаточное количество, чтобы нужно или не нужно было возвращать новую структуру?

Здравый смысл. Можно пользоваться тем же эмпиричиским првилом, что и в DI, если больше 3-4-х параметров и они часто идут вместе, стоит создать новую структуру. Вообще мне больше двух не приходилось возвращать.

Хмм, ООП — это не про tuple vs. class для возвращаемого значения. ООП — это про передачу сообщений между независимыми процессами (объектами).

Что Вы этим хотели сказать конкретно? God class’ы — тоже формально «ООП».

Мне очень любопытно какая логическая цепочка привела к таким занимательным заключаениям. Уверен, она сломана.

Какой один из самых крутых бенефитов ООП? Наличие иерархии и взаимосвязей целостных структур (классов) подталкивают разработчика писать хороший код (следовать SOLID и т.д.), думая системно, потому что эти структуры в голове постоянно пересматриваются при любом изменении. На примере кортежа мы позволяем разработчику просто забыть об этом, выбросив из головы взаимосвязи, и просто пробросить пачку объектов, отдав связи на откуп «гению». Вместо того, чтобы задать вопрос, а не наговнокодил ли кто-то, что дошёл до пробрасывания этой пачки вместо целостной структуры. В этом вся и соль, что когда структуру нельзя приткнуть, хотя вроде нужно, то это 90% вероятность нужды рефакторинга.

Ну и повторю вопрос, на который нет ответа

А с каких пор в кортежах можно вернуть всего два параметра?) И кто определяет достаточное количество, чтобы нужно или не нужно было возвращать новую структуру? Вы же сами здесь и расписываетесь, что ради возвращения 2х параметров ввели аж целую фичу языка, которая ломает ООП — супер решение.
А с каких пор в кортежах можно вернуть всего два параметра?

На сколько помню сходу, там до восьми параметров, что заставляет думать, что их там всего два?

И кто определяет достаточное количество, чтобы нужно или не нужно было возвращать новую структуру?

Там, если почитать, ответ выше, продублирую на всякий случай:

Здравый смысл. Можно пользоваться тем же эмпиричиским првилом, что и в DI, если больше 3-4-х параметров и они часто идут вместе, стоит создать новую структуру. Вообще мне больше двух не приходилось возвращать.
Вы же сами здесь и расписываетесь, что ради возвращения 2х параметров ввели аж целую фичу языка

Цитату или GTFO.

которая ломает ООП — супер решение.

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

Наличие иерархии и взаимосвязей целостных структур (классов) подталкивают разработчика писать хороший код (следовать SOLID и т.д.)

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

На сколько помню сходу, там до восьми параметров, что заставляет думать, что их там всего два?

Точно что)

Цытату или GTFO.

Где я пишу, что думаю, что их 2-то? Речь была о другом — для какого кейса фичу ввели.

Цытату или GTFO.
Именно, зачем создавать новый класс, если нужно вернуть два параметра вместо одного?

Ну вот Вам цитата. Где Вы фактически доказываете, что она нужна для двух, и большинство приводит пример с тем же количеством.

Там, если почитать, ответ выше, продублирую на всякий случай:

Ага, и автор тоже пишет, что больше двух не приходилось использовать. Поэтому в том-то и дело, что это плохой ответ на

На примере кортежа мы позволяем разработчику просто забыть об этом, выбросив из головы взаимосвязи, и просто пробросить пачку объектов, отдав связи на откуп «гению»

Т.к. чем больше подталкивания разработчика в правильное русло и меньше надежды на его «здравый смысл» ©, тем лучше. И чем больше будет таких «ответов», тем больше будет говнокода.

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

По причинам выше от этого больше вреда, чем пользы, и таким же образом можно не «загонять себя в рамки» © SOLID, а говнокодить — это ж «ООП» всё равно, значит всё типа ОК. Вообще, попытки два раза подряд спрятаться в определениях я вижу очень показательными, уж извините...

Я даже не нахожусь что ответить. Наверное, ты отстоял честное имя ООП. И еще раз для себя подтверидил, что ValueTuple, Deconstruction и фунциональщина не нужны.

Deconstruction

Сама по себе прикольная фича, кстати.

Впрочем, ValueTuple и Deconstruction, на самом деле ввели, как удобную альтернативу out параметрам в асинхнонных методах, так что да, можно считать, что фичу языка слелали ради возвращения двух параметров. Но где я об этом выше писал и в этом расписывался?

Впрочем, ValueTuple и Deconstruction, на самом деле ввели, как удобную альтернативу out параметрам в асинхнонных методах, так что да, можно считать, что фичу языка слелали ради возвращения двух параметров.

А если учесть, что out параметры хорошо ложаться по дефолту только на методы типа bool TryXXX(YYY x, out ZZZ result), а остальные кейсы в большинстве случаев тоже будут говнокодом, то...)

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

Какое-то догматическое и ограниченное мышление выходит.

Моя точка зрения состоит в том, что все люди несовершенны, и инструменты должны подталкивать писать хорошо. Для этого есть статическая сильная типизация, например — ну, никто ж не будет спорить, что она помогает писать код лучше? Хотя вот есть dynamic в Шарпе, который действительно нужен для специфических кейсов. Почему он оправдан? По той причине, что даже новички практически не лепят его не туда, т.е. именно эту фичу сложно заюзать неправильно, ну и на вопрос дизайна/иерархии/структуры он напрямую не так влияет, как те же кортежи. Хотя, по правде говоря, я видел случай, когда человек с годами опыта засунул dynamic проперти в модель в неймспейсе Contract, поэтому к чему это я: можно и дальше тешить себя иллюзиями, что раз мы молодцы, то всё будет хорошо, но я далеко не столь оптимистичен, поэтому против попустительства таким вещам на уровне языка.

Главное чтобы разработчики не разбежались.

Так это ж не значит, что это надо запрещать. Оно ушло в паблик, поэтому пути назад нет. Просто я считаю это решение неправильным, и объяснил, почему.

что out параметры хорошо ложаться по дефолту только на методы типа bool TryXXX(YYY x, out ZZZ result),

Что само по себе является адовым костылём, который придумали до массового распространения Maybe / Option / Either, с применением которых и railway programming код становится намного надёжнее и лаконичнее.

Затем, что ООП, не?

зачем этот пуризм?

которая ломает ООП

текущие реализации ооп и так сломаны, и с этим ничего не поделать.

по фиче языка на каждый чих

не хочтите фичи языка на каждый чих — делайте возможность делать фичи языка самостоятельно при помощи компиляторных плагинов и демократичного синтаксиса.

Именно, зачем создавать новый класс, если нужно вернуть два параметра вместо одного? Особенно, если это не является частью публичного контракта

Ну вот, только поговорили, и уже есть прецедент. Добавили возврат кортежа в новый метод интерфейса — не норм оказалось аж двум людям, включая меня). Поэтому я таки остаюсь при своём мнении...

качественно нужно продумывать публичные контракты
если код является инфраструктурным и не является частью публичного контракта

Интерфейс — это, по сути, публичный контракт. Тьюплы в публичных контрактах, наверное, не очень ок. Хотя, зависит от юзкейса. Лучше, наверное, добавить деконструктор, в возвращаемый объект, чтобы его можно было удобно использоваться, если это нужно.

Фичи не берутся ниоткуда — они решают частые задачи и проблемы.
А для неокрепших умов существуют рядом сидящие окрепшие, которые должны ревьюить и наставлять на путь истинный.

Фичи не берутся ниоткуда — они решают частые задачи и проблемы.
А для неокрепших умов существуют рядом сидящие окрепшие, которые должны ревьюить и наставлять на путь истинный.

«Против всего плохого, за всё хорошее».

Без понятия, что думали про лямбды в 3.5 и 2007й год, я тогда ещё Шарп не знал.

Чтобы узнать, что тогда происходило, можно глянуть на Java в 2017. Все те же аргументы, которые все давно забыли.

Чтобы узнать, что тогда происходило, можно глянуть на Java в 2017

В 18м пришлось писать на Джаве, поддерживая совместимость с версией 1.6 — вот это было по-настоящему весело.

В идеальном случае, фактически у меньшей части девелоперов. Язык должен подталкивать писать красивый код, а не предоставлять чрезмерное количество инструментов там, где этого не требуется. Выхлоп несоизмерим с негативным влиянием на неокрепшие умы, коих всегда больше. Вот у нас уже есть кортежи, и можно не думать, а зачем мне новый класс — фактически уже говнокод, просто массово одобренный а-ля ура, для 2-3 значений больше не нужно писать новую структуру (лень заморачиваться с ООП в конкретном кейсе, ну супер же просто покращення бгг).

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

В целом разработчики языка достаточно прагматичны и добавляют туда в основном вещи, что глобально не делают хуже чем было или есть. Хотя бывает и такое — как например null propagation operator.

От тюплов с момента их добавления вреда лично я много не видел, иногда это негативно влияло на чистоту паблик контрактов в коде, но что бы массово ими заменяли классы не видел.

Да, эта фигня абсолютно неудобная — я их именно в работе вообще ни разу не видел, чтобы кто-то юзал). Item1, Item2 или как там — брр.

Таки писали до версии 6 и тюплы практически не использовали, потом уже это все сделали красиво и тогда я начал встречаться с ними в коде.

Именные туплы не? Вы не путаете случайно ValueTuple и Tuple?

Предположим есть локальный метод (внутри метода) и возвращает он 2 значения.
Зачем ему заводить класс? Да можно сделать внутренний приватный класс, написать конструктор, а это один код 2 раза. Это 10 строк на класс, против по сути 0 строк. Плюс он всё равно находиться рядом, но отдельно от метода, где-то далеко.

Вторая причина паттерны для switch, как в конечных автоматах.

А что-то аналогичное case class в Scala в шарп пока не завезли ещё?

Да record нет пока, они их уже пару версий пытались ввести, но чтото не сходится у них.

github.com/...​ster/proposals/records.md

А что-то аналогичное case class в Scala в шарп пока не завезли ещё?

В C# 8 это делают деконструкцией и рекурсивными паттернами

 
static string M3(Shape shape)
  => shape switch
  {
    CombinedShape (var shape1, var (pos, _)) => $"combined shape - shape1: {shape1.Name}, pos of shape2: {pos}",
    { Size: (200, 200), Position: var pos } => $"shape with size 200x200 at position {pos.x}:{pos.y}",
    Ellipse (var pos, var size) => $"Ellipse with size {size} at position {pos}",
    Rectangle (_, var size) => $"Rectangle with size {size}",
    _ => "another shape"
  };

Это больше похоже на pattern matching... case class в Scala — это «сахар» для объявления классов, основная роль которых — это DTO в том или ином виде. Объявляются одной строкой, и не нужно ручками прописывать конструктор и свойства.

Это и есть аналог паттерн матчинга по дата класу в скале, я так полагал именно он интересен, а не сокращенное объявление.
Поскольку в таком варианте обычные C# классы с существующими компилятором и новым паттерн матчингом мало чем отличаються от того что есть в скале, кроме излешнего шума — характерного для императивного языка и отсутствия семантики нужной добавляемой компилятором по-умолчанию.

Возможно, не в ту ветку ответил. Приводил case classes как замену кортежа там, где хочется иметь именованные поля и более ООП стиль для возвращаемых значений.

Предположим есть локальный метод (внутри метода) и возвращает он 2 значения.
Зачем ему заводить класс? Да можно сделать внутренний приватный класс, написать конструктор, а это один код 2 раза. Это 10 строк на класс, против по сути 0 строк. Плюс он всё равно находиться рядом, но отдельно от метода, где-то далеко.

Если писать правильно и в тему, то, всё будет хорошо — я просто исхожу из позиции, что многие будут писать неправильно). Более того, о ужас, я и сам их пишу. Но если бы я решал, вводить это в Шарп или нет, то сказал бы — нет, т.к. кейсы с «10ю строками», по моему мнению, не настолько важны и часты, как удержание правильной иерархической структуры, которую дали лишний повод поломать.

по моему мнению, не настолько важны и часты, как удержание правильной иерархической структуры

Удержание правильной иерархической структры отъедает очень много времени, и лучше не удерживать её там где можно не удерживать. если мне нужно спдитнуть или парнуть коллекцию то лучше написать (foo, bar) = baz.split(...) . Если мне нужно в каком-нибуть процессинге вытащить несколько численных характеристик из объекта, или сделать груп бай по нескольким полям, то это делается проще через таплы и тегированные примитивы/типы нежели через архитектурные излишества.

По ссылке www.matheus.ro/...​g-asp-net-core-2-web-api EF вообще как таковой не вижу чтобы использовался, там человек используется оригинальный MongoDB.Driver

Спасибо, проверю, может не ту ссылку вставил.

Неплохо все расписно. Но непонятно почему не упомянуты Azure и соответственно Azure Devops как ci/cd система под Azure?

Azure это сама по себе большая платформа, я бы ее не выносил как часть .NET-стека, это одна из технологий для построения Cloud-решений.SDK Azure не только ведь под .NET существует.

Docker тоже само по себе большая платформа, но ее ж упомянули.
Я просто к тому, что .net developer на сегодняшний день, особенно если собирается работать с .net core то велика вероятность, что ему придется работать с Azure.

На самом деле не факт что Azure, очень часто выбирают AWS, ну и не факт что с клаудом, очень много проектов которые я видел использовали облако лишь как хостинг. Вероятность есть, конечно.

Странно. Я вижу Azure почти в каждой приходящей вакансии или контракте на дотнет. Но это у нас, может у вас по другому.

В общем по украине клауд в вакансиях не так сильно развит, а из клауда куда популярней AWS, субъективно.

Докер я упомянул потому, что для многих он совершенно непонятная вещь, а в релизах студии появилась его нативная поддержка. Вообще можете написать про историю Azure отдельную статью, я почитаю ) Но история Azure будет полезна только тем, кто работает с Azure, а их не так много.

а их не так много

но больше, чем фриков на GCP )

Підписатись на коментарі