День сто девяносто первый. #ЗаметкиНаПолях
Многопоточность
8. В чем разница между асинхронным программированием и многопоточностью?
Эти понятия часто путают. Многие думают, что многопоточность и асинхронность - это одно и то же, но это не так. Обычно помогает аналогия. Вы готовите в ресторане. Приходит заказ на яйца и тосты.
- Синхронно: вы готовите яйца, затем вы готовите тост.
- Асинхронно, однопоточно: вы начинаете готовить яйца и устанавливаете таймер. Вы начинаете готовить тосты и устанавливаете таймер. Пока они готовятся, вы убираетесь на кухне. Когда таймеры выключаются, вы снимаете яйца с огня и тосты из тостера и подаете их.
- Асинхронно, многопоточно: вы нанимаете еще двух поваров, один для приготовления яиц и один для приготовления тостов. Теперь у вас есть проблема координации поваров, чтобы они не конфликтовали друг с другом на кухне при совместном использовании ресурсов. И вы должны заплатить им.
Итак, значит ли это, что многопоточность - это только один вид асинхронности? Потоки - это рабочие; асинхронность - это задачи. В многопоточных рабочих процессах вы назначаете задачи работникам. В асинхронных однопоточных рабочих процессах у вас есть график задач, где некоторые задачи зависят от результатов других; По завершении каждой задачи вызывается код, который планирует следующую задачу, которая может быть запущена, с учетом результатов только что выполненной задачи. Но вам (надеюсь) нужен только один работник для выполнения всех задач, а не один работник на задачу.
Это помогает понять, что многие задачи не связаны с процессором. Для задач, связанных с процессором, имеет смысл нанять столько рабочих (потоков), сколько имеется процессоров, назначить по одной задаче каждому рабочему, назначить по одному процессору каждому рабочему, и каждый процессор будет выполняет только работу по вычислению результата как можно быстрее. Но для задач, которые не ждут обработки процессором, вам вообще не нужно назначать работника. Вы просто ждёте сообщения о том, что результат доступен, и делаете что-то еще, пока ждёте. Когда это сообщение прибудет, вы можете запланировать продолжение выполненной задачи в качестве следующего пункта в вашем списке дел.
Источник: https://stackoverflow.com/questions/34680985/what-is-the-difference-between-asynchronous-programming-and-multithreading
Многопоточность
8. В чем разница между асинхронным программированием и многопоточностью?
Эти понятия часто путают. Многие думают, что многопоточность и асинхронность - это одно и то же, но это не так. Обычно помогает аналогия. Вы готовите в ресторане. Приходит заказ на яйца и тосты.
- Синхронно: вы готовите яйца, затем вы готовите тост.
- Асинхронно, однопоточно: вы начинаете готовить яйца и устанавливаете таймер. Вы начинаете готовить тосты и устанавливаете таймер. Пока они готовятся, вы убираетесь на кухне. Когда таймеры выключаются, вы снимаете яйца с огня и тосты из тостера и подаете их.
- Асинхронно, многопоточно: вы нанимаете еще двух поваров, один для приготовления яиц и один для приготовления тостов. Теперь у вас есть проблема координации поваров, чтобы они не конфликтовали друг с другом на кухне при совместном использовании ресурсов. И вы должны заплатить им.
Итак, значит ли это, что многопоточность - это только один вид асинхронности? Потоки - это рабочие; асинхронность - это задачи. В многопоточных рабочих процессах вы назначаете задачи работникам. В асинхронных однопоточных рабочих процессах у вас есть график задач, где некоторые задачи зависят от результатов других; По завершении каждой задачи вызывается код, который планирует следующую задачу, которая может быть запущена, с учетом результатов только что выполненной задачи. Но вам (надеюсь) нужен только один работник для выполнения всех задач, а не один работник на задачу.
Это помогает понять, что многие задачи не связаны с процессором. Для задач, связанных с процессором, имеет смысл нанять столько рабочих (потоков), сколько имеется процессоров, назначить по одной задаче каждому рабочему, назначить по одному процессору каждому рабочему, и каждый процессор будет выполняет только работу по вычислению результата как можно быстрее. Но для задач, которые не ждут обработки процессором, вам вообще не нужно назначать работника. Вы просто ждёте сообщения о том, что результат доступен, и делаете что-то еще, пока ждёте. Когда это сообщение прибудет, вы можете запланировать продолжение выполненной задачи в качестве следующего пункта в вашем списке дел.
Источник: https://stackoverflow.com/questions/34680985/what-is-the-difference-between-asynchronous-programming-and-multithreading
День сто девяносто второй. #ЗаметкиНаПолях
Заметки по обобщённым типам
1. Инициализация и состояния обобщённых типов
Если выполнить следующий код:
1. Значение
2. Статический конструктор запускается дважды: один раз для каждого закрытого составного типа. Если бы статического конструктора не было, было бы меньше гарантий того, когда точно происходит инициализация каждого типа. Но можно однозначно сказать, что вы можете рассматривать
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
Заметки по обобщённым типам
1. Инициализация и состояния обобщённых типов
Если выполнить следующий код:
Console.WriteLine(typeof(List<int>));На экран выведется следующее:
Console.WriteLine(typeof(List<string>));
System.Collections.Generic.List`1[System.String]List`1 означает обобщённый тип List арности 1 (один параметр типа), а аргументы параметра типа указаны в квадратных скобках. Таким образом,
System.Collections.Generic.List`1[System.Int32]
List<int>
и List<string>
- это фактически разные типы, которые созданы из одного и того же определения обобщённого типа List<T>
. Они называются закрытыми составными типами (closed, constructed type). Это относится не только к тому, как типы используются, но и к тому, как они инициализируются и как обрабатываются статические поля. Каждый закрытый составной тип инициализируется отдельно и имеет собственный независимый набор статических полей. Следующий листинг демонстрирует это с помощью простого обобщённого счетчика.class GenericCounter<T>…
{
private static int value;
static GenericCounter()
{
Console.WriteLine("Инициализация счётчика для {0}", typeof(T));
}
public static void Increment()
{
value++;
}
public static void Display()
{
Console.WriteLine("Счётчик для {0}: {1}", typeof(T), value);
}
}
GenericCounter<string>.Increment();Вывод будет следующим:
GenericCounter<string>.Increment();
GenericCounter<string>.Display();
GenericCounter<int>.Display();
GenericCounter<int>.Increment();
GenericCounter<int>.Display();
Инициализация счётчика для System.StringИз этого можно сделать два вывода:
Счётчик для System.String: 2
Инициализация счётчика для System.Int32
Счётчик для System.Int32: 0
Счётчик для System.Int32: 1
1. Значение
GenericCounter<string>
не зависит от GenericCounter <int>
. 2. Статический конструктор запускается дважды: один раз для каждого закрытого составного типа. Если бы статического конструктора не было, было бы меньше гарантий того, когда точно происходит инициализация каждого типа. Но можно однозначно сказать, что вы можете рассматривать
GenericCounter<string>
и GenericCounter<int>
как независимые типы.Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
👍1
День сто девяносто третий. #ЗаметкиНаПолях
Заметки по обобщённым типам
2. Вывод типа из аргументов методов
Рассмотрим следующий код:
Хотя вывод типов применяется только к методам, его можно использовать для более простого создания экземпляров обобщённых типов. Например, рассмотрим семейство типов
Они выглядят бессмысленно тривиальными, но позволяют использовать вывод типа. То есть, вместо этого:
Разработчики языка постоянно работают над усовершенствованием процесса вывода типа, и порой логика вывода становится достаточно сложной из-за наследований, перегрузок и необязательных параметров. Поэтому надо понимать, что иногда на практике выведение типа может приводить к неожиданным результатам. Например, если вам нужен
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
Заметки по обобщённым типам
2. Вывод типа из аргументов методов
Рассмотрим следующий код:
public static List<T> CopyAtMost<T>(List<T> input, int elements)…
{
return input.Take(elements).ToList();
}
var numbers = new List<int>() { 1, 2, 3, 4 };Вам нужен аргумент типа для вызова
var firstTwo = CopyAtMost<int>(numbers, 2);
CopyAtMost
, потому что метод имеет параметр типа. Но вам не нужно указывать этот тип аргумента явно. Можно переписать этот код следующим образом:var firstTwo = CopyAtMost(numbers, 2);Это точно такой же вызов метода с точки зрения IL, который сгенерирует компилятор. Но аргумент типа int указывать не нужно, компилятор сделал это для вас на основе аргумента для первого параметра метода. Вы используете аргумент
List<int>
в качестве значения параметра типа List<T>
, поэтому T
должно быть int
. Вывод типа может использовать только аргументы, передаваемые методу, но не тип результата.Хотя вывод типов применяется только к методам, его можно использовать для более простого создания экземпляров обобщённых типов. Например, рассмотрим семейство типов
Tuple
, состоящее из необобщённого статического класса Tuple
и нескольких обобщённых классов: Tuple<T1>
, Tuple<T1, T2>
, Tuple<T1, T2, T3>
и так далее (до 8). Статический класс имеет набор перегруженных фабричных методов Create
:public static Tuple<T1> Create<T1>(T1 item1)и так далее…
{
return new Tuple<T1>(item1);
}
public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
{
return new Tuple<T1, T2>(item1, item2);
}
Они выглядят бессмысленно тривиальными, но позволяют использовать вывод типа. То есть, вместо этого:
new Tuple<int, string, int>(10, "x", 20)Можно написать:
Tuple.Create(10, "x", 20)Это мощная техника, о которой полезно знать; как правило, её просто реализовать и она может сделать работу с обобщённым кодом намного приятнее.
Разработчики языка постоянно работают над усовершенствованием процесса вывода типа, и порой логика вывода становится достаточно сложной из-за наследований, перегрузок и необязательных параметров. Поэтому надо понимать, что иногда на практике выведение типа может приводить к неожиданным результатам. Например, если вам нужен
Tuple<int, object, int>
, то из предыдущего вызова Tuple.Create(10, "x", 20)
вы его не получите. В этом случае можно использовать либо new Tuple<int, object, int>(10, "x", 20)
, либо приведение типа Tuple.Create(10, (object)"x", 20)
. Аналогично с null: Tuple.Create(null, 50)
завершится неудачей, но Tuple.Create((string) null, 50)
сработает.Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
День сто девяносто пятый. #ИнтересныйКод
Ленивый итератор с помощью yield return
Следующий код показывает метод
Можно избежать создания списка, печатая значение в цикле, но это делает метод Fibonacci() еще более тесно связанным с тем, что вы хотите сделать со значениями прямо сейчас. Что если вы хотите складывать значения, а не печатать их? Писать другой метод? Это ужасное нарушение разделения ответственности.
Решение через итератор - это именно то, что вам нужно: представление бесконечной последовательности и всё. Вызывающий код может выполнять итерацию по своему усмотрению (по крайней мере, до переполнения
Реализация последовательности Фибоначчи вручную проста. Не нужно сохранять много данных состояния (только два предыдущих числа), а управлять логикой выполнения довольно просто (здесь только один оператор
Кроме того, компилятор также достаточно умён, чтобы правильно обрабатывать блоки
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
Ленивый итератор с помощью yield return
Следующий код показывает метод
Fibonacci()
, возвращающий бесконечную последовательность чисел Фибоначчи, и использование этого метода для вывода чисел до нужного предела:static IEnumerable<int> Fibonacci()Как реализовать что-то подобное без итераторов? Можно изменить метод для создания
{
int current = 0;
int next = 1;
// бесконечный цикл? Только если продолжать запрашивать значения
while (true)
{
yield return current;
int oldCurrent = current;
current = next;
next += oldCurrent;
}
}
static void Main()
{
foreach (var value in Fibonacci())
{
Console.WriteLine(value);
// условие остановки цикла
if (value > 1000) { break; }
}
}
List<int>
и заполнять его, пока не достигнут предел. Но этот список может быть большим, если предел велик, и почему метод, который знает детали последовательности Фибоначчи, также должен знать, когда остановиться? Предположим, иногда нужно остановиться по значению текущего числа, а иногда по количеству выведенных чисел или через некоторое время. Можно избежать создания списка, печатая значение в цикле, но это делает метод Fibonacci() еще более тесно связанным с тем, что вы хотите сделать со значениями прямо сейчас. Что если вы хотите складывать значения, а не печатать их? Писать другой метод? Это ужасное нарушение разделения ответственности.
Решение через итератор - это именно то, что вам нужно: представление бесконечной последовательности и всё. Вызывающий код может выполнять итерацию по своему усмотрению (по крайней мере, до переполнения
int
) и использовать значения, как хочет.Реализация последовательности Фибоначчи вручную проста. Не нужно сохранять много данных состояния (только два предыдущих числа), а управлять логикой выполнения довольно просто (здесь только один оператор
yield return
). Но если логика усложняется, реализация её в коде становится крайне непростой. Таким образом, использование итераторов с yield return
– это мощный инструмент, сильно упрощающий разработку.Кроме того, компилятор также достаточно умён, чтобы правильно обрабатывать блоки
finally
, что не так очевидно, как кажется (об этом далее).Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
День сто девяносто шестой. #ЗаметкиНаПолях
Обработка блоков finally в итераторах
Рассмотрим следующий код:
- Если считать выполнение прерываемым на каждом вызове
- Если считать, что на самом деле на каждом
Выполним этот код:
На самом деле, и да, и нет. Если реализовать итератор вручную и вызвать
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
Обработка блоков finally в итераторах
Рассмотрим следующий код:
static IEnumerable<string> Iterator()Когда будет выполнен блок
{
try
{
Console.WriteLine("Перед первым yield");
yield return "первый";
Console.WriteLine("После первого yield");
yield return "второй";
Console.WriteLine("После второго yield");
}
finally
{
Console.WriteLine("Внутри finally");
}
}
finally
: - Если считать выполнение прерываемым на каждом вызове
yield return
, тогда логически они внутри блока try
, и нет надобности выполнять блок finally
каждый раз.- Если считать, что на самом деле на каждом
yield return
вызывается метод MoveNext()
итератора, то можно решить, что происходит выход из try
, и тогда finally
должен выполняться.Выполним этот код:
foreach (string value in Iterator())Вывод:
{
Console.WriteLine("Значение: {0}", value);
}
Перед первым yieldТаким образом, блок
Значение: первый
После первого yield
Значение: второй
После второго yield
Внутри finally
finally
выполнится только после окончания итерации. Это подходит под концепцию ленивого выполнения. Пока ничего сложного. Но что если мы прервём итерацию после первого значения? Выполнится ли блок finally
?На самом деле, и да, и нет. Если реализовать итератор вручную и вызвать
MoveNext()
один раз, то блок finally
действительно никогда не будет выполнен. Однако, если использовать итератор внутри цикла foreach
, как это чаще всего происходит, то компилятор использует скрытый блок using
вокруг цикла. При выходе из блока using
происходит вызов метода Dispose()
итератора, и, соответственно, вызов всех блоков finally
. Таким образом, результатом выполнения следующего кода:foreach (string value in Iterator())будет:
{
Console.WriteLine("Значение: {0}", value);
break;
}
Перед первым yieldЭто важно при итерации по объектам, которые требуют уничтожения, таким как обработчики файлов, для предотвращения утечки ресурсов. То есть, если вы в цикле итератора читаете все строки файла, то, даже если вы прерываете цикл на середине, либо в середине цикла возникнет ошибка, файл в любом случае будет корректно закрыт.
Значение: первый
Внутри finally
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
День сто девяносто седьмой. #оффтоп
Иногда мне кажется, что самых рукожопых разработчиков мелкомягкие не увольняют, а отправляют в ссылку на проект скайпа.
Иногда мне кажется, что самых рукожопых разработчиков мелкомягкие не увольняют, а отправляют в ссылку на проект скайпа.
День сто девяносто восьмой. #ЗаметкиНаПолях
Частичные методы
В C# 3 добавлена дополнительный функционал для частичных классов - частичные методы. Это методы, объявленные без тела в одной части, а затем опционально реализованные в другой части. Частичные методы неявно являются
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
Частичные методы
В C# 3 добавлена дополнительный функционал для частичных классов - частичные методы. Это методы, объявленные без тела в одной части, а затем опционально реализованные в другой части. Частичные методы неявно являются
private
, должны возвращать void и не иметь out
параметров (можно использовать параметры ref
). Во время компиляции сохраняются только частичные методы, имеющие реализации; если частичный метод не был реализован, он и все его вызовы удаляются. Это звучит странно, но позволяет автоматически сгенерированному коду предоставлять точки перехвата (hooks) для добавления дополнительной логики в коде, написанном вручную. Это может быть реально полезно. В примере ниже объявлены два частичных метода: partial class PartialMethodsDemoВо втором файле частичного класса один из методов реализован, а другой – нет:
{
public PartialMethodsDemo()
{
OnConstruction();
}
public override string ToString()
{
string ret = "Original return value";
CustomizeToString(ref ret);
return ret;
}
partial void OnConstruction();
partial void CustomizeToString(ref string text);
}
partial class PartialMethodsDemoВ листинге первая часть кода, скорее всего, будет сгенерирована автоматически, объявляет два метода, позволяющие обеспечить дополнительное поведение в конструкторе и при получении строкового представления объекта. Вторая часть соответствует написанному вручную коду, который не требует дополнительной логики в конструкторе, но хочет изменить строковое представление, возвращаемое
{
partial void CustomizeToString(ref string text)
{
text += " - customized!";
}
}
ToString()
. Несмотря на то, что метод CustomizeToString
не может возвращать значение напрямую, он может передавать информацию вызывающему его методу через ref
параметр. Поскольку OnConstruction
не реализован, он и вызовы его удаляются компилятором.Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
This media is not supported in your browser
VIEW IN TELEGRAM
День сто девяносто девятый. #ЧтоНовенького
Поиск по коду в панели быстрого поиска
В Visual Studio 2019 версии 16.3, которая доступна для предварительного скачивания и должна выйти в конце сентября, добавлена возможность поиска по коду через панель быстрого поиска (Ctrl + Q). Теперь можно искать классы и члены классов в коде C# и VB. Результаты будут отображаться при вводе запроса, а также на отдельной вкладке Code.
Также возможен camel-case поиск. Это позволяет вводить только заглавные буквы имени класса или члена вместо полного имени.
Источник: https://devblogs.microsoft.com/visualstudio/code-recent-items-and-template-search-in-visual-studio/
Поиск по коду в панели быстрого поиска
В Visual Studio 2019 версии 16.3, которая доступна для предварительного скачивания и должна выйти в конце сентября, добавлена возможность поиска по коду через панель быстрого поиска (Ctrl + Q). Теперь можно искать классы и члены классов в коде C# и VB. Результаты будут отображаться при вводе запроса, а также на отдельной вкладке Code.
Также возможен camel-case поиск. Это позволяет вводить только заглавные буквы имени класса или члена вместо полного имени.
Источник: https://devblogs.microsoft.com/visualstudio/code-recent-items-and-template-search-in-visual-studio/
День двухсотый. #ЗаметкиНаПолях
Псевдонимы пространств имён
Псевдонимы пространств имён используются, когда вам нужно обратиться к типам с одинаковым именем из разных пространств имён. Следующий пример показывает, как один метод может обратиться к классам Button из Windows Forms и ASP.NET Web Forms.
Несмотря на то, что типы крайне редко объявляются в глобальном пространстве имён, его можно использовать как «корневое» пространство имён. В C#2 введён псевдоним
Внешние псевдонимы
В крайнем случае, когда два типа с одинаковыми именами находятся в пространствах имен с одинаковыми именами, но предоставлены в разных сборках, можно воспользоваться внешними псевдонимами, используя ключевые слова
Псевдонимы пространств имён
Псевдонимы пространств имён используются, когда вам нужно обратиться к типам с одинаковым именем из разных пространств имён. Следующий пример показывает, как один метод может обратиться к классам Button из Windows Forms и ASP.NET Web Forms.
using System;Это хорошо работает до тех пор, пока не появляется пространство имён с именем
using WinForms = System.Windows.Forms;
using WebForms = System.Web.UI.WebControls;
class Test
{
static void Main()
{
Console.WriteLine(typeof(WinForms.Button));
Console.WriteLine(typeof(WebForms.Button));
}
}
WinForms
. Чтобы различать названия пространств имён и их псевдонимы в C#2 введены квалификаторы псевдонимов пространств имён, представляющие из себя просто пару двоеточий. Таким образом, код выше можно переписать так:static void Main()Псевдоним глобального пространства имён
{
Console.WriteLine(typeof(WinForms::Button));
Console.WriteLine(typeof(WebForms::Button));
}
Несмотря на то, что типы крайне редко объявляются в глобальном пространстве имён, его можно использовать как «корневое» пространство имён. В C#2 введён псевдоним
global
, обозначающий глобальное пространство имён. Например, при использовании типа DateTime
, если он объявлен во нескольких пространствах имён, то обратиться к системному типу можно, использовав следующую конструкцию global::System.DateTime
. Использовать псевдоним глобального пространства имён полезно, например, при написании автоматических генераторов кода, когда есть большая вероятность коллизий сгенерированных названий пространств имён и типов.Внешние псевдонимы
В крайнем случае, когда два типа с одинаковыми именами находятся в пространствах имен с одинаковыми именами, но предоставлены в разных сборках, можно воспользоваться внешними псевдонимами, используя ключевые слова
extern alias
:extern alias GridV1;Связать файл сборки с именем внешнего псевдонима можно либо в файле конфигурации, либо указав параметр
extern alias GridV2;
using GridV1::Grid;
using GridV2::Grid;
/r
компилятору в командной строке:/r:GridV1=grid.dllИсточник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.
/r:GridV2=grid20.dll
День двести первый. #BestPractices
Советы по разработке типов
1. Выбор между классом и структурой
Хорошее понимание различий в поведении ссылочных и значимых типов имеет решающее значение при принятии решения в этом вопросе:
- Инициализация и уничтожение значимых типов обычно дешевле.
- Инициализация и уничтожение массивов значимых типов намного дешевле.
- При приведении значимого типа к ссылочному или к интерфейсному типу и обратно выполняется упаковка и распаковка. Это отрицательно влияет на кучу, скорость сборки мусора и общую производительность.
- Ссылочные типы копируются по ссылке, а значимые по значению, т.е. присвоение большого объекта ссылочного типа дешевле, чем значимого.
- Ссылочные типы передаются по ссылке, то есть изменение экземпляра ссылочного типа изменяет все переменные, которые ссылаются на него. Когда же изменяется переменная значимого типа, это не влияет на копии элемента. Поэтому во избежание путаницы структуры делают неизменяемыми.
Большинство типов должны быть классами (ссылочными типами). Однако есть ситуации, когда характеристики значимого типа подойдут для использования структуры (struct).
⚠️ РАССМОТРИТЕ определение объекта как структуры, а не класса, если экземпляры типа небольшие и живут недолго, либо включены в другие объекты.
✅ ИСПОЛЬЗУЙТЕ структуры, когда тип обладает всеми следующими характеристиками:
- Логически представляет из себя единое значение.
- Размер экземпляра типа меньше 16 байт.
- Он неизменяем.
- Его не придётся часто упаковывать.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
1. Выбор между классом и структурой
Хорошее понимание различий в поведении ссылочных и значимых типов имеет решающее значение при принятии решения в этом вопросе:
- Инициализация и уничтожение значимых типов обычно дешевле.
- Инициализация и уничтожение массивов значимых типов намного дешевле.
- При приведении значимого типа к ссылочному или к интерфейсному типу и обратно выполняется упаковка и распаковка. Это отрицательно влияет на кучу, скорость сборки мусора и общую производительность.
- Ссылочные типы копируются по ссылке, а значимые по значению, т.е. присвоение большого объекта ссылочного типа дешевле, чем значимого.
- Ссылочные типы передаются по ссылке, то есть изменение экземпляра ссылочного типа изменяет все переменные, которые ссылаются на него. Когда же изменяется переменная значимого типа, это не влияет на копии элемента. Поэтому во избежание путаницы структуры делают неизменяемыми.
Большинство типов должны быть классами (ссылочными типами). Однако есть ситуации, когда характеристики значимого типа подойдут для использования структуры (struct).
⚠️ РАССМОТРИТЕ определение объекта как структуры, а не класса, если экземпляры типа небольшие и живут недолго, либо включены в другие объекты.
✅ ИСПОЛЬЗУЙТЕ структуры, когда тип обладает всеми следующими характеристиками:
- Логически представляет из себя единое значение.
- Размер экземпляра типа меньше 16 байт.
- Он неизменяем.
- Его не придётся часто упаковывать.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести второй. #BestPractices
Советы по разработке типов
2. Разработка абстрактных классов
❌ ИЗБЕГАЙТЕ определения открытых (
✅ ИСПОЛЬЗУЙТЕ защищённые (
✅ ИСПОЛЬЗУЙТЕ хотя бы один конкретный тип, наследующий от каждого абстрактного класса. Это помогает проверить дизайн абстрактного класса. Например,
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
2. Разработка абстрактных классов
❌ ИЗБЕГАЙТЕ определения открытых (
public
) или защищённых внутренних (protected internal
) конструкторов в абстрактных типах. Конструкторы должны быть открытыми, только если пользователям нужно будет создавать экземпляры типа. Поскольку вы не можете создавать экземпляры абстрактного типа, абстрактный тип с открытым конструктором неправильно спроектирован и вводит пользователей класса в заблуждение.✅ ИСПОЛЬЗУЙТЕ защищённые (
protected
) или внутренние (internal
) конструкторы в абстрактных классах. Защищенный конструктор является более распространенным и позволяет базовому классу выполнять собственную инициализацию при создании подтипов. Внутренний конструктор может использоваться для ограничения конкретных реализаций абстрактного класса только сборкой, определяющей класс.✅ ИСПОЛЬЗУЙТЕ хотя бы один конкретный тип, наследующий от каждого абстрактного класса. Это помогает проверить дизайн абстрактного класса. Например,
System.IO.FileStream
является реализацией абстрактного класса System.IO.Stream
.Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести третий. #ЗаметкиНаПолях
Захват переменных лямбда-выражениями
Внутри лямбда-выражения вы можете использовать любую переменную, которую вы могли бы использовать в обычном коде в данном месте. Это может быть статическое поле, поле экземпляра (если вы пишете лямбда-выражение внутри экземплярного метода), переменная this, параметры метода или локальные переменные. Все они называются захваченными переменными, потому что они объявленны вне непосредственного контекста лямбда-выражения. Для сравнения параметры лямбда-выражения или локальные переменные, объявленные внутри лямбда-выражения не являются захваченными переменными. В следующем примере показан захват различных переменных лямбда-выражением:
-
захватывается лямбда-выражением.
-
-
-
-
-
Важно понимать, что лямбда-выражение захватывает сами переменные, а не значения переменных в момент создания делегата. Если вы изменили какую-либо из захваченных переменных между временем создания делегата и его вызовом, лямбда-выражение «увидит» эти изменения. Аналогично, лямбда-выражение может изменить значение захваченных переменных. Более того, в этом случае каждый последующий вызов лямбда-выражения будет «видеть» изменения захваченных переменных, сделанные предыдущими вызовами.
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 3.
Захват переменных лямбда-выражениями
Внутри лямбда-выражения вы можете использовать любую переменную, которую вы могли бы использовать в обычном коде в данном месте. Это может быть статическое поле, поле экземпляра (если вы пишете лямбда-выражение внутри экземплярного метода), переменная this, параметры метода или локальные переменные. Все они называются захваченными переменными, потому что они объявленны вне непосредственного контекста лямбда-выражения. Для сравнения параметры лямбда-выражения или локальные переменные, объявленные внутри лямбда-выражения не являются захваченными переменными. В следующем примере показан захват различных переменных лямбда-выражением:
class CapturedVariablesDemo…
{
private string instanceField = "instance field";
public Action<string> CreateAction(string methodParameter)
{
string methodLocal = "method local";
string uncaptured = "uncaptured local";
Action<string> action = lambdaParameter =>
{
string lambdaLocal = "lambda local";
Console.WriteLine("Instance field: {0}", instanceField);
Console.WriteLine("Method parameter: {0}", methodParameter);
Console.WriteLine("Method local: {0}", methodLocal);
Console.WriteLine("Lambda parameter: {0}", lambdaParameter);
Console.WriteLine("Lambda local: {0}", lambdaLocal);
};
methodLocal = "modified method local";
return action;
}
}
var demo = new CapturedVariablesDemo();Вывод:
Action<string> action = demo.CreateAction("method argument");
action("lambda argument");
Instance field: instance fieldВ этом примере задействованы следующие переменные:
Method parameter: method argument
Method local: modified method local
Lambda parameter: lambda argument
Lambda local: lambda local
-
instanceField
- поле экземпляра класса CapturedVariablesDemo
,захватывается лямбда-выражением.
-
methodParameter
- параметр метода CreateAction
, захватывается лямбда-выражением.-
methodLocal
- локальная переменная метода CreateAction
, захватывается и изменяется лямбда-выражением.-
uncaptured
- локальная переменная метода CreateAction
, не используется лямбда-выражением, поэтому не захватывается им.-
lambdaParameter
- параметр лямбда-выражения, не является захваченной переменной.-
lambdaLocal
- локальная переменная в лямбда-выражении, не является захваченной переменной.Важно понимать, что лямбда-выражение захватывает сами переменные, а не значения переменных в момент создания делегата. Если вы изменили какую-либо из захваченных переменных между временем создания делегата и его вызовом, лямбда-выражение «увидит» эти изменения. Аналогично, лямбда-выражение может изменить значение захваченных переменных. Более того, в этом случае каждый последующий вызов лямбда-выражения будет «видеть» изменения захваченных переменных, сделанные предыдущими вызовами.
Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 3.
День двести четвёртый. #BestPractices
Советы по разработке типов
3. Разработка Статических Классов
Статический класс содержит только статические члены (кроме экземплярных членов, унаследованных от
Статические классы - это компромисс между чисто объектно-ориентированным дизайном и простотой. Они обычно используются для предоставления ярлыков для других операций (например,
✅ ИСПОЛЬЗУЙТЕ статические классы в исключительных случаях. Статические классы должны использоваться только как вспомогательные классы для объектно-ориентированного ядра платформы.
❌ ИЗБЕГАЙТЕ использования статических классов для хранения разнородного функционала.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
3. Разработка Статических Классов
Статический класс содержит только статические члены (кроме экземплярных членов, унаследованных от
System.Object
и, возможно, приватного конструктора). Некоторые языки предоставляют встроенную поддержку статических классов. В C# статический класс неявно закрытый (sealed), абстрактный (abstract), и никакие экземплярные члены не могут быть переопределены или объявлены.Статические классы - это компромисс между чисто объектно-ориентированным дизайном и простотой. Они обычно используются для предоставления ярлыков для других операций (например,
System.IO.File
), хранителем методов расширения или функциональных возможностей, для которых реализация полностью объектно-ориентированной оболочки невозможно (например, System.Environment
).✅ ИСПОЛЬЗУЙТЕ статические классы в исключительных случаях. Статические классы должны использоваться только как вспомогательные классы для объектно-ориентированного ядра платформы.
❌ ИЗБЕГАЙТЕ использования статических классов для хранения разнородного функционала.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
This media is not supported in your browser
VIEW IN TELEGRAM
День двести пятый. #юмор
Модульное (блочное или юнит-) тестирование — процесс в программировании, позволяющий проверить на корректность отдельные модули программы. При интеграционном тестировании отдельные программные модули объединяются и тестируются в группе. В примере выше модульное тестирование завершилось успешно, а при интеграционном возникли проблемы.
Модульное (блочное или юнит-) тестирование — процесс в программировании, позволяющий проверить на корректность отдельные модули программы. При интеграционном тестировании отдельные программные модули объединяются и тестируются в группе. В примере выше модульное тестирование завершилось успешно, а при интеграционном возникли проблемы.
День двести шестой. #BestPractices
Советы по разработке типов
4. Разработка интерфейсов
Хотя большинство API лучше всего моделировать с использованием классов и структур, в некоторых случаях интерфейсы являются более подходящим или даже единственным вариантом:
- Реализация эффекта множественного наследования. CLR не поддерживает множественное наследование, но позволяет типам реализовывать один или несколько интерфейсов в дополнение к наследованию от базового класса. Например, IDisposable - это интерфейс, который позволяет типам поддерживать освобождение ресурсов независимо от их собственной иерархии наследования.
- Создание общего интерфейса, который может поддерживаться несколькими типами, включая некоторые значимые типы. Значимые типы не могут наследоваться, но могут реализовывать интерфейсы, поэтому использование интерфейса является единственным вариантом для обеспечения общего поведения.
✅ ИСПОЛЬЗУЙТЕ интерфейс, если вам нужен общий API, который будет поддерживаться набором типов, включая значимые типы.
⚠️ РАССМОТРИТЕ использование интерфейса, если вам необходимо поддерживать функциональность для типов, которые уже наследуются от какого-либо другого типа.
❌ ИЗБЕГАЙТЕ использования маркерных интерфейсов (интерфейсов без членов). Если вам нужно пометить класс как имеющий определенную характеристику (маркер), используйте пользовательский атрибут, а не интерфейс.
✅ ИСПОЛЬЗУЙТЕ хотя бы один тип, реализующий интерфейс. Это помогает проверить реализацию интерфейса. Например, List<T> является реализацией интерфейса IList<T>.
✅ ИСПОЛЬЗУЙТЕ хотя бы один API, использующий каждый определенный вами интерфейс (метод, принимающий интерфейс в качестве параметра или свойство интерфейсного типа). Это помогает проверить реализацию интерфейса. Например, List<T>.Sort использует интерфейс System.Collections.Generic.IComparer<T>.
❌ ИЗБЕГАЙТЕ добавления членов в интерфейс, выпущенный в предыдущей версии, если вы поставляете библиотеку. Это нарушит все реализации этого интерфейса. Вы должны создать новый интерфейс, чтобы избежать проблем при переходе от одной версии к другой.
ЗАМЕЧАНИЕ: Начиная с C#8 в таких случаях для интерфейсных методов можно использовать реализацию по умолчанию.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
4. Разработка интерфейсов
Хотя большинство API лучше всего моделировать с использованием классов и структур, в некоторых случаях интерфейсы являются более подходящим или даже единственным вариантом:
- Реализация эффекта множественного наследования. CLR не поддерживает множественное наследование, но позволяет типам реализовывать один или несколько интерфейсов в дополнение к наследованию от базового класса. Например, IDisposable - это интерфейс, который позволяет типам поддерживать освобождение ресурсов независимо от их собственной иерархии наследования.
- Создание общего интерфейса, который может поддерживаться несколькими типами, включая некоторые значимые типы. Значимые типы не могут наследоваться, но могут реализовывать интерфейсы, поэтому использование интерфейса является единственным вариантом для обеспечения общего поведения.
✅ ИСПОЛЬЗУЙТЕ интерфейс, если вам нужен общий API, который будет поддерживаться набором типов, включая значимые типы.
⚠️ РАССМОТРИТЕ использование интерфейса, если вам необходимо поддерживать функциональность для типов, которые уже наследуются от какого-либо другого типа.
❌ ИЗБЕГАЙТЕ использования маркерных интерфейсов (интерфейсов без членов). Если вам нужно пометить класс как имеющий определенную характеристику (маркер), используйте пользовательский атрибут, а не интерфейс.
✅ ИСПОЛЬЗУЙТЕ хотя бы один тип, реализующий интерфейс. Это помогает проверить реализацию интерфейса. Например, List<T> является реализацией интерфейса IList<T>.
✅ ИСПОЛЬЗУЙТЕ хотя бы один API, использующий каждый определенный вами интерфейс (метод, принимающий интерфейс в качестве параметра или свойство интерфейсного типа). Это помогает проверить реализацию интерфейса. Например, List<T>.Sort использует интерфейс System.Collections.Generic.IComparer<T>.
❌ ИЗБЕГАЙТЕ добавления членов в интерфейс, выпущенный в предыдущей версии, если вы поставляете библиотеку. Это нарушит все реализации этого интерфейса. Вы должны создать новый интерфейс, чтобы избежать проблем при переходе от одной версии к другой.
ЗАМЕЧАНИЕ: Начиная с C#8 в таких случаях для интерфейсных методов можно использовать реализацию по умолчанию.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Docs
Framework Design Guidelines
See framework design guidelines for designing libraries that extend and interact with .NET, to ensure API consistency and ease of use.
День двести седьмой. #BestPractices
Советы по разработке типов
5. Разработка структур
Значимый тип общего назначения чаще всего называют структурой (ключевое слово struct).
❌ ИЗБЕГАЙТЕ конструкторов без параметров для структур. Это позволяет создавать массивы структур без необходимости запуска конструктора для каждого элемента массива. C# не позволяет структурам иметь конструкторы без параметров.
❌ ИЗБЕГАЙТЕ изменяемых структур. С изменяемыми структурами возникает несколько проблем. Например, когда аксессор get свойства возвращает структуру, вызывающая сторона получает копию. Поскольку копия создаётся неявно, разработчики могут не знать, что они изменяют копию, а не исходное значение. Кроме того, некоторые языки (в частности, динамические языки) имеют проблемы с использованием изменяемых значимых типов, потому что даже локальные переменные, при разыменовании, приводят к созданию копии.
⚠️ УБЕДИТЕСЬ, что состояние, в котором все данные экземпляра установлены на ноль, ложь или null (в зависимости от ситуации), является допустимым. Это предотвращает случайное создание недопустимых экземпляров при создании массива структур.
✅ ИСПОЛЬЗУЙТЕ реализацию IEquatable<T> для структур. Метод Object.Equals для значимых типов приводит к упаковке, и его реализация по умолчанию не очень эффективна, поскольку использует отражение. Метод Equals может иметь гораздо лучшую производительность и может быть реализован так, чтобы это не приводило к упаковке.
❌ ИЗБЕГАЙТЕ явного наследования от ValueType. На самом деле, большинство языков запрещают это.
В общем случае структуры могут быть очень полезны, но их следует использовать только для небольших, единичных неизменяемых значений, которые не будут часто упаковываться.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
5. Разработка структур
Значимый тип общего назначения чаще всего называют структурой (ключевое слово struct).
❌ ИЗБЕГАЙТЕ конструкторов без параметров для структур. Это позволяет создавать массивы структур без необходимости запуска конструктора для каждого элемента массива. C# не позволяет структурам иметь конструкторы без параметров.
❌ ИЗБЕГАЙТЕ изменяемых структур. С изменяемыми структурами возникает несколько проблем. Например, когда аксессор get свойства возвращает структуру, вызывающая сторона получает копию. Поскольку копия создаётся неявно, разработчики могут не знать, что они изменяют копию, а не исходное значение. Кроме того, некоторые языки (в частности, динамические языки) имеют проблемы с использованием изменяемых значимых типов, потому что даже локальные переменные, при разыменовании, приводят к созданию копии.
⚠️ УБЕДИТЕСЬ, что состояние, в котором все данные экземпляра установлены на ноль, ложь или null (в зависимости от ситуации), является допустимым. Это предотвращает случайное создание недопустимых экземпляров при создании массива структур.
✅ ИСПОЛЬЗУЙТЕ реализацию IEquatable<T> для структур. Метод Object.Equals для значимых типов приводит к упаковке, и его реализация по умолчанию не очень эффективна, поскольку использует отражение. Метод Equals может иметь гораздо лучшую производительность и может быть реализован так, чтобы это не приводило к упаковке.
❌ ИЗБЕГАЙТЕ явного наследования от ValueType. На самом деле, большинство языков запрещают это.
В общем случае структуры могут быть очень полезны, но их следует использовать только для небольших, единичных неизменяемых значений, которые не будут часто упаковываться.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Docs
Framework Design Guidelines
See framework design guidelines for designing libraries that extend and interact with .NET, to ensure API consistency and ease of use.
День двести восьмой. #BestPractices
Советы по разработке типов
6. Разработка перечислений. Начало
Перечисления - это особый значимый тип.
Виды перечислений:
- Простые - небольшие замкнутые наборы вариантов (например, набор цветов).
- Битовые флаги - предназначены для поддержки побитовых операций над значениями (например, список опций).
✅ ИСПОЛЬЗУЙТЕ существительные в единственном числе для простых перечислений и во множественном для битовых флагов.
❌ ИЗБЕГАЙТЕ наследования от
✅ ИСПОЛЬЗУЙТЕ перечисление для строгого ввода параметров, свойств и возвращаемых значений, представляющих наборы значений.
✅ ИСПОЛЬЗУЙТЕ перечисление вместо статических констант.
❌ ИЗБЕГАЙТЕ использования перечислений для открытых наборов (таких как версии операционной системы, имена ваших друзей и т.д.).
❌ ИЗБЕГАЙТЕ создания зарезервированных значений перечисления, которые предназначены для будущего использования. Вы всегда можете просто добавить значения к существующему перечислению на более позднем этапе. Зарезервированные значения просто загрязняют набор реальных значений и приводят к ошибкам при использовании.
❌ ИЗБЕГАЙТЕ создания перечисления только с одним значением. Обычной практикой обеспечения будущей расширяемости API является добавление зарезервированных параметров в сигнатуры методов. Такие зарезервированные параметры могут быть выражены в виде перечисления с одним значением по умолчанию. Это неверная практика для управляемых API. Перегрузка метода позволяет добавлять параметры в будущих выпусках.
❌ ИЗБЕГАЙТЕ включения сервисных значений в перечисления. Несмотря на то, что они иногда полезны для разработчиков фреймворка, сервисные значения сбивают с толку его пользователей. Они используются для отслеживания состояния перечисления, а не являются одним из значений из набора, представляемого перечислением.
✅ ИСПОЛЬЗУЙТЕ нулевое значение для простых перечислений. Вы можете назвать это значение «None» («Значение отсутствует»). Если такое значение не подходит для данного конкретного перечисления, наиболее распространенному значению по умолчанию для перечисления должно быть присвоено базовое значение ноль.
⚠️ РАССМОТРИТЕ использование Int32 (по умолчанию в большинстве языков) в качестве базового типа перечисления, если не выполняется одно из следующих условий:
- Перечисление представляет собой битовые флаги, и у вас более 32 флагов или вы ожидаете, что их будет больше в будущем.
- Базовый тип отличается от Int32 для облегчения взаимодействия с неуправляемым кодом, ожидающим перечисления разных размеров.
- Меньший по размеру базовый тип приведёт к значительной экономии места. Если вы ожидаете, что перечисление будет использоваться главным образом в качестве аргумента в логике кода, размер не имеет большого значения. Экономия по размеру может быть значительной, если ожидается, что:
- перечисление будет использоваться как поле в очень часто создаваемой структуре или классе.
- пользователи будут создавать большие массивы или коллекции экземпляров перечисления.
- большое количество экземпляров перечисления будет сериализовываться.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
6. Разработка перечислений. Начало
Перечисления - это особый значимый тип.
Виды перечислений:
- Простые - небольшие замкнутые наборы вариантов (например, набор цветов).
- Битовые флаги - предназначены для поддержки побитовых операций над значениями (например, список опций).
✅ ИСПОЛЬЗУЙТЕ существительные в единственном числе для простых перечислений и во множественном для битовых флагов.
❌ ИЗБЕГАЙТЕ наследования от
System.Enum
напрямую. System.Enum
- это специальный тип, используемый CLR для создания пользовательских перечислений. Большинство языков программирования предоставляют специальный элемент, который дает вам доступ к этой функциональности. В C# для определения перечисления используется ключевое слово enum
.✅ ИСПОЛЬЗУЙТЕ перечисление для строгого ввода параметров, свойств и возвращаемых значений, представляющих наборы значений.
✅ ИСПОЛЬЗУЙТЕ перечисление вместо статических констант.
❌ ИЗБЕГАЙТЕ использования перечислений для открытых наборов (таких как версии операционной системы, имена ваших друзей и т.д.).
❌ ИЗБЕГАЙТЕ создания зарезервированных значений перечисления, которые предназначены для будущего использования. Вы всегда можете просто добавить значения к существующему перечислению на более позднем этапе. Зарезервированные значения просто загрязняют набор реальных значений и приводят к ошибкам при использовании.
❌ ИЗБЕГАЙТЕ создания перечисления только с одним значением. Обычной практикой обеспечения будущей расширяемости API является добавление зарезервированных параметров в сигнатуры методов. Такие зарезервированные параметры могут быть выражены в виде перечисления с одним значением по умолчанию. Это неверная практика для управляемых API. Перегрузка метода позволяет добавлять параметры в будущих выпусках.
❌ ИЗБЕГАЙТЕ включения сервисных значений в перечисления. Несмотря на то, что они иногда полезны для разработчиков фреймворка, сервисные значения сбивают с толку его пользователей. Они используются для отслеживания состояния перечисления, а не являются одним из значений из набора, представляемого перечислением.
✅ ИСПОЛЬЗУЙТЕ нулевое значение для простых перечислений. Вы можете назвать это значение «None» («Значение отсутствует»). Если такое значение не подходит для данного конкретного перечисления, наиболее распространенному значению по умолчанию для перечисления должно быть присвоено базовое значение ноль.
⚠️ РАССМОТРИТЕ использование Int32 (по умолчанию в большинстве языков) в качестве базового типа перечисления, если не выполняется одно из следующих условий:
- Перечисление представляет собой битовые флаги, и у вас более 32 флагов или вы ожидаете, что их будет больше в будущем.
- Базовый тип отличается от Int32 для облегчения взаимодействия с неуправляемым кодом, ожидающим перечисления разных размеров.
- Меньший по размеру базовый тип приведёт к значительной экономии места. Если вы ожидаете, что перечисление будет использоваться главным образом в качестве аргумента в логике кода, размер не имеет большого значения. Экономия по размеру может быть значительной, если ожидается, что:
- перечисление будет использоваться как поле в очень часто создаваемой структуре или классе.
- пользователи будут создавать большие массивы или коллекции экземпляров перечисления.
- большое количество экземпляров перечисления будет сериализовываться.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести девятый. #BestPractices
Советы по разработке типов
6. Разработка перечислений. Окончание
Разработка битовых флагов
✅ ИСПОЛЬЗУЙТЕ атрибут
✅ ИСПОЛЬЗУЙТЕ степени двойки для значений битовых флагов, чтобы их можно было свободно комбинировать с помощью операции побитового ИЛИ.
⚠️ РАССМОТРИТЕ предоставление специальных значений для часто используемых комбинаций флагов. Побитовые операции не должны требоваться для простых задач. Например:
❌ ИЗБЕГАЙТЕ создания битовых флагов, где определенные комбинации значений недопустимы.
✅ ИСПОЛЬЗУЙТЕ название
❌ ИЗБЕГАЙТЕ использования нулевого значения битовых флагов, если только оно не означает «все флаги сняты» и не названо соответствующим образом.
Добавление значения к перечислению
Очень часто обнаруживается, что вам нужно добавить значения в перечисление после того, как оно уже выпущено в одной из версий программы. Существует потенциальная проблема совместимости приложений при возвращении нового значения из существующего API, поскольку плохо написанные приложения могут неправильно обрабатывать новое значение.
⚠️ РАССМОТРИТЕ добавление значений к перечислениям, несмотря на небольшой риск несовместимости.
Если у вас есть реальные данные о несовместимости приложений, вызванной добавлением значений в перечисление, рассмотрите возможность добавления нового API, который возвращает и новые, и старые значения, и пометьте старый API как устаревший, который должен продолжать возвращать только старые значения. Это обеспечит совместимость существующих приложений.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
6. Разработка перечислений. Окончание
Разработка битовых флагов
✅ ИСПОЛЬЗУЙТЕ атрибут
System.FlagsAttribute
для пометки битовых флагов. Не применяйте этот атрибут к простым перечислениям.✅ ИСПОЛЬЗУЙТЕ степени двойки для значений битовых флагов, чтобы их можно было свободно комбинировать с помощью операции побитового ИЛИ.
⚠️ РАССМОТРИТЕ предоставление специальных значений для часто используемых комбинаций флагов. Побитовые операции не должны требоваться для простых задач. Например:
ReadWrite
(комбинация значений Read
и Write
).❌ ИЗБЕГАЙТЕ создания битовых флагов, где определенные комбинации значений недопустимы.
✅ ИСПОЛЬЗУЙТЕ название
None
для нулевого значения битовых флагов. В этом случае оно всегда должно означать «все флаги сняты».❌ ИЗБЕГАЙТЕ использования нулевого значения битовых флагов, если только оно не означает «все флаги сняты» и не названо соответствующим образом.
Добавление значения к перечислению
Очень часто обнаруживается, что вам нужно добавить значения в перечисление после того, как оно уже выпущено в одной из версий программы. Существует потенциальная проблема совместимости приложений при возвращении нового значения из существующего API, поскольку плохо написанные приложения могут неправильно обрабатывать новое значение.
⚠️ РАССМОТРИТЕ добавление значений к перечислениям, несмотря на небольшой риск несовместимости.
Если у вас есть реальные данные о несовместимости приложений, вызванной добавлением значений в перечисление, рассмотрите возможность добавления нового API, который возвращает и новые, и старые значения, и пометьте старый API как устаревший, который должен продолжать возвращать только старые значения. Это обеспечит совместимость существующих приложений.
Продолжение следует…
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
День двести десятый. #BestPractices
Советы по разработке типов
7. Вложенные Типы
Вложенный тип - это тип, определенный в области действия другого типа, который называется включающим типом. Вложенный тип имеет доступ ко всем членам своего включающего типа. Например, он имеет доступ к закрытым (private) полям, определенным во включающем типе, и к защищенным (protected) полям, определенным во всех потомках включающего типа.
Как правило, вложенные типы следует использовать с осторожностью. На это есть несколько причин. Некоторые разработчики не полностью знакомы с концепцией. Они могут, например, иметь проблемы с синтаксисом объявления переменных вложенных типов. Вложенные типы также очень тесно связаны со своими включающими типами и сами по себе не подходят для типов общего назначения.
Вложенные типы лучше всего подходят для моделирования деталей реализации включающих их типов. Конечный пользователь редко должен объявлять переменные вложенного типа и почти никогда не должен явно создавать экземпляры вложенных типов. Например, перечислитель коллекции (enumerator) может быть вложенным типом этой коллекции. Перечислители обычно создаются включающим типом, и, поскольку многие языки поддерживают оператор foreach, переменные перечислителя редко должны объявляться конечным пользователем.
✅ ИСПОЛЬЗУЙТЕ вложенные типы, когда связь между вложенным типом и его внешним типом такова, что желательно иметь доступ к членам типа.
❌ ИЗБЕГАЙТЕ использования открытых (public) вложенных типов для логической группировки типов; используйте для этого пространства имен.
❌ ИЗБЕГАЙТЕ открытых вложенных типов в публичных API. Единственное исключение - если переменные вложенного типа необходимо объявлять только в редких случаях, например, при создании подклассов или при расширенной настройке.
❌ ИЗБЕГАЙТЕ использования вложенных типов, если на тип, скорее всего, будут ссылаться извне включающим его типа. Например, перечисление, передаваемое методу, определенному в классе, не должно определяться как вложенный тип класса.
❌ ИЗБЕГАЙТЕ использования вложенных типов, если они должны создаваться клиентским кодом. Если тип имеет открытый конструктор, он, скорее всего, не должен быть вложенным. Если тип может быть создан, значит он должен быть самостоятельным (вы можете создавать его, работать с ним и уничтожать его, не используя включающий тип). Внутренние типы не должны широко использоваться за пределами внешнего типа вне контекста внешнего типа.
❌ ИЗБЕГАЙТЕ определения вложенного типа как члена интерфейса. Многие языки не поддерживают такую конструкцию.
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Советы по разработке типов
7. Вложенные Типы
Вложенный тип - это тип, определенный в области действия другого типа, который называется включающим типом. Вложенный тип имеет доступ ко всем членам своего включающего типа. Например, он имеет доступ к закрытым (private) полям, определенным во включающем типе, и к защищенным (protected) полям, определенным во всех потомках включающего типа.
Как правило, вложенные типы следует использовать с осторожностью. На это есть несколько причин. Некоторые разработчики не полностью знакомы с концепцией. Они могут, например, иметь проблемы с синтаксисом объявления переменных вложенных типов. Вложенные типы также очень тесно связаны со своими включающими типами и сами по себе не подходят для типов общего назначения.
Вложенные типы лучше всего подходят для моделирования деталей реализации включающих их типов. Конечный пользователь редко должен объявлять переменные вложенного типа и почти никогда не должен явно создавать экземпляры вложенных типов. Например, перечислитель коллекции (enumerator) может быть вложенным типом этой коллекции. Перечислители обычно создаются включающим типом, и, поскольку многие языки поддерживают оператор foreach, переменные перечислителя редко должны объявляться конечным пользователем.
✅ ИСПОЛЬЗУЙТЕ вложенные типы, когда связь между вложенным типом и его внешним типом такова, что желательно иметь доступ к членам типа.
❌ ИЗБЕГАЙТЕ использования открытых (public) вложенных типов для логической группировки типов; используйте для этого пространства имен.
❌ ИЗБЕГАЙТЕ открытых вложенных типов в публичных API. Единственное исключение - если переменные вложенного типа необходимо объявлять только в редких случаях, например, при создании подклассов или при расширенной настройке.
❌ ИЗБЕГАЙТЕ использования вложенных типов, если на тип, скорее всего, будут ссылаться извне включающим его типа. Например, перечисление, передаваемое методу, определенному в классе, не должно определяться как вложенный тип класса.
❌ ИЗБЕГАЙТЕ использования вложенных типов, если они должны создаваться клиентским кодом. Если тип имеет открытый конструктор, он, скорее всего, не должен быть вложенным. Если тип может быть создан, значит он должен быть самостоятельным (вы можете создавать его, работать с ним и уничтожать его, не используя включающий тип). Внутренние типы не должны широко использоваться за пределами внешнего типа вне контекста внешнего типа.
❌ ИЗБЕГАЙТЕ определения вложенного типа как члена интерфейса. Многие языки не поддерживают такую конструкцию.
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/