День 2009. #ЧтоНовенького
Предложение Типов-Объединений в C#
Бывает, что вы хотите сохранить в переменной значение не всегда одного и того же типа. Может потребоваться сохранить один из нескольких связанных типов в зависимости от того, что данные должны представлять в конкретный момент.
Например, приложение может определить клиента и поставщика, которые имеют несколько общих свойств, и может потребоваться выполнить общую операцию над обоими типами, но с учётом их различий.
Обычно здесь выделяют общие члены в абстрактный класс или интерфейс, и делают разных наследников. Но это хорошо, только когда эти типы существуют в первую очередь для цели операции или имеет смысл, чтобы операция отображалась как часть типа. Если типы имеют более широкое назначение, загрязнение их такими методами может быть нежелательным. Кроме того, вы можете не владеть определениями типов, либо у вас слишком много похожих ситуаций и вы можете решить только одну из них через наследование, либо вы решили не пропускать требования конкретной операции в определение данных, тогда единственный простой выбор, который у вас есть, — объявить переменную как object и позволить ей быть чем угодно.
Альтернативой является создание одной и той же логики для обработки обоих типов, и объявление параметра или переменной, которая может содержать любой из типов. Было бы хорошо, если бы язык предоставлял способ объявить тип, который хранит значение одного из ограниченного набора других типов, и при этом сохранял бы безопасность типов. Многие другие языки уже делают это. Обычно такие специальные типы называют discriminated union (дискриминированными/размеченными объединениями) или просто объединениями типов.
Недавно было предложено (точнее уже давно, сейчас предложение просто обновили) добавить в C# 4 типа объединений:
1. Стандартные
Именованное объединение типов, которое объявляет все типы своих членов в одном объявлении (похоже на расширенный enum):
2. Структуры
Аналогично классу, но объединение и типы членов являются структурами.
3. «По требованию» (Ad hoc)
Анонимные объединения типов, объявляемые в любом месте.
4. Пользовательские
Если необходимо объявить тип объединения, который не может быть указан как один из типов выше, можно объявить пользовательский тип объединения.
Но не спешите радоваться, это пока только обновлённое предложение. Так что завезут его в лучшем случае через пару релизов (лет), и то, скорей всего, сначала в виде превью.
Источник: https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md
Предложение Типов-Объединений в C#
Бывает, что вы хотите сохранить в переменной значение не всегда одного и того же типа. Может потребоваться сохранить один из нескольких связанных типов в зависимости от того, что данные должны представлять в конкретный момент.
Например, приложение может определить клиента и поставщика, которые имеют несколько общих свойств, и может потребоваться выполнить общую операцию над обоими типами, но с учётом их различий.
Обычно здесь выделяют общие члены в абстрактный класс или интерфейс, и делают разных наследников. Но это хорошо, только когда эти типы существуют в первую очередь для цели операции или имеет смысл, чтобы операция отображалась как часть типа. Если типы имеют более широкое назначение, загрязнение их такими методами может быть нежелательным. Кроме того, вы можете не владеть определениями типов, либо у вас слишком много похожих ситуаций и вы можете решить только одну из них через наследование, либо вы решили не пропускать требования конкретной операции в определение данных, тогда единственный простой выбор, который у вас есть, — объявить переменную как object и позволить ей быть чем угодно.
Альтернативой является создание одной и той же логики для обработки обоих типов, и объявление параметра или переменной, которая может содержать любой из типов. Было бы хорошо, если бы язык предоставлял способ объявить тип, который хранит значение одного из ограниченного набора других типов, и при этом сохранял бы безопасность типов. Многие другие языки уже делают это. Обычно такие специальные типы называют discriminated union (дискриминированными/размеченными объединениями) или просто объединениями типов.
Недавно было предложено (точнее уже давно, сейчас предложение просто обновили) добавить в C# 4 типа объединений:
1. Стандартные
Именованное объединение типов, которое объявляет все типы своих членов в одном объявлении (похоже на расширенный enum):
union U
{
A(int x, string y);
B(int z);
C;
}
U u = new A(10, "ten");
2. Структуры
Аналогично классу, но объединение и типы членов являются структурами.
union struct U
{
A(int x, string y);
B(int z);
C;
}
U u = new A(10, "ten");
3. «По требованию» (Ad hoc)
Анонимные объединения типов, объявляемые в любом месте.
(A or B or C) u = new A(10, "ten");
4. Пользовательские
Если необходимо объявить тип объединения, который не может быть указан как один из типов выше, можно объявить пользовательский тип объединения.
[Closed]
public class U { … }
public class A(int x, string y) : U { … }
public class B(int z) : U { … }
Но не спешите радоваться, это пока только обновлённое предложение. Так что завезут его в лучшем случае через пару релизов (лет), и то, скорей всего, сначала в виде превью.
Источник: https://github.com/dotnet/csharplang/blob/main/proposals/TypeUnions.md
👍18👎2
День 2010. #ЗаметкиНаПолях #AsyncTips
Конечный Автомат в C# для async/await. Начало
Часто говорят, что ключевые слова async/await приводят к созданию конечного автомата. Но что это значит? Рассмотрим на простом примере:
Здесь несколько вызовов await: для получения ответа, и для чтения содержимого.
Деление метода по границе await
Каждый раз при вызове await, мы знаем, что нам не нужно ничего делать, кроме как ждать результата. Логично при этом просто выйти из метода и вернуться, как только будет получен результат. Компилятор разделит метод по границе await и создаст конечный автомат. Вот упрощённый код:
Это очень упрощенная версия того, что делает компилятор, и она не учитывает важные части, например, как содержимое извлекается из HttpClient. Важно то, что мы синхронно вызываем часть метода до await, а затем используем механизм обратных вызовов (именно поэтому используется ref this) для продолжений метода. Таким образом, как только HTTP-вызов завершается, мы возвращаемся в метод и продолжаем с того места, где остановились (state = 1), когда завершается чтение – продолжаем со state=2.
Окончание следует…
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
Конечный Автомат в C# для async/await. Начало
Часто говорят, что ключевые слова async/await приводят к созданию конечного автомата. Но что это значит? Рассмотрим на простом примере:
async Task<Dto> GetAsync()
{
using var hc = new HttpClient();
hc.BaseAddress = new Uri("…");
var resp = await hc.GetAsync("");
resp.EnsureSuccessStatusCode();
var content =
await resp.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<Dto>(content);
}
public class Dto;
Здесь несколько вызовов await: для получения ответа, и для чтения содержимого.
Деление метода по границе await
Каждый раз при вызове await, мы знаем, что нам не нужно ничего делать, кроме как ждать результата. Логично при этом просто выйти из метода и вернуться, как только будет получен результат. Компилятор разделит метод по границе await и создаст конечный автомат. Вот упрощённый код:
public class GetAllAsync_StateMachine
{
public ContinuationMachine _builder =
ContinuationMachineBuilder.Create();
private int _state = 0;
private HttpClient _hc;
private HttpResponseMessage _resp;
private string _content;
private void MoveNext()
{
switch (_state)
{
case 0:
_hc = new HttpClient();
_hc.BaseAddress = new Uri("…");
_hc.GetAsync("");
_state = 1;
_builder.Continue(ref this);
break;
case 1:
_resp.EnsureSuccessStatusCode();
_resp.Content.ReadAsStringAsync();
_state = 2;
_builder.Continue(ref this);
break;
case 2:
return JsonSerializer.Deserialize<Dto>(_content);
}
}
}
Это очень упрощенная версия того, что делает компилятор, и она не учитывает важные части, например, как содержимое извлекается из HttpClient. Важно то, что мы синхронно вызываем часть метода до await, а затем используем механизм обратных вызовов (именно поэтому используется ref this) для продолжений метода. Таким образом, как только HTTP-вызов завершается, мы возвращаемся в метод и продолжаем с того места, где остановились (state = 1), когда завершается чтение – продолжаем со state=2.
Окончание следует…
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
👍21
День 2011. #ЗаметкиНаПолях #AsyncTips
Конечный Автомат в C# для async/await. Окончание
Начало
Планировщик заданий
Продолжения (обратные вызовы) размещаются в планировщике заданий (TaskScheduler). Он берёт конечный автомат и планирует его выполнение после завершения ожидаемой задачи. Таким образом, TaskScheduler отвечает за продолжения. Здесь есть ещё несколько интересных моментов, таких как контекст синхронизации (SynchronizationContext) и пул потоков (ThreadPool), но это детали.
Итак: при использовании async/await компилятор разделит метод по границам await и создаст конечный автомат. Этот конечный автомат будет запланирован в TaskScheduler для продолжения после завершения ожидаемой задачи.
Где хранится конечный автомат?
В общем случае - в Task или Task<T>. Там хранится текущее состояние (включая продолжения), а также результат ожидаемой задачи. Но, кроме того, объект Task также хранит исключения. Исключения в с async/await-коде немного отличаются от исключений в синхронном коде.
Рассмотрим следующий код:
Компилятор преобразует этот код в следующий:
Важно отметить, что внутри блока catch нет throw. Т.е., если возникнет исключение в асинхронной части метода, оно не будет выброшено, а будет сохранено в объекте Task.
Возможно, теперь вы понимаете, почему async void — плохая идея: исключения будут потеряны. Они будут возникать, но вы не сможете их перехватить или обработать каким-либо образом. То же самое относится к async Task, если вы не ожидаете её. Поэтому:
Исключение перехватывается и сохраняется в объекте Task, не выбрасываясь во внешний код. Но когда вы ожидаете объект Task, вызов await будет преобразован во что-то вроде GetAwaiter().GetResult(), и именно здесь исключение выбрасывается из объекта Task наружу.
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
Конечный Автомат в C# для async/await. Окончание
Начало
Планировщик заданий
Продолжения (обратные вызовы) размещаются в планировщике заданий (TaskScheduler). Он берёт конечный автомат и планирует его выполнение после завершения ожидаемой задачи. Таким образом, TaskScheduler отвечает за продолжения. Здесь есть ещё несколько интересных моментов, таких как контекст синхронизации (SynchronizationContext) и пул потоков (ThreadPool), но это детали.
Итак: при использовании async/await компилятор разделит метод по границам await и создаст конечный автомат. Этот конечный автомат будет запланирован в TaskScheduler для продолжения после завершения ожидаемой задачи.
Где хранится конечный автомат?
В общем случае - в Task или Task<T>. Там хранится текущее состояние (включая продолжения), а также результат ожидаемой задачи. Но, кроме того, объект Task также хранит исключения. Исключения в с async/await-коде немного отличаются от исключений в синхронном коде.
Рассмотрим следующий код:
static async Task ThrowExceptionAsync()
{
await Task.Yield();
throw new Exception("Ex");
}
Компилятор преобразует этот код в следующий:
try
{
YieldAwaitable.YieldAwaiter awaiter;
// Другой код
awaiter.GetResult();
throw new Exception("Ex");
}
catch (Exception exception)
{
<>1__state = -2;
<>t__builder.SetException(exception);
}
Важно отметить, что внутри блока catch нет throw. Т.е., если возникнет исключение в асинхронной части метода, оно не будет выброшено, а будет сохранено в объекте Task.
Возможно, теперь вы понимаете, почему async void — плохая идея: исключения будут потеряны. Они будут возникать, но вы не сможете их перехватить или обработать каким-либо образом. То же самое относится к async Task, если вы не ожидаете её. Поэтому:
static async Task ThrowExAsync()
{
throw new Exception("Ex");
await SomethingAsync();
}
// не выбросит исключения
_ = ThrowExAsync();
// не выбросит исключения
ThrowExAsync();
// выбросит исключение
await ThrowExAsync();
Исключение перехватывается и сохраняется в объекте Task, не выбрасываясь во внешний код. Но когда вы ожидаете объект Task, вызов await будет преобразован во что-то вроде GetAwaiter().GetResult(), и именно здесь исключение выбрасывается из объекта Task наружу.
Источник: https://steven-giesel.com/blogPost/720a48fd-0abe-4c32-83ac-26926d501895/the-state-machine-in-c-with-asyncawait
👍16👎1
День 2012. #BlazorBasics
Основы Blazor. Создание Компонента Blazor
Приложения Blazor состоят из нескольких слоёв компонентов. Все в Blazor является компонентом. Компонентная архитектура имеет много преимуществ, таких как простое повторное использование кода, изолированное программирование и компоновка, даже в нескольких приложениях Blazor.
Компоненты можно разделить на 3 категории: страницы, компоненты и элементы управления. Технически это всё компоненты Blazor, создающие в сочетании дерево компонентов.
Компонент Blazor состоит из двух основных частей — шаблона и кода взаимодействия. Шаблон использует стандартные веб-технологии, такие как HTML и CSS. Код взаимодействия реализован на C#:
Этот простой компонент содержит шаблон с одним HTML-элементом <h1>. Синтаксис шаблона Blazor основан на синтаксисе Razor. Содержимое элемента <h1> ссылается на свойство Title кода C# в блоке @code. Значение переменной выводится с помощью символа @, за которым следует имя свойства. Символ @ используется везде в шаблоне, когда нужно сослаться на код из раздела @code компонента.
Использование существующих элементов
Идея в том, чтобы сделать компонент как можно меньше, ограничив его одной ответственностью. Например, компонент формы обрабатывает взаимодействие с формой, но не определяет шаблон для каждого отдельного поля в форме.
Некоторые компоненты могут использоваться в нескольких приложениях. Можно создавать компоненты самому, что занимает много времени, либо использовать стороннюю библиотеку для UI компонентов Blazor, вроде Blazorise, Radzen, MudBlazor или Telerik Blazor UI.
Страницы в приложениях Blazor
В Blazor страницы являются компонентами. Единственное, что нужно, чтобы превратить компонент в страницу, — это добавить директиву @page.
В этом примере мы использовали маршрут
Деревья компонентов
Теперь извлечём компонент MyComponent.razor из страницы MyPage.razor:
Мы создали два компонента и сослались на компонент MyComponent в компоненте MyPage. Можно продолжить в том же духе для построения целого дерева компонентов: страница может содержать компоненты Content и Menu. Menu - содержать несколько компонентов MenuItem и т.д.
Передача параметров из родительского компонента в дочерний
Нужно добавить атрибут Parameter, чтобы превратить свойство в параметр, который можно задать извне компонента:
Теперь в родительском компоненте зададим значение для свойства Title:
Источник: https://www.telerik.com/blogs/blazor-basics-creating-blazor-component
Основы Blazor. Создание Компонента Blazor
Приложения Blazor состоят из нескольких слоёв компонентов. Все в Blazor является компонентом. Компонентная архитектура имеет много преимуществ, таких как простое повторное использование кода, изолированное программирование и компоновка, даже в нескольких приложениях Blazor.
Компоненты можно разделить на 3 категории: страницы, компоненты и элементы управления. Технически это всё компоненты Blazor, создающие в сочетании дерево компонентов.
Компонент Blazor состоит из двух основных частей — шаблона и кода взаимодействия. Шаблон использует стандартные веб-технологии, такие как HTML и CSS. Код взаимодействия реализован на C#:
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
Этот простой компонент содержит шаблон с одним HTML-элементом <h1>. Синтаксис шаблона Blazor основан на синтаксисе Razor. Содержимое элемента <h1> ссылается на свойство Title кода C# в блоке @code. Значение переменной выводится с помощью символа @, за которым следует имя свойства. Символ @ используется везде в шаблоне, когда нужно сослаться на код из раздела @code компонента.
Использование существующих элементов
Идея в том, чтобы сделать компонент как можно меньше, ограничив его одной ответственностью. Например, компонент формы обрабатывает взаимодействие с формой, но не определяет шаблон для каждого отдельного поля в форме.
Некоторые компоненты могут использоваться в нескольких приложениях. Можно создавать компоненты самому, что занимает много времени, либо использовать стороннюю библиотеку для UI компонентов Blazor, вроде Blazorise, Radzen, MudBlazor или Telerik Blazor UI.
Страницы в приложениях Blazor
В Blazor страницы являются компонентами. Единственное, что нужно, чтобы превратить компонент в страницу, — это добавить директиву @page.
@page "/MyPage"
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
В этом примере мы использовали маршрут
/MyPage
в качестве маршрута, по которому будет доступна страница. Директива @page регистрирует компонент как страницу и добавляет маршрут к поставщику маршрутизации ASP.NET Core.Деревья компонентов
Теперь извлечём компонент MyComponent.razor из страницы MyPage.razor:
@page "/MyPage"
<MyComponent />
<h1>@Title</h1>
@code {
public string Title { get; set; }
= "Мой компонент";
}
Мы создали два компонента и сослались на компонент MyComponent в компоненте MyPage. Можно продолжить в том же духе для построения целого дерева компонентов: страница может содержать компоненты Content и Menu. Menu - содержать несколько компонентов MenuItem и т.д.
Передача параметров из родительского компонента в дочерний
Нужно добавить атрибут Parameter, чтобы превратить свойство в параметр, который можно задать извне компонента:
<h1>@Title</h1>
@code {
[Parameter]
public string Title { get; set; }
}
Теперь в родительском компоненте зададим значение для свойства Title:
@page "/MyPage"
<MyComponent Title="Заголовок" />
Источник: https://www.telerik.com/blogs/blazor-basics-creating-blazor-component
👍9
День 2013. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Начало
В классической книге «Мифический человеко-месяц» Брукс советует: «Планируйте выбросить первую версию — вам всё равно придётся это сделать». Он имеет в виду, что в крупных проектах желательно создавать пилотную систему, чтобы выяснить, как лучше построить основную. Это недешёвое удовольствие, особенно если в систему входят аппаратные компоненты. Но пилотная система может пригодиться, если есть сомнения в технической осуществимости или подходящая стратегия проектирования изначально неясна. Пилотная система также может помочь выявить неизвестные факторы, о существовании которых вы не догадывались.
Вряд ли вы согласитесь создать, а потом выбросить первую версию продукта, но лучше пересмотреть потенциальное проектное решение, прежде чем команда слишком далеко продвинется в разработке. Идея создания максимально простого проекта выглядит привлекательно и действительно ускоряет доставку решения. Быстрая доставка может повысить ценность продукта для покупателя в краткосрочной перспективе, но редко когда оказывается лучшей долгосрочной стратегией, особенно если продукт продолжает развиваться.
Любая задача имеет несколько решений, и редко лучшим оказывается первое из них. Работу по проектированию можно считать выполненной, только если вы придумали хотя бы три решения, отбросили их все, а затем объединили лучшие их части в превосходное четвёртое решение. Иногда только рассмотрев три варианта, начинаешь осознавать, насколько плохо понимаешь проблему.
Есть несколько методов, помогающих разработчикам переходить от первоначальной концепции к эффективному решению. Один из них — прототипирование, помогающее последовательно выполнять техническое проектирование и улучшать пользовательский опыт.
Прототип — это частичное, предварительное или возможное решение. В прототипе вы воплощаете часть системы, проверяя, насколько правильно вы понимаете, как спроектировать решение. Если эксперимент не удался, то вы меняете подход и пробуете снова. Прототип помогает оценить риски и снизить их, особенно если используется новый архитектурный или проектный шаблон, который следует проверить, прежде чем принять его за основу.
Во-первых, определите, что вы собираетесь делать: отказаться от прототипа и разработать реальное решение или превратить его в окончательный продукт. Если второе, вы должны с самого начала создания прототипа придавать ему промышленное качество. Это потребует больше усилий, чем создание чего-то временного, от чего вы откажетесь.
Прежде чем приступить к конкретному решению, Agile-команды иногда создают истории, называемые спайками (spike), предназначенные для исследования технических подходов, устранения неопределённости и снижения риска. В отличие от других пользовательских историй, основной результат спайка — не рабочий код, а знания. В спайки может входить создание технических прототипов, прототипов UI или того и другого в зависимости от искомой информации. Спайк должен иметь чёткую цель, как научный эксперимент. У разработчика есть гипотеза, и для получения доказательств её верности или неверности, подтверждения обоснованности какого-либо подхода или быстрого принятия технического решения реализуется спайк.
Например, задача — сделать авторизацию в приложении. Но разработчики не понимают, какую технологию лучше использовать. И вместо того, чтобы бросаться создавать код, они тормозят разработку и изучают технологии, подбирая наиболее подходящую с учётом всех требований.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Начало
В классической книге «Мифический человеко-месяц» Брукс советует: «Планируйте выбросить первую версию — вам всё равно придётся это сделать». Он имеет в виду, что в крупных проектах желательно создавать пилотную систему, чтобы выяснить, как лучше построить основную. Это недешёвое удовольствие, особенно если в систему входят аппаратные компоненты. Но пилотная система может пригодиться, если есть сомнения в технической осуществимости или подходящая стратегия проектирования изначально неясна. Пилотная система также может помочь выявить неизвестные факторы, о существовании которых вы не догадывались.
Вряд ли вы согласитесь создать, а потом выбросить первую версию продукта, но лучше пересмотреть потенциальное проектное решение, прежде чем команда слишком далеко продвинется в разработке. Идея создания максимально простого проекта выглядит привлекательно и действительно ускоряет доставку решения. Быстрая доставка может повысить ценность продукта для покупателя в краткосрочной перспективе, но редко когда оказывается лучшей долгосрочной стратегией, особенно если продукт продолжает развиваться.
Любая задача имеет несколько решений, и редко лучшим оказывается первое из них. Работу по проектированию можно считать выполненной, только если вы придумали хотя бы три решения, отбросили их все, а затем объединили лучшие их части в превосходное четвёртое решение. Иногда только рассмотрев три варианта, начинаешь осознавать, насколько плохо понимаешь проблему.
Есть несколько методов, помогающих разработчикам переходить от первоначальной концепции к эффективному решению. Один из них — прототипирование, помогающее последовательно выполнять техническое проектирование и улучшать пользовательский опыт.
Прототип — это частичное, предварительное или возможное решение. В прототипе вы воплощаете часть системы, проверяя, насколько правильно вы понимаете, как спроектировать решение. Если эксперимент не удался, то вы меняете подход и пробуете снова. Прототип помогает оценить риски и снизить их, особенно если используется новый архитектурный или проектный шаблон, который следует проверить, прежде чем принять его за основу.
Во-первых, определите, что вы собираетесь делать: отказаться от прототипа и разработать реальное решение или превратить его в окончательный продукт. Если второе, вы должны с самого начала создания прототипа придавать ему промышленное качество. Это потребует больше усилий, чем создание чего-то временного, от чего вы откажетесь.
Прежде чем приступить к конкретному решению, Agile-команды иногда создают истории, называемые спайками (spike), предназначенные для исследования технических подходов, устранения неопределённости и снижения риска. В отличие от других пользовательских историй, основной результат спайка — не рабочий код, а знания. В спайки может входить создание технических прототипов, прототипов UI или того и другого в зависимости от искомой информации. Спайк должен иметь чёткую цель, как научный эксперимент. У разработчика есть гипотеза, и для получения доказательств её верности или неверности, подтверждения обоснованности какого-либо подхода или быстрого принятия технического решения реализуется спайк.
Например, задача — сделать авторизацию в приложении. Но разработчики не понимают, какую технологию лучше использовать. И вместо того, чтобы бросаться создавать код, они тормозят разработку и изучают технологии, подбирая наиболее подходящую с учётом всех требований.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍19
День 2014. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Окончание
Начало
Проверка концепции
Прототипы для проверки концепции, также называемые вертикальными прототипами (реализуется часть функциональности от базовых функций до UI), полезны для проверки предлагаемой архитектуры. Экспериментирование с прототипом для проверки концепции — относительно недорогой способ итерации, даже при том, что требует создания некоего действующего ПО. Такие прототипы полезны для оценки технических аспектов проекта: архитектуры, алгоритмов, структуры БД, системных интерфейсов и взаимодействий. С помощью прототипа можно оценить свойства архитектуры, такие как производительность, безопасность, защищённость и надёжность, а затем постепенно улучшать их.
Макеты
Проектирование UI всегда должно выполняться итеративно. Даже следуя принятым соглашениям о UI, вы должны провести хотя бы неформальное тестирование удобства использования, чтобы выбрать подходящие элементы управления и схемы их размещения, обеспечивающие простоту освоения и использования, а также высокую доступность. Например, A/B-тестирование — подход, при котором вы предоставляете пользователям два альтернативных интерфейса и даете им возможность выбрать наиболее удобный для них. Люди, которые проводят A/B-тестирование, могут наблюдать за поведением пользователей с помощью различных подходов и определять, какой вариант понятнее или быстрее приводит к успешным результатам. Проводить такие эксперименты проще, быстрее и дешевле, находясь на этапе изучения дизайна, чем после доставки решения клиентам, реагируя на их жалобы или на более низкую, чем ожидалось, частоту щелчков на веб-странице.
Как и в случае с требованиями, проектирование UI продвигается успешнее при постепенном уточнении деталей с помощью прототипирования. С этой целью можно создавать макеты, также называемые горизонтальными прототипами, поскольку они представляют собой тонкий слой UI без какой-либо функциональной основы под ним. Макетами могут быть и простые эскизы экрана, и действующие интерфейсы, которые выглядят аутентично, но не выполняют никакой реальной работы. Ценны даже простые бумажные прототипы — их можно быстро создавать и изменять. Вы можете использовать текстовый редактор или даже каталожные карточки, чтобы разместить элементы данных в прямоугольных областях, представляющих потенциальные экраны, посмотреть, как они соотносятся друг с другом, и отметить, какие из них принимают ввод пользователя, а какие отображают результаты. Но при прототипировании UI остерегайтесь следующих ловушек.
1. Не тратьте слишком много времени на совершенствование UI («Может, для этого текста лучше выбрать темно-красный цвет?») до того, как организуете информацию и элементы управления на экране и создадите функциональные макеты.
2. Клиенты или руководители могут подумать, что ПО почти готово, увидев удобный и красивый UI, даже если за ним нет ничего, кроме имитации функциональности. Менее изысканный прототип показывает, что ПО ещё не готово.
3. Не стремитесь подсказывать, как правильно действовать, тем, кто оценивает прототип и пытается выполнить неочевидную для них задачу. Вы не сможете судить об удобстве использования, помогая участникам тестирования изучать и применять прототип.
Если вы не потратили время на итеративное изучение UI и техническое проектирование до реализации, то рискуете создать продукт, который не понравится клиентам. Бездумно спроектированные продукты раздражают клиентов, вынуждают их неэффективно тратить своё время, подрывают их хорошее отношение к вашему продукту и компании и заставляют писать негативные отзывы. Потратив чуть больше времени на проектирование, вы значительно приблизитесь к созданию полезного и удобного решения.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 17. Проектирование — итеративный процесс. Окончание
Начало
Проверка концепции
Прототипы для проверки концепции, также называемые вертикальными прототипами (реализуется часть функциональности от базовых функций до UI), полезны для проверки предлагаемой архитектуры. Экспериментирование с прототипом для проверки концепции — относительно недорогой способ итерации, даже при том, что требует создания некоего действующего ПО. Такие прототипы полезны для оценки технических аспектов проекта: архитектуры, алгоритмов, структуры БД, системных интерфейсов и взаимодействий. С помощью прототипа можно оценить свойства архитектуры, такие как производительность, безопасность, защищённость и надёжность, а затем постепенно улучшать их.
Макеты
Проектирование UI всегда должно выполняться итеративно. Даже следуя принятым соглашениям о UI, вы должны провести хотя бы неформальное тестирование удобства использования, чтобы выбрать подходящие элементы управления и схемы их размещения, обеспечивающие простоту освоения и использования, а также высокую доступность. Например, A/B-тестирование — подход, при котором вы предоставляете пользователям два альтернативных интерфейса и даете им возможность выбрать наиболее удобный для них. Люди, которые проводят A/B-тестирование, могут наблюдать за поведением пользователей с помощью различных подходов и определять, какой вариант понятнее или быстрее приводит к успешным результатам. Проводить такие эксперименты проще, быстрее и дешевле, находясь на этапе изучения дизайна, чем после доставки решения клиентам, реагируя на их жалобы или на более низкую, чем ожидалось, частоту щелчков на веб-странице.
Как и в случае с требованиями, проектирование UI продвигается успешнее при постепенном уточнении деталей с помощью прототипирования. С этой целью можно создавать макеты, также называемые горизонтальными прототипами, поскольку они представляют собой тонкий слой UI без какой-либо функциональной основы под ним. Макетами могут быть и простые эскизы экрана, и действующие интерфейсы, которые выглядят аутентично, но не выполняют никакой реальной работы. Ценны даже простые бумажные прототипы — их можно быстро создавать и изменять. Вы можете использовать текстовый редактор или даже каталожные карточки, чтобы разместить элементы данных в прямоугольных областях, представляющих потенциальные экраны, посмотреть, как они соотносятся друг с другом, и отметить, какие из них принимают ввод пользователя, а какие отображают результаты. Но при прототипировании UI остерегайтесь следующих ловушек.
1. Не тратьте слишком много времени на совершенствование UI («Может, для этого текста лучше выбрать темно-красный цвет?») до того, как организуете информацию и элементы управления на экране и создадите функциональные макеты.
2. Клиенты или руководители могут подумать, что ПО почти готово, увидев удобный и красивый UI, даже если за ним нет ничего, кроме имитации функциональности. Менее изысканный прототип показывает, что ПО ещё не готово.
3. Не стремитесь подсказывать, как правильно действовать, тем, кто оценивает прототип и пытается выполнить неочевидную для них задачу. Вы не сможете судить об удобстве использования, помогая участникам тестирования изучать и применять прототип.
Если вы не потратили время на итеративное изучение UI и техническое проектирование до реализации, то рискуете создать продукт, который не понравится клиентам. Бездумно спроектированные продукты раздражают клиентов, вынуждают их неэффективно тратить своё время, подрывают их хорошее отношение к вашему продукту и компании и заставляют писать негативные отзывы. Потратив чуть больше времени на проектирование, вы значительно приблизитесь к созданию полезного и удобного решения.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2015. #ЗаметкиНаПолях
Статические Абстрактные Члены Интерфейсов в Конфигурации
Статические абстрактные члены интерфейсов появились в C# 11. Основной мотивацией для них является поддержка обобщённой математики. Но они могут быть полезны и в других сценариях. Например, упростить код регистрации и использования типов разделов пользовательской конфигурации.
Например, вы хотите настроить клиент API в appSettings.json:
Хорошей практикой является связать раздел с отдельным типом:
Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
Добавим методы расширения для регистрации секций конфигурации:
Теперь можно регистрировать различные секции конфигурации так:
Источник: https://haacked.com/archive/2024/07/18/better-config-sections/
Статические Абстрактные Члены Интерфейсов в Конфигурации
Статические абстрактные члены интерфейсов появились в C# 11. Основной мотивацией для них является поддержка обобщённой математики. Но они могут быть полезны и в других сценариях. Например, упростить код регистрации и использования типов разделов пользовательской конфигурации.
Например, вы хотите настроить клиент API в appSettings.json:
{
…
"GoogleAPI": {
"ApiKey": "…",
"OrganizationId": "…",
…
}
}
Хорошей практикой является связать раздел с отдельным типом:
public class GoogleOptions {
public string? ApiKey { get; init; }
public string? OrganizationId { get; init; }
…
}
…
// добавляем в Program.cs
builder.Configuration
.Configure<GoogleOptions>(
builder.Configuration.GetSection("GoogleAPI"));
…
// внедряем через DI
public class GoogleClient(
IOptions<GoogleOptions> options) {
// …
}
Упростим регистрацию через статические члены интерфейсов. Сначала определим интерфейс:
public interface IConfigOptions
{
static abstract string SectionName { get; }
}
Все реализации этого интерфейса должны иметь статическое свойство SectionName:
public class GoogleOptions: IConfigOptions {
public static string SectionName => "GoogleAPI";
// остальные члены
}
Добавим методы расширения для регистрации секций конфигурации:
public static class OptionsExtensions {
public static IHostApplicationBuilder
Configure<TOptions>(
this IHostApplicationBuilder builder)
where TOptions : class, IConfigOptions
{
var sect = builder.Configuration
.GetSection(TOptions.SectionName);
builder.Services.Configure<TOptions>(sect);
return builder;
}
}
Теперь можно регистрировать различные секции конфигурации так:
builder.Configure<GoogleOptions>()
.Configure<GitHubOptions>()
.Configure<WeatherOptions>()
Источник: https://haacked.com/archive/2024/07/18/better-config-sections/
👍47
День 2016.
Сейчас, конечно, самое время рекомендовать видео с Ютуба, но я не мог пройти мимо этого. Один из моих любимых докладчиков, Дилан Битти, недавно выступил на конференции NDC Oslo 2024 с рассказом о ПО с открытым кодом и вообще бесплатно распространяемом ПО. Кстати, поставив вопрос о самом значении слова «Free» в выражении «Free software». Оно означает «бесплатное» как бесплатное пиво, или «свободное», как свобода слова?
Когда мы говорим о чем-то «бесплатном», это обычно хорошо. Но, как знает любой, кто когда-либо раздавал свое ПО бесплатно, это не так просто.
С одной стороны, волонтеры используют бесплатное ПО на благие цели. С другой стороны, технологические гиганты используют пакеты бесплатного ПО для создания продуктов, которые приносят миллионы долларов прибыли каждый год — но, когда создатели этих пакетов пытаются просить часть этого дохода в уплату за пользование их пакетом, на них сваливаются тонны хейта.
Дилан Битти обозревает прошлое, настоящее и будущее «свободного» ПО: историю движения за свободное ПО, от лаборатории ИИ MIT до эпохи GitHub и менеджеров пакетов. Он расскажет, почему люди изначально решают отдавать свой код бесплатно, и что произойдёт, если они изменят свое решение. Также будут описаны различные лицензии и законность использования ПО, некоторые странные и замечательные крайности, которые свободное ПО создало за эти годы. И в итоге Дилан попробует дать ответ на вопрос: возможна ли по-настоящему устойчивая экосистема с открытым исходным кодом, и если да, то как она может выглядеть?
https://youtu.be/vzYqxo13I1U
Сейчас, конечно, самое время рекомендовать видео с Ютуба, но я не мог пройти мимо этого. Один из моих любимых докладчиков, Дилан Битти, недавно выступил на конференции NDC Oslo 2024 с рассказом о ПО с открытым кодом и вообще бесплатно распространяемом ПО. Кстати, поставив вопрос о самом значении слова «Free» в выражении «Free software». Оно означает «бесплатное» как бесплатное пиво, или «свободное», как свобода слова?
Когда мы говорим о чем-то «бесплатном», это обычно хорошо. Но, как знает любой, кто когда-либо раздавал свое ПО бесплатно, это не так просто.
С одной стороны, волонтеры используют бесплатное ПО на благие цели. С другой стороны, технологические гиганты используют пакеты бесплатного ПО для создания продуктов, которые приносят миллионы долларов прибыли каждый год — но, когда создатели этих пакетов пытаются просить часть этого дохода в уплату за пользование их пакетом, на них сваливаются тонны хейта.
Дилан Битти обозревает прошлое, настоящее и будущее «свободного» ПО: историю движения за свободное ПО, от лаборатории ИИ MIT до эпохи GitHub и менеджеров пакетов. Он расскажет, почему люди изначально решают отдавать свой код бесплатно, и что произойдёт, если они изменят свое решение. Также будут описаны различные лицензии и законность использования ПО, некоторые странные и замечательные крайности, которые свободное ПО создало за эти годы. И в итоге Дилан попробует дать ответ на вопрос: возможна ли по-настоящему устойчивая экосистема с открытым исходным кодом, и если да, то как она может выглядеть?
https://youtu.be/vzYqxo13I1U
YouTube
Open Source, Open Mind: The Cost of Free Software - Dylan Beattie - NDC Oslo 2024
This talk was recorded at NDC Oslo in Oslo, Norway. #ndcoslo #ndcconferences #developer #softwaredeveloper
Attend the next NDC conference near you:
https://ndcconferences.com
https://ndcoslo.com/
Subscribe to our YouTube channel and learn every day:…
Attend the next NDC conference near you:
https://ndcconferences.com
https://ndcoslo.com/
Subscribe to our YouTube channel and learn every day:…
👍14
День 2017. #ЗаметкиНаПолях
Организуем Параметры Минимальных API
Несмотря на то, что эта функциональность была добавлена в .NET 7, я не писал о ней подробно, только пару слов про превью. Сегодня расскажу, что это.
Рассмотрим следующий минимальный API:
У нас несколько разнородных параметров, разбросанных по сигнатуре метода. В простом примере это не страшно, но может быстро перерасти в беспорядок. Мы можем использовать атрибут AsParameters, чтобы сгруппировать их вместе:
Здесь используется структура-запись (см. п.5 здесь), но классы также поддерживаются. Рекомендуется использовать структуру-запись, чтобы избежать дополнительного выделения памяти.
Кроме того, мы можем применять любые атрибуты привязки (FromHeader, FromQuery, FromServices и т. д.). В примере выше значение тенанта будет браться из заголовка запроса.
Источники:
- https://steven-giesel.com/blogPost/ee26a396-8f5a-40a7-9dd5-f08cd8c03518/organizing-parameters-in-minimal-api-with-the-asparametersattribute
- https://jaliyaudagedara.blogspot.com/2022/07/net-7-preview-5-using-asparameters.html
Организуем Параметры Минимальных API
Несмотря на то, что эта функциональность была добавлена в .NET 7, я не писал о ней подробно, только пару слов про превью. Сегодня расскажу, что это.
Рассмотрим следующий минимальный API:
app.MapGet("ToDos", async (
ToDoDbContext dbContext,
string? tenantId,
int offset,
int limit,
ILogger<Program> logger) =>
{
logger.LogInformation(…);
var todos = await dbContext.ToDos
.Where(x => x.TenantId == tenantId)
.OrderBy(x => x.Id)
.Skip(offset)
.Take(limit)
.ToListAsync();
return TypedResults.Ok(todos);
});
У нас несколько разнородных параметров, разбросанных по сигнатуре метода. В простом примере это не страшно, но может быстро перерасти в беспорядок. Мы можем использовать атрибут AsParameters, чтобы сгруппировать их вместе:
app.MapGet("ToDos/WithRequest", async ([AsParameters] GetRequest request) =>
{
…
});
record struct GetRequest(
ToDoDbContext DbContext,
int Offset,
int Limit,
[FromHeader(Name = "X-TenantId")]
string? TenantId,
ILogger<GetRequest> Logger);
Здесь используется структура-запись (см. п.5 здесь), но классы также поддерживаются. Рекомендуется использовать структуру-запись, чтобы избежать дополнительного выделения памяти.
Кроме того, мы можем применять любые атрибуты привязки (FromHeader, FromQuery, FromServices и т. д.). В примере выше значение тенанта будет браться из заголовка запроса.
Источники:
- https://steven-giesel.com/blogPost/ee26a396-8f5a-40a7-9dd5-f08cd8c03518/organizing-parameters-in-minimal-api-with-the-asparametersattribute
- https://jaliyaudagedara.blogspot.com/2022/07/net-7-preview-5-using-asparameters.html
👍17
День 2018. #BlazorBasics
Основы Blazor. Привязка Данных
Привязка данных используется для синхронизации данных между пользовательским интерфейсом и моделью данных C#. Также она позволяет реагировать на события, которые запускает пользователь или браузер.
1. Одностороннее связывание — связывание свойств
Это самый базовый подход, используемый для вывода содержимого свойства или переменной C# на экран:
В этом компоненте в элемент <h1> выводятся данные с помощью символа @, за которым следует имя свойства или переменной. Всякий раз, когда содержимое свойства Title изменяется, UI автоматически обновляется, чтобы отразить новое значение.
Тот же подход можно использовать, чтобы предоставить значение свойства в качестве аргумента для параметра компонента.
Здесь мы отображаем дочерний компонент MyComponent, задавая его параметру Title значение параметра Name страницы MyPage.
2. Двусторонняя привязка
При использовании двусторонней привязки данных данные передаются из модели данных в UI и обратно:
В этом компоненте мы привязываем свойство Username к свойству value текстового поля. Синтаксис @bind — это сокращение. Каждый компонент имеет свойство по умолчанию, привязываемое при использовании сокращённого синтаксиса. Мы также могли бы использовать более длинную форму и явно привязаться к свойству value:
Заметьте, что важно использовать правильный синтаксис. Например, при использовании компонента InputText, Value нужно писать с заглавной буквы, потому что компонент имеет свойство Value:
Обычно используйте нижний регистр для стандартных HTML-элементов и заглавные буквы для свойств компонентов Blazor.
3. Привязка событий
В дополнение к односторонней и двусторонней привязке данных мы можем привязать метод C# к событию:
Здесь мы привязываем:
- свойство Model к параметру Model компонента EditForm,
- свойство Value компонента InputText к свойству Username класса User,
- метод Save к событию OnSubmit компонента EditForm.
Когда пользователь отправляет форму с помощью кнопки отправки, срабатывает событие OnSubmit и вызывается метод Save. В методе Save мы можем получить доступ к имени пользователя с помощью свойства Model и, например, сохранить его в базе данных.
Событие можно связать и с отдельным HTML-элементом:
Источник: https://www.telerik.com/blogs/blazor-basics-data-binding
Основы Blazor. Привязка Данных
Привязка данных используется для синхронизации данных между пользовательским интерфейсом и моделью данных C#. Также она позволяет реагировать на события, которые запускает пользователь или браузер.
1. Одностороннее связывание — связывание свойств
Это самый базовый подход, используемый для вывода содержимого свойства или переменной C# на экран:
<h1>@Title</h1>
@code {
public string Title { get; set; }
}
В этом компоненте в элемент <h1> выводятся данные с помощью символа @, за которым следует имя свойства или переменной. Всякий раз, когда содержимое свойства Title изменяется, UI автоматически обновляется, чтобы отразить новое значение.
Тот же подход можно использовать, чтобы предоставить значение свойства в качестве аргумента для параметра компонента.
@page "/MyPage"
<MyComponent Title="@Name" />
…
Здесь мы отображаем дочерний компонент MyComponent, задавая его параметру Title значение параметра Name страницы MyPage.
2. Двусторонняя привязка
При использовании двусторонней привязки данных данные передаются из модели данных в UI и обратно:
<input @bind="Username" />
@code {
public string Username { get; set; }
}
В этом компоненте мы привязываем свойство Username к свойству value текстового поля. Синтаксис @bind — это сокращение. Каждый компонент имеет свойство по умолчанию, привязываемое при использовании сокращённого синтаксиса. Мы также могли бы использовать более длинную форму и явно привязаться к свойству value:
<input @bind-value="Username" />
…
Заметьте, что важно использовать правильный синтаксис. Например, при использовании компонента InputText, Value нужно писать с заглавной буквы, потому что компонент имеет свойство Value:
<InputText @bind-Value="Username" />
Обычно используйте нижний регистр для стандартных HTML-элементов и заглавные буквы для свойств компонентов Blazor.
3. Привязка событий
В дополнение к односторонней и двусторонней привязке данных мы можем привязать метод C# к событию:
<h3>User</h3>
<EditForm Model="@Model" OnSubmit="@Save">
<InputText @bind-Value="Model.Username" />
<input type="submit" value="Submit" />
</EditForm>
@code {
public User Model { get; set; } = new User();
public void Save()
{
// Сохраняем пользователя
}
public class User
{
public string Username { get; set; }
}
}
Здесь мы привязываем:
- свойство Model к параметру Model компонента EditForm,
- свойство Value компонента InputText к свойству Username класса User,
- метод Save к событию OnSubmit компонента EditForm.
Когда пользователь отправляет форму с помощью кнопки отправки, срабатывает событие OnSubmit и вызывается метод Save. В методе Save мы можем получить доступ к имени пользователя с помощью свойства Model и, например, сохранить его в базе данных.
Событие можно связать и с отдельным HTML-элементом:
<input type="button" @onclick="Save" />
Источник: https://www.telerik.com/blogs/blazor-basics-data-binding
👍13
День 2019. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Начало
Один из способов усовершенствовать проект — несколько раз создать продукт целиком, улучшая его с каждым циклом. Но это непрактично. Другой способ — реализовывать решение постепенно, добавляя сначала сложные или малопонятные части и определяя, какие подходы дают наилучший результат. Также можно сначала построить операционную часть системы, чтобы пользователи могли работать с ней и оставлять отзывы, на основе которых можно делать последующие расширения.
Этот поэтапный подход помогает получить от пользователя информацию о чём-то осязаемом и скорректировать работу, чтобы лучше удовлетворить потребности клиентов. Вы можете обнаружить, что первоначальный проект соответствует первичной реализации продукта, но сдерживает его дальнейшее развитие. Можете обнаружить, что команда недостаточно хорошо продумала проект, торопясь выпустить рабочее ПО, и теперь назрела необходимость пересмотреть те решения. Устранение недостатков проектирования архитектуры и БД часто требует больших затрат. Поэтому поспешная реализация на первых нескольких итерациях и отсутствие тщательного изучения технических основ могут привести к болезненным последствиям для команды.
Общее у всех трёх стратегий - создание действующего ПО для оценки идей. Поэтому поэтапное совершенствование проектного решения протекает относительно медленно и обходится дорого. Вы можете переделывать созданное несколько раз, чтобы получить подходящий проект.
Альтернативный подход — выполнение итераций на более высоком уровне абстракции, чем ПО. Проектное моделирование обеспечивает менее дорогую альтернативу.
Как для выявления требований, так и для проектирования большое значение имеет рисование схем, представляющих различные аспекты системы, и их последующее поэтапное уточнение. Схему изменить проще, чем код.
Модели не отображают мельчайших деталей реального продукта, но помогают визуально представить, как эти части сочетаются друг с другом. Диаграммы, безусловно, могут стать запутанными, когда речь идёт о сложных системах. Но сам этот факт является сильным аргументом в пользу использования методов, помогающих понять концептуальную сложность и управлять ею.
Упрощение итераций
Моделирование позволяет быстро исследовать несколько вариантов и разработать лучший проект, чем тот, который можно создать с первой попытки. Имейте в виду, что вам не нужно создавать идеальные модели. Вы также не должны моделировать всю систему — достаточно смоделировать только особенно сложные или неопределённые части. Инструменты построения диаграмм упрощают итеративное улучшение, но при этом легко попасть в бесконечный цикл попыток усовершенствовать модель.
Визуальные модели — это средства коммуникации, способы представления знаний и обмена ими. Важно говорить на одном языке. Поэтому при моделировании требований или проекта используйте общепринятые обозначения. Предлагаемую системную архитектуру можно смоделировать в виде простой блок-схемы, но элементы проекта более низкого уровня требуют применения специализированных символов. Наиболее популярным выбором для ООП является унифицированный язык моделирования (Unified Modeling Language, UML). Чтобы исследовать, улучшать и документировать свои идеи, а также делиться ими с другими, используйте готовый стандарт, такой как UML, а не изобретайте собственные обозначения, которые могут быть непонятны другим. Моделирование не может полностью заменить создание прототипов, но любой метод, упрощающий просмотр и изменение проекта на высоком уровне абстракции, поможет вам создавать более качественные продукты.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Начало
Один из способов усовершенствовать проект — несколько раз создать продукт целиком, улучшая его с каждым циклом. Но это непрактично. Другой способ — реализовывать решение постепенно, добавляя сначала сложные или малопонятные части и определяя, какие подходы дают наилучший результат. Также можно сначала построить операционную часть системы, чтобы пользователи могли работать с ней и оставлять отзывы, на основе которых можно делать последующие расширения.
Этот поэтапный подход помогает получить от пользователя информацию о чём-то осязаемом и скорректировать работу, чтобы лучше удовлетворить потребности клиентов. Вы можете обнаружить, что первоначальный проект соответствует первичной реализации продукта, но сдерживает его дальнейшее развитие. Можете обнаружить, что команда недостаточно хорошо продумала проект, торопясь выпустить рабочее ПО, и теперь назрела необходимость пересмотреть те решения. Устранение недостатков проектирования архитектуры и БД часто требует больших затрат. Поэтому поспешная реализация на первых нескольких итерациях и отсутствие тщательного изучения технических основ могут привести к болезненным последствиям для команды.
Общее у всех трёх стратегий - создание действующего ПО для оценки идей. Поэтому поэтапное совершенствование проектного решения протекает относительно медленно и обходится дорого. Вы можете переделывать созданное несколько раз, чтобы получить подходящий проект.
Альтернативный подход — выполнение итераций на более высоком уровне абстракции, чем ПО. Проектное моделирование обеспечивает менее дорогую альтернативу.
Как для выявления требований, так и для проектирования большое значение имеет рисование схем, представляющих различные аспекты системы, и их последующее поэтапное уточнение. Схему изменить проще, чем код.
Модели не отображают мельчайших деталей реального продукта, но помогают визуально представить, как эти части сочетаются друг с другом. Диаграммы, безусловно, могут стать запутанными, когда речь идёт о сложных системах. Но сам этот факт является сильным аргументом в пользу использования методов, помогающих понять концептуальную сложность и управлять ею.
Упрощение итераций
Моделирование позволяет быстро исследовать несколько вариантов и разработать лучший проект, чем тот, который можно создать с первой попытки. Имейте в виду, что вам не нужно создавать идеальные модели. Вы также не должны моделировать всю систему — достаточно смоделировать только особенно сложные или неопределённые части. Инструменты построения диаграмм упрощают итеративное улучшение, но при этом легко попасть в бесконечный цикл попыток усовершенствовать модель.
Визуальные модели — это средства коммуникации, способы представления знаний и обмена ими. Важно говорить на одном языке. Поэтому при моделировании требований или проекта используйте общепринятые обозначения. Предлагаемую системную архитектуру можно смоделировать в виде простой блок-схемы, но элементы проекта более низкого уровня требуют применения специализированных символов. Наиболее популярным выбором для ООП является унифицированный язык моделирования (Unified Modeling Language, UML). Чтобы исследовать, улучшать и документировать свои идеи, а также делиться ими с другими, используйте готовый стандарт, такой как UML, а не изобретайте собственные обозначения, которые могут быть непонятны другим. Моделирование не может полностью заменить создание прототипов, но любой метод, упрощающий просмотр и изменение проекта на высоком уровне абстракции, поможет вам создавать более качественные продукты.
Окончание следует…
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍9
День 2020. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Окончание
Начало
Быстрые визуальные итерации
Пользовательские интерфейсы имеют два уровня проектирования: архитектурное и техническое. Просматривая экран UI, вы видите техническую часть с элементами визуального оформления, особенностями размещения текста, изображений, ссылок, полей ввода и элементов управления. Если нужна бОльшая точность, можно создать подробную структуру экрана или веб-страницы с помощью такого инструмента, как модель «отображение—действие—отклик» (Display-Action-Response, DAR). Однако поэтапное техническое проектирование UI требует изменения отдельных отображаемых элементов. Эти изменения могут быть утомительными, если не использовать эффективный инструмент создания экранов.
Архитектурное проектирование UI раскрывается через параметры навигации, представленные на каждом экране. Вы можете быстро уточнить проект архитектуры, нарисовав диалоговую карту (см. пример на рисунке выше). Она представляет архитектуру UI в форме диаграммы состояний или диаграммы переходов. Каждый экран, который система демонстрирует пользователю, — это отдельное состояние, в котором может находиться система. Прямоугольники на диаграмме переходов представляют собой диалоговые элементы, с помощью которых пользователь взаимодействует с системой (веб-страница, меню, диалоговое окно и т.п.). А стрелки указывают пути перехода от одного элемента к другому. Стрелки можно подписать, чтобы указать условия и/или действия, которые запускают переход.
Представление UI на данном уровне абстракции не позволяет людям отвлекаться на детали оформление каждого элемента диалога. Они могут сосредоточиться на общей картине того, как пользователь будет взаимодействовать с системой в рамках решения задачи, последовательно используя диалоговые элементы.
Также с помощью диалоговой карты можно выяснять, какая конкретная последовательность шагов позволяет добиться наибольшей эффективности при работе в создаваемой системе. При обсуждении в группе с заинтересованными сторонами вы можете рисовать и изменять на диалоговой карте прямоугольники и стрелки. По мере того, как участники будут критиковать схему и предлагать изменения, вы постепенно придёте к общему пониманию оптимального навигационного процесса. Также вы можете обнаружить некоторые ошибки и упущения в первоначальных замыслах.
Следующий уровень итераций улучшения дизайна UI — моделирование набора экранных форм, чтобы создать более или менее реалистичную раскадровку UI. Такие модели позволяют более точно имитировать взаимодействие с пользователем, переходя в потоке задач от одного такого снимка экрана к другому.
Сочетание быстрого проектного моделирования, имитации взаимодействия с пользователем и прототипирования потребует меньше усилий, чем реализация всего UI с последующим поэтапным его изменением до тех пор, пока ваши пользователи не останутся довольны.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 18. Чем выше уровень абстракции, тем проще выполнять итерации. Окончание
Начало
Быстрые визуальные итерации
Пользовательские интерфейсы имеют два уровня проектирования: архитектурное и техническое. Просматривая экран UI, вы видите техническую часть с элементами визуального оформления, особенностями размещения текста, изображений, ссылок, полей ввода и элементов управления. Если нужна бОльшая точность, можно создать подробную структуру экрана или веб-страницы с помощью такого инструмента, как модель «отображение—действие—отклик» (Display-Action-Response, DAR). Однако поэтапное техническое проектирование UI требует изменения отдельных отображаемых элементов. Эти изменения могут быть утомительными, если не использовать эффективный инструмент создания экранов.
Архитектурное проектирование UI раскрывается через параметры навигации, представленные на каждом экране. Вы можете быстро уточнить проект архитектуры, нарисовав диалоговую карту (см. пример на рисунке выше). Она представляет архитектуру UI в форме диаграммы состояний или диаграммы переходов. Каждый экран, который система демонстрирует пользователю, — это отдельное состояние, в котором может находиться система. Прямоугольники на диаграмме переходов представляют собой диалоговые элементы, с помощью которых пользователь взаимодействует с системой (веб-страница, меню, диалоговое окно и т.п.). А стрелки указывают пути перехода от одного элемента к другому. Стрелки можно подписать, чтобы указать условия и/или действия, которые запускают переход.
Представление UI на данном уровне абстракции не позволяет людям отвлекаться на детали оформление каждого элемента диалога. Они могут сосредоточиться на общей картине того, как пользователь будет взаимодействовать с системой в рамках решения задачи, последовательно используя диалоговые элементы.
Также с помощью диалоговой карты можно выяснять, какая конкретная последовательность шагов позволяет добиться наибольшей эффективности при работе в создаваемой системе. При обсуждении в группе с заинтересованными сторонами вы можете рисовать и изменять на диалоговой карте прямоугольники и стрелки. По мере того, как участники будут критиковать схему и предлагать изменения, вы постепенно придёте к общему пониманию оптимального навигационного процесса. Также вы можете обнаружить некоторые ошибки и упущения в первоначальных замыслах.
Следующий уровень итераций улучшения дизайна UI — моделирование набора экранных форм, чтобы создать более или менее реалистичную раскадровку UI. Такие модели позволяют более точно имитировать взаимодействие с пользователем, переходя в потоке задач от одного такого снимка экрана к другому.
Сочетание быстрого проектного моделирования, имитации взаимодействия с пользователем и прототипирования потребует меньше усилий, чем реализация всего UI с последующим поэтапным его изменением до тех пор, пока ваши пользователи не останутся довольны.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2021. #ЗаметкиНаПолях #EFCore
Полезные Функции EF Core. Начало
EF Core – мощный инструмент, и знание нескольких ключевых функций может сэкономить вам много времени и избежать разочарований.
1. Разделение запросов
Функция которая нужна редко, но, когда требуется, приходится очень кстати. Разделение запросов полезно в сценариях, где вы ждёте загрузки из нескольких коллекций. Она помогает избежать проблемы декартового взрыва.
Допустим, вы хотите получить отдел со всеми командами, сотрудниками и их задачами:
Это преобразуется в один SQL-запрос с несколькими JOIN. Предположим, что в отделе много команд, а в каждой команде много сотрудников. В этом случае БД возвращает много строк из каждого подзапроса, что значительно влияет на производительность. Вот как можно избежать этих проблем с производительностью с помощью разделения запросов:
При использовании AsSplitQuery EF Core будет выполнять дополнительный SQL-запрос для каждого навигационного свойства коллекции. Однако не злоупотребляйте разделением запросов. Измерьте производительность в каждом случае для вашего конкретного набора данных. Разделённые запросы делают больше обращений к БД, что может быть медленнее, если задержка базы данных высока. Также нет гарантии согласованности между несколькими SQL-запросами.
См. подробнее о разделённых запросах.
2. Массовые обновления и удаления
EF Core 7 добавил два новых API для выполнения массовых обновлений и удалений, ExecuteUpdate и ExecuteDelete. Они позволяют эффективно обновлять большое количество строк за один цикл обращения к БД.
Например, компания решила повысить зарплату на 5% всем сотрудникам в отделе «Продажи». Мы могли бы пройтись по всем сотрудникам и обновить их зарплату:
Этот подход приведёт к множеству запросов в БД, что может быть неэффективным, особенно для больших наборов данных. Мы можем добиться того же за один запрос, используя ExecuteUpdate:
Это приведёт к одному SQL-запросу UPDATE, и прямому изменению зарплаты в БД без загрузки сущностей в память.
Ещё пример. Допустим, платформа электронной коммерции хочет удалить все корзины покупок старше года:
Однако массовые обновления обходят трекер изменений EF. Это может быть проблемой, о чём см. подробнее.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
Полезные Функции EF Core. Начало
EF Core – мощный инструмент, и знание нескольких ключевых функций может сэкономить вам много времени и избежать разочарований.
1. Разделение запросов
Функция которая нужна редко, но, когда требуется, приходится очень кстати. Разделение запросов полезно в сценариях, где вы ждёте загрузки из нескольких коллекций. Она помогает избежать проблемы декартового взрыва.
Допустим, вы хотите получить отдел со всеми командами, сотрудниками и их задачами:
List<Department> departments =
context.Departments
.Include(d => d.Teams)
.ThenInclude(t => t.Employees)
.ThenInclude(e => e.Tasks)
.ToList();
Это преобразуется в один SQL-запрос с несколькими JOIN. Предположим, что в отделе много команд, а в каждой команде много сотрудников. В этом случае БД возвращает много строк из каждого подзапроса, что значительно влияет на производительность. Вот как можно избежать этих проблем с производительностью с помощью разделения запросов:
List<Department> departments =
context.Departments
.Include(d => d.Teams)
.ThenInclude(t => t.Employees)
.ThenInclude(e => e.Tasks)
.AsSplitQuery()
.ToList();
При использовании AsSplitQuery EF Core будет выполнять дополнительный SQL-запрос для каждого навигационного свойства коллекции. Однако не злоупотребляйте разделением запросов. Измерьте производительность в каждом случае для вашего конкретного набора данных. Разделённые запросы делают больше обращений к БД, что может быть медленнее, если задержка базы данных высока. Также нет гарантии согласованности между несколькими SQL-запросами.
См. подробнее о разделённых запросах.
2. Массовые обновления и удаления
EF Core 7 добавил два новых API для выполнения массовых обновлений и удалений, ExecuteUpdate и ExecuteDelete. Они позволяют эффективно обновлять большое количество строк за один цикл обращения к БД.
Например, компания решила повысить зарплату на 5% всем сотрудникам в отделе «Продажи». Мы могли бы пройтись по всем сотрудникам и обновить их зарплату:
var salesEmployees = context.Employees
.Where(e => e.Department == "Sales")
.ToList();
foreach (var employee in salesEmployees)
employee.Salary *= 1.05m;
context.SaveChanges();
Этот подход приведёт к множеству запросов в БД, что может быть неэффективным, особенно для больших наборов данных. Мы можем добиться того же за один запрос, используя ExecuteUpdate:
context.Employees
.Where(e => e.Department == "Sales")
.ExecuteUpdate(s =>
s.SetProperty(e => e.Salary, e => e.Salary * 1.05m));
Это приведёт к одному SQL-запросу UPDATE, и прямому изменению зарплаты в БД без загрузки сущностей в память.
Ещё пример. Допустим, платформа электронной коммерции хочет удалить все корзины покупок старше года:
context.Carts
.Where(o => o.CreatedOn < DateTime.Now.AddYears(-1))
.ExecuteDelete();
Однако массовые обновления обходят трекер изменений EF. Это может быть проблемой, о чём см. подробнее.
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
👍35
День 2022. #ЗаметкиНаПолях #EFCore
Полезные Функции EF Core. Окончание
Начало
3. Чистые SQL-запросы
EF Core 8 добавил новую функцию, которая позволяет нам запрашивать несопоставленные (unmapped) типы. Предположим, мы хотим получить данные из представления БД, хранимой процедуры или таблицы, которые напрямую не соответствуют ни одному из классов сущностей контекста БД. Например, получить сводные данные продаж продукта. В EF Core 8 можно определить простой класс и запросить его напрямую:
Метод SqlQuery возвращает IQueryable, что позволяет встраивать чистые SQL-запросы в LINQ-запросы.
Не забывайте использовать параметризованные запросы, чтобы предотвратить SQL-инъекции. Метод SqlQuery принимает FormattableString, что означает, что вы можете безопасно использовать интерполированную строку. Каждый аргумент преобразуется в параметр SQL.
См. подробнее.
4. Фильтры запросов
Фильтры автоматически добавляются в LINQ-запросы, когда вы извлекаете сущности соответствующего типа. Это избавляет от необходимости многократно писать одну и ту же логику фильтрации в нескольких местах.
Обычные сценарии:
- Мягкие удаления: отфильтровывают записи, помеченные как удалённые.
- Мультитенантность: фильтруют данные на основе текущего пользователя.
- Безопасность на уровне строк: ограничивают доступ к определённым записям на основе ролей или разрешений пользователей.
В мультитенантном приложении часто нужно фильтровать данные на основе текущего пользователя:
См. подробнее о мультитенантности с помощью EF Core.
Настройка нескольких фильтров запроса для одной сущности применит только последний. Можно использовать IgnoreQueryFilters, чтобы обойти фильтры в запросах, где это необходимо.
Источник: https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
Полезные Функции EF Core. Окончание
Начало
3. Чистые SQL-запросы
EF Core 8 добавил новую функцию, которая позволяет нам запрашивать несопоставленные (unmapped) типы. Предположим, мы хотим получить данные из представления БД, хранимой процедуры или таблицы, которые напрямую не соответствуют ни одному из классов сущностей контекста БД. Например, получить сводные данные продаж продукта. В EF Core 8 можно определить простой класс и запросить его напрямую:
public class ProductSummary
{
public int Id { get; set; }
public string Name { get; set; }
public decimal TotalSales { get; set; }
}
var sums = await context.Database
.SqlQuery<ProductSummary>(
@$"""
SELECT p.Id, p.Name,
SUM(oi.Quantity * oi.UnitPrice) AS TotalSales
FROM Products p
JOIN OrderItems oi ON p.ProductId = oi.ProductId
WHERE p.CategoryId = {categoryId}
GROUP BY p.Id, p.Name
""")
.ToListAsync();
Метод SqlQuery возвращает IQueryable, что позволяет встраивать чистые SQL-запросы в LINQ-запросы.
Не забывайте использовать параметризованные запросы, чтобы предотвратить SQL-инъекции. Метод SqlQuery принимает FormattableString, что означает, что вы можете безопасно использовать интерполированную строку. Каждый аргумент преобразуется в параметр SQL.
См. подробнее.
4. Фильтры запросов
Фильтры автоматически добавляются в LINQ-запросы, когда вы извлекаете сущности соответствующего типа. Это избавляет от необходимости многократно писать одну и ту же логику фильтрации в нескольких местах.
Обычные сценарии:
- Мягкие удаления: отфильтровывают записи, помеченные как удалённые.
- Мультитенантность: фильтруют данные на основе текущего пользователя.
- Безопасность на уровне строк: ограничивают доступ к определённым записям на основе ролей или разрешений пользователей.
В мультитенантном приложении часто нужно фильтровать данные на основе текущего пользователя:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
// Связываем продукты с пользователями
public int TenantId { get; set; }
}
protected override void
OnModelCreating(ModelBuilder mb)
{
// Текущий пользователь задаётся из контекста/запроса
mb.Entity<Product>()
.HasQueryFilter(p => p.TenantId == _currentTenantId);
}
// Запросы автоматически фильтруются по пользователю:
var products = context.Products.ToList();
См. подробнее о мультитенантности с помощью EF Core.
Настройка нескольких фильтров запроса для одной сущности применит только последний. Можно использовать IgnoreQueryFilters, чтобы обойти фильтры в запросах, где это необходимо.
Источник: https://www.milanjovanovic.tech/blog/5-ef-core-features-you-need-to-know
👍26
День 2023. #ЗаметкиНаПолях
Используем Memory для Эффективного Управления Памятью. Начало
В C# разработчики имеют доступ к мощному API Memory<T>, позволяющему гибко и эффективно работать с памятью.
Мы используем Span для безопасного предоставления непрерывной области памяти. Как и Span<T>, Memory<T> представляет собой непрерывную область памяти. Однако она может находиться в куче, а не только в стеке, как Span.
См. также «Различия Между Span и Memory в C#»
Создание Memory
В примере выше memory ссылается на данные в куче, т.к. массивы в C# размещаются в куче.
Memory<T> и асинхронные методы
Здесь мы имитируем асинхронную операцию с помощью Task.Delay(), а затем получаем доступ к данным в памяти с помощью memory.Span и выводим их на консоль. Это было бы невозможно с помощью Span<T>, поскольку мы не можем использовать Span<T> в асинхронном коде из-за того, что это ref struct.
Метод расширения AsMemory()
Метод расширения string.AsMemory() позволяет создавать объект Memory<char> из строки без копирования базовых данных. Это может быть полезно при передаче подстрок методам, которые принимают параметры Memory<T>, без дополнительных выделений памяти:
Мы создаем Memory<char> из строки с помощью AsMemory(), делаем срез этого Memory<char>, чтобы получить новый Memory<char>, представляющий часть исходной строки. И выводим этот срез на консоль, преобразуя его в строку с помощью метода ToString().
Далее рассмотрим продвинутые методы управления памятью.
Окончание следует…
Источник: https://code-maze.com/csharp-using-memory/
Используем Memory для Эффективного Управления Памятью. Начало
В C# разработчики имеют доступ к мощному API Memory<T>, позволяющему гибко и эффективно работать с памятью.
Мы используем Span для безопасного предоставления непрерывной области памяти. Как и Span<T>, Memory<T> представляет собой непрерывную область памяти. Однако она может находиться в куче, а не только в стеке, как Span.
См. также «Различия Между Span и Memory в C#»
Создание Memory
var numbers = new int[] { 1, 2, 3, 4, 5 };
var memory = new Memory<int>(numbers);
В примере выше memory ссылается на данные в куче, т.к. массивы в C# размещаются в куче.
Memory<T> и асинхронные методы
public static async Task
ProcessMemoryAsync(Memory<int> memory)
{
await Task.Delay(1000);
for (var i = 0; i < memory.Span.Length; i++)
{
var item = memory.Span[i];
Console.WriteLine(item);
}
}
Здесь мы имитируем асинхронную операцию с помощью Task.Delay(), а затем получаем доступ к данным в памяти с помощью memory.Span и выводим их на консоль. Это было бы невозможно с помощью Span<T>, поскольку мы не можем использовать Span<T> в асинхронном коде из-за того, что это ref struct.
Метод расширения AsMemory()
Метод расширения string.AsMemory() позволяет создавать объект Memory<char> из строки без копирования базовых данных. Это может быть полезно при передаче подстрок методам, которые принимают параметры Memory<T>, без дополнительных выделений памяти:
const string str = "Hello, World!";
var memory = str.AsMemory();
var slice = memory.Slice(7, 5);
Console.WriteLine(slice.ToString());
// World
Мы создаем Memory<char> из строки с помощью AsMemory(), делаем срез этого Memory<char>, чтобы получить новый Memory<char>, представляющий часть исходной строки. И выводим этот срез на консоль, преобразуя его в строку с помощью метода ToString().
Далее рассмотрим продвинутые методы управления памятью.
Окончание следует…
Источник: https://code-maze.com/csharp-using-memory/
👍15
День 2024. #ЗаметкиНаПолях
Используем Memory для Эффективного Управления Памятью. Окончание
Начало
Модели владения и интерфейс IMemoryOwner
Метод MemoryPool<T>.Rent() возвращает интерфейс IMemoryOwner<T>, который действует как владелец блока памяти. Общий пул позволяет сдавать блок памяти в аренду. Когда память больше не используется, владелец блока несёт ответственность за его утилизацию:
Мы арендуем блок памяти из общего пула с помощью MemoryPool<int>.Shared.Rent(). Он возвращает IMemoryOwner<int>, который владеет арендованным блоком. Затем извлекаем Memory<int>, который представляет этот блок памяти через свойство Memory. Мы используем этот блок для хранения некоторых данных, а затем отображаем эти данные.
Директива using отвечает за утилизацию блока и возвращение его в пул, делая его доступным для последующих вызовов Rent().
Реальный сценарий использования Memory<T> в C#
Создадим метод, который считывает данные из файла в арендованный блок памяти, обрабатывает данные, а затем возвращает память в пул:
Как и в предыдущем примере, мы арендуем блок памяти из пула. Затем используем его в качестве буфера для чтения данных из файла с помощью метода FileStream.ReadAsync(). После чтения и обработки данных выводим их на консоль.
Метод дольше по времени, чем обычное чтение файла (например, построчно), но экономит память, переиспользуя её из пула.
Итого
Благодаря возможности выделения памяти как в стеке, так и в куче, совместимости со строками и пулингу памяти, с помощью Memory<T> разработчики могут оптимизировать использование памяти, повысить производительность приложений и создавать более надёжные и масштабируемые программные решения.
Источник: https://code-maze.com/csharp-using-memory/
Используем Memory для Эффективного Управления Памятью. Окончание
Начало
Модели владения и интерфейс IMemoryOwner
Метод MemoryPool<T>.Rent() возвращает интерфейс IMemoryOwner<T>, который действует как владелец блока памяти. Общий пул позволяет сдавать блок памяти в аренду. Когда память больше не используется, владелец блока несёт ответственность за его утилизацию:
using IMemoryOwner<int> owner =
MemoryPool<int>.Shared.Rent(16);
var memory = owner.Memory;
for (var i = 0; i < memory.Length; i++)
memory.Span[i] = i;
foreach (var item in memory.Span)
Console.WriteLine(item);
Мы арендуем блок памяти из общего пула с помощью MemoryPool<int>.Shared.Rent(). Он возвращает IMemoryOwner<int>, который владеет арендованным блоком. Затем извлекаем Memory<int>, который представляет этот блок памяти через свойство Memory. Мы используем этот блок для хранения некоторых данных, а затем отображаем эти данные.
Директива using отвечает за утилизацию блока и возвращение его в пул, делая его доступным для последующих вызовов Rent().
Реальный сценарий использования Memory<T> в C#
Создадим метод, который считывает данные из файла в арендованный блок памяти, обрабатывает данные, а затем возвращает память в пул:
public static async Task
ProcessFileAsync(string path)
{
using var owner =
MemoryPool<byte>.Shared.Rent(4096);
var buffer = owner.Memory[..4096];
await using var stream = File.OpenRead(path);
int bytes;
while ((bytes = await stream.ReadAsync(buffer)) > 0)
{
var data = buffer[..bytes];
for (var i = 0; i < data.Span.Length; i++)
{
var b = data.Span[i];
Console.Write((char)b);
}
Console.WriteLine();
}
}
Как и в предыдущем примере, мы арендуем блок памяти из пула. Затем используем его в качестве буфера для чтения данных из файла с помощью метода FileStream.ReadAsync(). После чтения и обработки данных выводим их на консоль.
Метод дольше по времени, чем обычное чтение файла (например, построчно), но экономит память, переиспользуя её из пула.
Итого
Благодаря возможности выделения памяти как в стеке, так и в куче, совместимости со строками и пулингу памяти, с помощью Memory<T> разработчики могут оптимизировать использование памяти, повысить производительность приложений и создавать более надёжные и масштабируемые программные решения.
Источник: https://code-maze.com/csharp-using-memory/
👍10
День 2025. #BlazorBasics
Основы Blazor. Маршрутизация и Навигация. Начало
Маршрутизация — ключевая функция любого одностраничного приложения. Она позволяет разработчику организовать веб-сайт, а пользователю — перемещаться по разным страницам.
Когда мы используем директиву @page в компоненте Blazor, компилятор добавляет RouteAttribute во время компиляции, делая компонент доступным в качестве маршрута.
Маршрутизатор в Blazor
При создании проекта Blazor из шаблона в файле App.razor создаётся такой код:
Маршрутизатор работает одинаково как в приложениях Blazor Server, так и в WebAssembly. При запуске он считывает сборку приложения (из свойства AppAssembly) и ищет RouteAttribute, применённый к скомпилированным классам компонентов Blazor.
Если маршрут найден, используется компонент RouteView для визуализации представления, связанного с информацией о маршрутизации. Также устанавливается компонент макета по умолчанию - DefaultLayout (он используется для всех страниц, которые явно не указывают макет с помощью директивы @layout).
Если маршрут не найден, отображается простое сообщение об ошибке. Его можно изменить и сделать более дружелюбным к пользователю.
FocusOnNavigate позволяет поместить фокус на элементе HTML после завершения навигации. Можно использовать любой селектор CSS.
Важно: Маршрутизация в Blazor требует, чтобы мы установили тег <base> в разделе заголовка HTML. В шаблоне по умолчанию он устанавливается в файле _Host.cshtml. Измените это значение в зависимости от того, где и как вы размещаете своё приложение.
NavigationManager
NavigationManager позволяет нам переходить с одной страницы на другую в коде C#.
Мы внедрили NavigationManager на страницу при помощи директивы @inject и использовали его в метода NavigateTo, привязанном к элементам списка через событие @onclick. При навигации с использованием NavigationManager мы можем предоставить различные опции (NavigationOptions). Например, обойти клиентскую маршрутизацию и заставить браузер загрузить новую страницу с сервера с помощью свойства ForceLoad. Или заменить запись истории с помощью свойства ReplaceHistoryEntry.
Помимо инициирования навигации, мы также можем прослушивать событие LocationChanged для изменения маршрута и выполнять пользовательский код при навигации на определенную страницу.
Окончание следует…
Источник: https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
Основы Blazor. Маршрутизация и Навигация. Начало
Маршрутизация — ключевая функция любого одностраничного приложения. Она позволяет разработчику организовать веб-сайт, а пользователю — перемещаться по разным страницам.
Когда мы используем директиву @page в компоненте Blazor, компилятор добавляет RouteAttribute во время компиляции, делая компонент доступным в качестве маршрута.
Маршрутизатор в Blazor
При создании проекта Blazor из шаблона в файле App.razor создаётся такой код:
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Маршрутизатор работает одинаково как в приложениях Blazor Server, так и в WebAssembly. При запуске он считывает сборку приложения (из свойства AppAssembly) и ищет RouteAttribute, применённый к скомпилированным классам компонентов Blazor.
Если маршрут найден, используется компонент RouteView для визуализации представления, связанного с информацией о маршрутизации. Также устанавливается компонент макета по умолчанию - DefaultLayout (он используется для всех страниц, которые явно не указывают макет с помощью директивы @layout).
Если маршрут не найден, отображается простое сообщение об ошибке. Его можно изменить и сделать более дружелюбным к пользователю.
FocusOnNavigate позволяет поместить фокус на элементе HTML после завершения навигации. Можно использовать любой селектор CSS.
Важно: Маршрутизация в Blazor требует, чтобы мы установили тег <base> в разделе заголовка HTML. В шаблоне по умолчанию он устанавливается в файле _Host.cshtml. Измените это значение в зависимости от того, где и как вы размещаете своё приложение.
NavigationManager
NavigationManager позволяет нам переходить с одной страницы на другую в коде C#.
@page "/list"
@inject NavigationManager Nav
<ul>
<li @onclick="() => NavigateTo(1)">1</li>
<li @onclick="() => NavigateTo(2)">2</li>
</ul>
@code {
public void NavigateTo(int id)
{
Nav.NavigateTo($"item/{id}");
}
}
Мы внедрили NavigationManager на страницу при помощи директивы @inject и использовали его в метода NavigateTo, привязанном к элементам списка через событие @onclick. При навигации с использованием NavigationManager мы можем предоставить различные опции (NavigationOptions). Например, обойти клиентскую маршрутизацию и заставить браузер загрузить новую страницу с сервера с помощью свойства ForceLoad. Или заменить запись истории с помощью свойства ReplaceHistoryEntry.
Помимо инициирования навигации, мы также можем прослушивать событие LocationChanged для изменения маршрута и выполнять пользовательский код при навигации на определенную страницу.
Окончание следует…
Источник: https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
👍9
День 2026. #BlazorBasics
Основы Blazor. Маршрутизация и Навигация. Окончание
Начало
Маршруты
Маршруты страниц Blazor определяются аналогично маршрутам Razor Pages. Они могут включать параметры в фигурных скобках и ограничения параметров, указанные после двоеточия:
@page "/list"
@page "/item/{id}" – параметр id
@page "/item/{id:int}" – целочисленный параметр id
Cтроки запроса
Чтобы компонент страницы мог читать параметр из строки запроса, нужно использовать атрибуты Parameter и SupplyParameterFromQuery:
Когда мы вызываем URL
Компоненты NavLink и NavMenu
Компонент NavLink можно использовать вместо HTML-тега <a>. Он автоматически применяет класс CSS “active” в зависимости от того, соответствует ли его URL текущему URL.
Компонент NavMenu в шаблоне проекта Blazor содержит пример использования компонента NavLink. Вот его упрощённый код:
Поведение сопоставления маршрута с адресом ссылки настраивается с помощью перечисления NavLinkMatch:
- All - ссылка активна, если адрес полностью соответствует адресу текущего компонента.
- Prefix - ссылка активна, если адрес соответствует началу адреса текущего компонента (по умолчанию).
Также можно предоставить пользовательский класс CSS для активного NavLink с помощью свойства ActiveClass (по умолчанию — "active").
Индикация загрузки между переходами страниц
Иногда страницы могут загружаться в течение длительного времени. Мы можем использовать компонент Navigating в компоненте Router, чтобы показать пользовательскую разметку во время перехода между страницами:
Однако лучшим решением будет загружать данные на странице асинхронно, а переходы между страницами делать как можно более быстрыми.
Итого
Маршрутизация и навигация являются основополагающими при работе с одностраничными приложениями. Blazor обеспечивает простую, но мощную реализацию обоих из коробки. Не нужно выбирать один из нескольких вариантов маршрутизации; всё это включено в Blazor. Тем не менее, он предоставляет много места для пользовательского кода и является очень гибким и расширяемым. Он не только обрабатывает простые варианты использования, но и предоставляет API для сложных сценариев.
Источник: https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
Основы Blazor. Маршрутизация и Навигация. Окончание
Начало
Маршруты
Маршруты страниц Blazor определяются аналогично маршрутам Razor Pages. Они могут включать параметры в фигурных скобках и ограничения параметров, указанные после двоеточия:
@page "/list"
@page "/item/{id}" – параметр id
@page "/item/{id:int}" – целочисленный параметр id
Cтроки запроса
Чтобы компонент страницы мог читать параметр из строки запроса, нужно использовать атрибуты Parameter и SupplyParameterFromQuery:
@page "/search"
<h3>Результаты поиска</h3>
<p>Вы искали: @FirstName @LastName</p>
@code {
[Parameter]
[SupplyParameterFromQuery]
public string FirstName { get; set; }
[Parameter]
[SupplyParameterFromQuery]
public string LastName { get; set; }
}
Когда мы вызываем URL
/search?firstname=Jon&lastname=Smith
, Blazor назначает обоим свойствам соответствующие части строки запроса. Если вы хотите иметь другое имя для свойства, чем в URL, можно использовать свойство Name атрибута SupplyParameterFromQuery, чтобы определить имя параметра строки запроса.Компоненты NavLink и NavMenu
Компонент NavLink можно использовать вместо HTML-тега <a>. Он автоматически применяет класс CSS “active” в зависимости от того, соответствует ли его URL текущему URL.
Компонент NavMenu в шаблоне проекта Blazor содержит пример использования компонента NavLink. Вот его упрощённый код:
<nav>
<NavLink href="" Match="NavLinkMatch.All">
Home
</NavLink>
<NavLink href="counter">
Counter
</NavLink>
…
</nav>
Поведение сопоставления маршрута с адресом ссылки настраивается с помощью перечисления NavLinkMatch:
- All - ссылка активна, если адрес полностью соответствует адресу текущего компонента.
- Prefix - ссылка активна, если адрес соответствует началу адреса текущего компонента (по умолчанию).
Также можно предоставить пользовательский класс CSS для активного NavLink с помощью свойства ActiveClass (по умолчанию — "active").
Индикация загрузки между переходами страниц
Иногда страницы могут загружаться в течение длительного времени. Мы можем использовать компонент Navigating в компоненте Router, чтобы показать пользовательскую разметку во время перехода между страницами:
<Router AppAssembly="@typeof(App).Assembly">
…
<Navigating>
<p>Loading…</p>
</Navigating>
</Router>
Однако лучшим решением будет загружать данные на странице асинхронно, а переходы между страницами делать как можно более быстрыми.
Итого
Маршрутизация и навигация являются основополагающими при работе с одностраничными приложениями. Blazor обеспечивает простую, но мощную реализацию обоих из коробки. Не нужно выбирать один из нескольких вариантов маршрутизации; всё это включено в Blazor. Тем не менее, он предоставляет много места для пользовательского кода и является очень гибким и расширяемым. Он не только обрабатывает простые варианты использования, но и предоставляет API для сложных сценариев.
Источник: https://www.telerik.com/blogs/blazor-basics-blazor-routing-navigation-fundamentals
👍7
День 2027. #УрокиРазработки
Уроки 50 Лет Разработки ПО
Урок 19. Разрабатывайте продукты так, чтобы их легко было использовать правильно и трудно — неправильно
Хорошо продуманный UI делает продукт одновременно простым для правильного использования и трудным для неправильного. Подсказки и пункты меню чётко описывают действия, при этом используется терминология, понятная предполагаемым пользователям. Дизайнер предоставляет варианты возврата, позволяющие пользователю вернуться к предыдущему экрану или запустить выполнение задачи с самого начала, желательно без повторного ввода уже предоставленной информации. Ввод данных осуществляется в логической последовательности. Значения для ввода в каждое поле очевидно вытекают из оформления этих полей: с помощью раскрывающихся списков или из расположенных рядом текстовых инструкций. Помимо удобства использования, дизайнеры также должны учитывать возможность ошибок и стараться предотвращать или помогать исправлять их.
Есть 4 варианта обращения с потенциальными ошибками.
1. Не дать пользователю возможности ошибиться
Предотвращение ошибок — предпочтительная стратегия. Если пользователь должен вводить данные в изначально пустое текстовое поле, то оно предполагает возможность ввода произвольных данных, которые программа обязательно должна проверить. Раскрывающиеся списки (или другие элементы управления) с допустимыми вариантами позволяют ввести только допустимые значения. Не давайте возможности ввести недопустимые варианты. Элементы управления, позволяющие ввести несуществующую дату, вроде 30 февраля или выбрать год истечения карты меньше текущего, логически бессмысленны. Разрешение ввода неверных данных приведёт к ошибке, когда приложение попытается обработать информацию.
2. Затруднить пользователю возможность ошибиться
Если нельзя лишить пользователя возможности ошибиться, то хотя бы затрудните её. Добавьте поясняющий текст, чтобы исключить двусмысленность в понимании. Не заставляйте пользователя вводить одну и ту же информацию дважды, т.к. это удваивает вероятность ошибиться и занимает вдвое больше времени. Например, если форма запрашивает два адреса — доставки и выставления счета, — дайте возможность указать, что они совпадают, установив флажок.
3. Упростить исправление допущенной ошибки
Несмотря на все ваши усилия, ошибки (допущенные пользователем или системой во время работы) всё равно будут возникать. Предусмотрите для пользователя простую возможность исправлять их. Особенно полезны в этом отношении многоуровневая функция отмены/возврата и чёткие, содержательные сообщения, помогающие пользователю исправить любые ошибки. Загадочные числовые коды ошибок HTTP или сетевых сбоев могут помочь при технической диагностике, но бесполезны для обычного пользователя.
4. Просто позволить ошибкам случиться (не делайте так!)
Наименее желательный вариант — позволить ошибкам случиться и заставить пользователя самому разбираться с последствиями. Если пользователь просит запустить некую процедуру, имеющую определённые предварительные условия, которые должны быть выполнены, ПО должно проверять эти условия и помочь пользователю выполнить их, если это необходимо, а не просто продолжать работать в надежде на лучшее. Более того, оно вообще не должно запускать процедуру, если предварительные условия не выполнены. Система должна сама обнаруживать невыполненные предварительные условия и сообщать о них как можно раньше, чтобы не тратить время пользователя впустую. Пользователи ценят системы, которые им понятны, предотвращают или исправляют их ошибки и взаимодействуют с ними, услужливо предоставляя ясные подсказки.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
Уроки 50 Лет Разработки ПО
Урок 19. Разрабатывайте продукты так, чтобы их легко было использовать правильно и трудно — неправильно
Хорошо продуманный UI делает продукт одновременно простым для правильного использования и трудным для неправильного. Подсказки и пункты меню чётко описывают действия, при этом используется терминология, понятная предполагаемым пользователям. Дизайнер предоставляет варианты возврата, позволяющие пользователю вернуться к предыдущему экрану или запустить выполнение задачи с самого начала, желательно без повторного ввода уже предоставленной информации. Ввод данных осуществляется в логической последовательности. Значения для ввода в каждое поле очевидно вытекают из оформления этих полей: с помощью раскрывающихся списков или из расположенных рядом текстовых инструкций. Помимо удобства использования, дизайнеры также должны учитывать возможность ошибок и стараться предотвращать или помогать исправлять их.
Есть 4 варианта обращения с потенциальными ошибками.
1. Не дать пользователю возможности ошибиться
Предотвращение ошибок — предпочтительная стратегия. Если пользователь должен вводить данные в изначально пустое текстовое поле, то оно предполагает возможность ввода произвольных данных, которые программа обязательно должна проверить. Раскрывающиеся списки (или другие элементы управления) с допустимыми вариантами позволяют ввести только допустимые значения. Не давайте возможности ввести недопустимые варианты. Элементы управления, позволяющие ввести несуществующую дату, вроде 30 февраля или выбрать год истечения карты меньше текущего, логически бессмысленны. Разрешение ввода неверных данных приведёт к ошибке, когда приложение попытается обработать информацию.
2. Затруднить пользователю возможность ошибиться
Если нельзя лишить пользователя возможности ошибиться, то хотя бы затрудните её. Добавьте поясняющий текст, чтобы исключить двусмысленность в понимании. Не заставляйте пользователя вводить одну и ту же информацию дважды, т.к. это удваивает вероятность ошибиться и занимает вдвое больше времени. Например, если форма запрашивает два адреса — доставки и выставления счета, — дайте возможность указать, что они совпадают, установив флажок.
3. Упростить исправление допущенной ошибки
Несмотря на все ваши усилия, ошибки (допущенные пользователем или системой во время работы) всё равно будут возникать. Предусмотрите для пользователя простую возможность исправлять их. Особенно полезны в этом отношении многоуровневая функция отмены/возврата и чёткие, содержательные сообщения, помогающие пользователю исправить любые ошибки. Загадочные числовые коды ошибок HTTP или сетевых сбоев могут помочь при технической диагностике, но бесполезны для обычного пользователя.
4. Просто позволить ошибкам случиться (не делайте так!)
Наименее желательный вариант — позволить ошибкам случиться и заставить пользователя самому разбираться с последствиями. Если пользователь просит запустить некую процедуру, имеющую определённые предварительные условия, которые должны быть выполнены, ПО должно проверять эти условия и помочь пользователю выполнить их, если это необходимо, а не просто продолжать работать в надежде на лучшее. Более того, оно вообще не должно запускать процедуру, если предварительные условия не выполнены. Система должна сама обнаруживать невыполненные предварительные условия и сообщать о них как можно раньше, чтобы не тратить время пользователя впустую. Пользователи ценят системы, которые им понятны, предотвращают или исправляют их ошибки и взаимодействуют с ними, услужливо предоставляя ясные подсказки.
Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍13
День 2028. #ЧтоНовенького #CSharp13
Альтернативный Доступ к Коллекциям
До C#13 ref-структуры не могли быть аргументами параметра-типа в обобщённых типах. Теперь в объявление обобщённого типа после ключевого слова where добавлено «анти-ограничение» allows ref struct, которое сообщает, что аргументом для параметра типа может быть ref-структура. Анти-ограничение оно, потому что по факту оно не ограничивает возможный набор параметров типа, а расширяет его. Это анти-ограничение налагает все доступные для ref-структур правила (вроде запрета размещения в куче) на все экземпляры параметра-типа. Смысл этого изменения также в том, чтобы можно было использовать типы Span в обобщённых-алгоритмах.
Например, таблицы поиска (lookup-таблицы) типа Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве своего рода кэша. Однако, так как ключи таблиц обычно строкового типа, раньше приходилось выделять строку, чтобы обратиться в lookup-таблицу по ключу. Теперь упомянутое выше «анти-ограничение» добавляет новые возможности для этих типов коллекций.
Следующий код считает количество различных слов в тексте, используя так называемые альтернативные ключи словаря:
Сначала мы создаём lookup-словарь counts и указываем, что нам не важен регистр ключей.
Затем, чтобы искать в словаре по spanам, а не по строкам, мы создаём структуру lookup для альтернативного доступа к элементам. Здесь она вот такого длинного типа Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. В примере указан тип полностью для демонстрации. В реальном коде это можно заменить на var. Заметьте, что тут используется обобщённый тип AlternateLookup с параметром типа виде ref-структуры ReadOnlySpan<char>.
Далее мы разбиваем текст по «не-словам», используя Regex.EnumerateSplits. В результате получаем перечислитель диапазонов результатов. С его помощью мы делаем срезы исходного текста, которые представляют собой отдельные слова. Это переменная word типа ReadOnlySpan<char>. И вот здесь, чтобы не создавать строк для доступа в словарь counts по строковому ключу, нам и потребуется структура lookup, которая обеспечит доступ к словарю по альтернативному ключу типа ReadOnlySpan<char>. В данном случае мы увеличиваем счётчик для текущего слова.
Теперь мы можем взять текст и посчитать в нём слова:
Источник: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
Альтернативный Доступ к Коллекциям
До C#13 ref-структуры не могли быть аргументами параметра-типа в обобщённых типах. Теперь в объявление обобщённого типа после ключевого слова where добавлено «анти-ограничение» allows ref struct, которое сообщает, что аргументом для параметра типа может быть ref-структура. Анти-ограничение оно, потому что по факту оно не ограничивает возможный набор параметров типа, а расширяет его. Это анти-ограничение налагает все доступные для ref-структур правила (вроде запрета размещения в куче) на все экземпляры параметра-типа. Смысл этого изменения также в том, чтобы можно было использовать типы Span в обобщённых-алгоритмах.
Например, таблицы поиска (lookup-таблицы) типа Dictionary<TKey,TValue> и HashSet<T> часто используются в качестве своего рода кэша. Однако, так как ключи таблиц обычно строкового типа, раньше приходилось выделять строку, чтобы обратиться в lookup-таблицу по ключу. Теперь упомянутое выше «анти-ограничение» добавляет новые возможности для этих типов коллекций.
Следующий код считает количество различных слов в тексте, используя так называемые альтернативные ключи словаря:
static Dictionary<string, int>
CountWords(ReadOnlySpan<char> input)
{
Dictionary<string, int> counts =
new(StringComparer.OrdinalIgnoreCase);
Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>
lookup = counts.GetAlternateLookup<string, int, ReadOnlySpan<char>>();
foreach (Range r in Regex.EnumerateSplits(input, @"\b\W+\b"))
{
ReadOnlySpan<char> word = input[r];
lookup[word] = lookup
.TryGetValue(word, out int count) ? count + 1 : 1;
}
return counts;
}
Сначала мы создаём lookup-словарь counts и указываем, что нам не важен регистр ключей.
Затем, чтобы искать в словаре по spanам, а не по строкам, мы создаём структуру lookup для альтернативного доступа к элементам. Здесь она вот такого длинного типа Dictionary<string, int>.AlternateLookup<ReadOnlySpan<char>>. В примере указан тип полностью для демонстрации. В реальном коде это можно заменить на var. Заметьте, что тут используется обобщённый тип AlternateLookup с параметром типа виде ref-структуры ReadOnlySpan<char>.
Далее мы разбиваем текст по «не-словам», используя Regex.EnumerateSplits. В результате получаем перечислитель диапазонов результатов. С его помощью мы делаем срезы исходного текста, которые представляют собой отдельные слова. Это переменная word типа ReadOnlySpan<char>. И вот здесь, чтобы не создавать строк для доступа в словарь counts по строковому ключу, нам и потребуется структура lookup, которая обеспечит доступ к словарю по альтернативному ключу типа ReadOnlySpan<char>. В данном случае мы увеличиваем счётчик для текущего слова.
Теперь мы можем взять текст и посчитать в нём слова:
var text = "<какой-то длинный текст>";
foreach (var word in CountWords(text))
Console.WriteLine(word);
Источник: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13
👍20