10 заметок с тегом

c#

Лучшее объяснения работы семафора

A semaphore is like a nightclub: it has a certain capacity, enforced by a bouncer. Once it’s full, no more people can enter, and a queue builds up outside. Then, for each person that leaves, one person enters from the head of the queue. The constructor requires a minimum of two arguments: the number of places currently available in the nightclub and the club’s total capacity.

Алгоритм поиска Ли

Алгоритм поиска пути в планарном графе. Зачастую используется в схемотехнике и в играх (стратегиях) для поиска кратчайшего пути.

Алгоритм состоит из 3 шагов:

  1. Инициализация
  2. Распространение волны
  3. Восстановление пути

Также есть 2 способа поиска пути: ортогональный и ортогонально-диагональный. Ниже на скриншотах можно увидеть работу этих двух способов.

Ортогональный поиск.
Ортогонально-диагональный поиск.

Псевдокод

Взято из Википедии.

Инициализация

Пометить стартовую ячейку 
d := 0

Распространение волны

ЦИКЛ
  ДЛЯ каждой ячейки loc, помеченной числом d
    пометить все соседние свободные непомеченные ячейки числом d + 1
  КЦ
  d := d + 1
ПОКА (финишная ячейка не помечена) И (есть возможность распространения волны)

Восстановление пути

ЕСЛИ финишная ячейка помечена
ТО
  перейти в финишную ячейку
  ЦИКЛ
    выбрать среди соседних ячейку, помеченную числом на 1 меньше числа в текущей ячейке
    перейти в выбранную ячейку и добавить её к пути
  ПОКА текущая ячейка — не стартовая
  ВОЗВРАТ путь найден
ИНАЧЕ
  ВОЗВРАТ путь не найден

Пример кода, который реализует алгоритм и выводит на экран путь (C#).

Правила использования AutoMapper в .NET

Оригинал статьи: https://bool.dev/blog/detail/pravila-ispolzovaniya-automapper-v-net

Конфигурация
✔ Используйте инициализировать AutoMapper один раз в Mapper.Initialize в AppDomain startup в устаревшем ASP.NET
✔ Используйте использовать пакет AutoMapper.Extensions.Microsoft.DependencyInjection в ASP.NET Core с services.AddAutoMapper(assembly[])
✔ Используйте конфигурацию маппингов в профайле
✔ Рассмотрите организацию классов профайлов близко к типам назначения, которые они настраивают (иными словами в UserProfile должны быть маппинги классов связанных с юзером)
✔ Рассмотрите использование параметров конфигурации поддерживаемых LINQ, по сравнению с параметрами, не поддерживаемыми LINQ
Х Избегайте before/after map конфигурацию
Х Не размещайте логику которая строго не относится к поведению при маппинге данных
Х Не используйте MapFrom, когда элемент и так может быть автоматически смаплен
Х Не используйте AutoMapper, за исключением случаев, когда тип назначения представляет собой уплощенное подмножество свойств типа источника
Х Не используйте AutoMapper для поддержки сложной многоуровневой архитектуры
Х Избегайте использования AutoMapper, когда у вас значительный процент конфигурации прописан в форме Ignore или MapFrom
Х Не используйте доступ к статичному Mapper классу внутри профайла
Х Не используйте DI контейнер для регистрации всех профилей
Х Не используйте инджект зависимостей в профайлы
Х Не используйте CreateMap на каждый запрос
Х Не используйте inline maps

Моделирование
✔ Используйте не большие DTO
✔ Необходимо создавать внутренние типы в DTO для типов элементов, которые не могут быть сведены
✔ Следует помещать общие простые «computed property» в source модель
✔ Следует помещать «computed property» которые специфичны для dest модели в dest модель
Х Избегайте совмесное использование DTO между маппингами
Х Не создавайте DTO с «circular» ассоциациями
Х Избегайте изменения имен членов DTO для управления сереализацией

Выполнение
✔ Рассмотрите возможность использования query projection поверх in-memory mapping’a​
✔ Необходимо использовать mapping опции для runtime-resolved значений в проекциях.
✔ Используйте опции mappinga для разрешения контекстуализированных сервисов в in-memory mapping’e
Х Не абстрагируйте и не инкапсулируйте mapping за интерфейсом

Коллекции в .NET

Сегодня мы рассмотрим следующие интерфейсы:

  • IEnumerable
  • IQueryable
  • ICollection
  • IList
  • ISet
  • IDictionary
  • Queue и Stack

Все эти интерфейсы находятся в пространствах имен System.Collection и System.Collection.Generic. Первый тип коллекций стоит избегать, так как они имеют худшую производительность и не обеспечивают безопасность типов.

IEnumerable

Данный интерфейс является базовым для всех коллекций. В нем объявлен только один метод, который позволяет обходить элементы коллекции в цикле. Нельзя изменить данные из IEnumerable.

IQueryable

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

Разница между IQueryable и IEnumerable

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

Когда что использовать?

IEnumerable IQueryable
Может двигаться только вперед по коллекции, он не может идти назад может двигаться только вперед по коллекции, он не может идти назад
Хорошо подходит для работы с данными в памяти (списки, массивы) Лучше работает с запросами к базе данных (вне памяти)
Подходит для LINQ to Object и LINQ to XML Подходит для LINQ to SQL
Поддерживает отложенное выполнение (Lazy Evaluation) Поддерживает отложенное выполнение (Lazy Evaluation)
Не поддерживает произвольные запросы Поддерживает произвольные запросы (используя CreateQuery и метод Execute)
Не поддерживает ленивую загрузку (lazy loading) Поддерживает ленивую загрузку (lazy loading)
Методы расширения, работающие с IEnumerable принимают функциональные объекты Методы расширения, работающие с IQueryable принимают объекты выражения (expression tree)

ICollection

Этот интерфейс содержит методы, которые позволяют манипулировать элементами коллекции. Также содержит свойство Count.

IList

Расширяет коллекции методами поиска по индексу и индексатором.

ISet

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

ISet<int> set = new HashSet<int> { 1, 2, 3, 4, 5 };
 
set.UnionWith(new[] { 5, 6 });              // set = { 1, 2, 3, 4, 5, 6 }
set.IntersectWith(new[] { 3, 4, 5, 6, 7 }); // set = { 3, 4, 5, 6 }
set.ExceptWith(new[] { 6, 7 });             // set = { 3, 4, 5 }
set.SymmetricExceptWith(new[] { 4, 5, 6 }); // set = { 3, 6 }

Остальные только выполняют проверки над коллекцией и возвращают логическое значение (bool):

ISet<int> set = new HashSet<int> { 1, 2, 3, 4, 5 };
 
var isSubset = set.IsSubsetOf(new[] { 1, 2, 3, 4, 5 }); // = true
var isProperSubset = set.IsProperSubsetOf(new[] { 1, 2, 3, 4, 5 }); // = false
var isSuperset = set.IsSupersetOf(new[] { 1, 2, 3, 4, 5 }); // = true
var isProperSuperset = set.IsProperSupersetOf(new[] { 1, 2, 3, 4, 5 }); // = false
var equals = set.SetEquals(new[] { 1, 2, 3, 4, 5 }); // = true
var overlaps = set.Overlaps(new[] { 5, 6 }); // = true

IDictionary

В отличие от предыдущих двух, этот предназначен для хранения пар ключ-значение вместо отдельных значений, т. е. он использует KeyValuePair в качестве универсального параметра в ICollection.
Также он не позволяет хранить несколько элементов с одинаковым ключом.

Реализуют его следующие классы:

  1. Dictionary — обычный словарь.
  2. SortedDictionary — отсортированный список, реализован как словарь. Быстрее вставляет и удаляет элементы.
  3. SortedList — отсортированный список, который основан на массиве. Использует меньше памяти, чем SortedDictionary.

Queue и Stack

Класс Queue реализует принцип FIFO (first in, first out) — первым пришел, первым вышел.
Класс Stack похож на класс Queue, но он реализует принцип LIFO (последний пришел, первый вышел). Единственный элемент, который непосредственно доступен в этой коллекции, — это тот, который был добавлен совсем недавно.

Потокобезопасность

Обычные generic классы в Base Class Library имеют один очень важный недостаток, если вам нужно использовать их в многопоточных приложениях: они не являются поточно-ориентированными или по крайней мере, не полностью потокобезопасные.

Base Class Library содержит класс ReaderWriterLockSlim, который можно использовать для реализации этой конкретной функции относительно простым способом. Содержит 2 метода:

  1. EnterReadLock и ExitReadLock
  2. EnterWriteLock и ExitWriteLock

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

Immutable Collections

Immutable (неизменяемые) коллекции не включены в библиотеку базовых классов. Чтобы использовать их в проекте должен быть установлен пакет NuGet System.Collections.Immutable.

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

Ссылки

  1. https://bool.dev/blog/detail/sravnenie-kollektsiy-v-net
  2. https://bool.dev/blog/detail/kak-pravilno-vybrat-net-kollektsiyu

Первый проект

Нашел в старой переписке скриншоты игры, которую я делал когда только изучал программирование на C#. Игру делал на Unity в сеттинге сталкера. Дальше одной локации и простенького управления не зашло. Жаль что видео не сохранились.

Это был 2014 год.

Также пробовал моделировать в 3ds Max.

Разработка на C# с помощью Visual Studio Code

Пару недель назад решил перейти на VS Code в качестве основной IDE для .NET приложений. Составил список плагинов, которые использую каждый день.

  1. C#
  2. vscode-solution-explorer — позволяет работать с файлами .sln. Добавляет для этого дополнительное меню.
  3. Visual Studio IntelliCode
  4. PowerShell — отличное расширение, которое полностью заменяет PowerShell ISE.
  5. GitLens — Git supercharged
  6. Code Spell Checker
  7. C# Extension
  8. .NET Core Test Explorer
  9. Bookmarks

CancellationToken в C#

В .NET есть механизм, который позволяет отменять асинхронные операции. Состоит он из 3 элементов:

  1. CancellationTokenSource — создает маркеры отмены, которые можно получить из свойства Token и обрабатывает запросы на отмену.
  2. CancellationToken — сам маркер, позволяет отслеживать состояние отмены.
  3. OperationCanceledException — исключение, которое должно генерироваться в случае отмены асинхронной операции.

Как это работает?

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

Пример

public static void Main() 
{
    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
    CancellationToken token = cancelTokenSource.Token;
    
    var task = Task.Run(() => Task(cancelTokenSource.Token), cancelTokenSource.Token);

    cancelTokenSource.Cancel();
}

public static void Task(CancellationToken cancellationToken) 
{
    while (true) 
    {
        // делает полезную работу.

        cancellationToken.ThrowIfCancellationRequested();
    }
    
}

Как работать в токеном?

Токен содержит набор полезных методов и полей:

  1. Register() — позволяет зарегистрировать callback функцию.
  2. IsCancellationRequested — свойство, возвращает true, если операция отменена.
  3. ThrowIfCancellationRequested() — метод, бросает исключения, в случае отмены задачи.
  4. CanBeCanceled — если свойство возвращает false, то запроса на отмену гарантированно не будет. Такой токен можно получить с помощью статического свойства CancellationTokenSource.None или вызвав конструктор с параметром false.

Источники:

  1. Задачи и отмена в .Net — tips & tricks
  2. Отмена задач и параллельных операций. CancellationToken
 Нет комментариев    49   6 мес   .net   c#

Кратко о WebSocket

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

Основной библиотекой для упрощения работы с сокетами от Microsoft является SignalR. Она берет на себя всю работу с данным протоколом и дает разработчику удобный интерфейс для работы.

Где может использоваться SignalR? Прежде всего это приложения, которые получают данные в реальном режиме времени, например, чаты, социальные сети, игровые приложения, карты, приложения для аукционов, голосований и карт, панели управления, приложения для мониторинга данных и так далее.

SignalR предоставляет разработчикам две модели: постоянные подключения (Persistent Connection) и хабы (Hubs).
Постоянные подключения (Persistent Connection API) представляют разработчикам прямой доступ к низкоуровневому протоколу коммуникации. Подключения в этой модели представляют конечную точку, к которой подключаются клиенты, наподобие модели подключений в WCF.
Хабы же предоставляют протокол взаимодействия более высокого уровня. Они представляют верхний слой над Persistent Connection API и позволяют клиенту и серверу напрямую вызывать методы друг друга.

SignalR предоставляет следующие типы технологий для взаимодействия сервера и клиента:

  • WebSockets
  • Server-sent events
  • Forever Frames
  • Long polling

Картинка, которая иллюстрирует процесс установления связи по протоколу WebSocket

Следующий код был взят из websocket.org, но он демонстрирует базовое взаимодействие со стороны клиента с данным протоколом:

var wsUri = "ws://echo.websocket.org/";
var output;

function init()
{
  output = document.getElementById("output");
  testWebSocket();
}

function testWebSocket()
{
  websocket = new WebSocket(wsUri);
  websocket.onopen = function(evt) { onOpen(evt) };
  websocket.onclose = function(evt) { onClose(evt) };
  websocket.onmessage = function(evt) { onMessage(evt) };
  websocket.onerror = function(evt) { onError(evt) };
}

function onOpen(evt)
{
  writeToScreen("CONNECTED");
  doSend("WebSocket rocks");
}

function onClose(evt)
{
  writeToScreen("DISCONNECTED");
}

function onMessage(evt)
{
  writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
  websocket.close();
}

function onError(evt)
{
  writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}

function doSend(message)
{
  writeToScreen("SENT: " + message);
  websocket.send(message);
}

function writeToScreen(message)
{
  var pre = document.createElement("p");
  pre.style.wordWrap = "break-word";
  pre.innerHTML = message;
  output.appendChild(pre);
}

window.addEventListener("load", init, false);

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

По мнению многих, WebSockets — едва ли не самое полезное изобретение после горячей воды. Когда вы ухватите суть WebSockets, вы будете удивляться, как это мир программного обеспечения мог обходиться без этих сокетов. WebSockets здорово пригодится в ряде приложений, но отнюдь не во всех. В любом приложении, где мгновенный обмен сообщениями является ключевым требованием, — имеет смысл серьезно подумать о создании WebSocket-сервера и клиентов (веб-приложения, мобильные и даже настольные программы). Еще одна группа приложений, которые получат выигрыш от внедрения WebSocket Protocol, — игры и каналы реального времени. Да, WebSockets — определенно лучшее, что было изобретено после горячей воды!

Ссылки:

  1. Отличный пример реализации клиента и сервера без использования сторонних библиотек
  2. SignalR Core. Первое приложение
  3. Офф. сайт
 Нет комментариев    46   1 год   .net   c#   скиллы

C# Debug vs Release. Сборки и дебаг

Оригинальная статья: тык.

Из коробки в C# нам доступны 2 способа сборки проекта release и debug.

О компиляции C# кода

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

Обычно первый этап происходит на вашем CI сервере, а второй шаг происходит позже, во время работы самого приложения. Когда же мы работаем локально в Visual Studio, то она все эти шаги выполняет перед запуском приложения из меню Debug.

Шаг первый. Компиляция приложения
Ваш код превращается в Common Intermediate Language (CIL), который уже может быть выполнен в любом окружении, которое поддерживает CIL. Обратите внимание, что собранная сборка не является читаемым текстом IL, а фактически метаданными и байтовым кодом в виде двоичных данных.

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

Шаг второй. JIT компилятор
JIT компилятор конвертирует IL код в инструкции процессора, которые можно выполнить на вашей машине. Однако не вся компиляция происходит заранее — в нормальном режиме, код компилируется, только тогда когда его вызывают в первый раз, после чего он кэшируется.

Компилятор JIT — это всего лишь один из целого ряда сервисов, которые составляют Common Language Runtime (CLR), позволяя ему выполнять код .NET.

Основная часть оптимизации кода будет проведена на этом шаге.

Что такое оптимизация кода в одном предложении?

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

Почему мы заинтересованы в оптимизации в этой статье?

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

Оптимизация компилятора C#

C# компилятор на самом деле делает очень мало оптимизаций. На самом деле большенство оптимизаций производит JIT компилятор во время генерирования машинного кода. Тем не менее это все равно ухудшит работу по отладке.

Инструкция nop в IL
Команда nop имеет ряд применений при программировании на низком уровне, например, для включения небольших предсказуемых задержек или инструкции по перезаписи, которые вы хотите удалить. В IL коде данные конструкции помогают при использовании точек останова (breakpoints) для того чтобы они вели себя предсказуемо.

Если мы посмотрим на IL код, который сгенерирован с отключенными оптимизациями:

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

Даная инструкция была бы удалена если у нас включены оптимизации, что повлияло бы на отладку приложения.

Более подробное обсуждение оптимизаций компилятора C# в статье Эрика Липперта: Что делает переключатель оптимизации?. Существует также хороший комментарий о IL до и после оптимизации здесь.

Оптимизация JIT компилятора

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

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

Для реальной оптимизации, сделанной компилятором JIT, я буду показывать инструкции по сборке. Это всего лишь макет на C #, чтобы дать вам общую идею.

Предположим, что у меня есть:

private long Add(int a, int b)
{
    return a + b;
}
public void MethodA()
{
    var r = Add(a, b);
}

Компилятор JIT, скорее всего, выполнит встроенное расширение. Он заменит вызов метода Add() телом данного метода:

public void MethodA()
{
    var r = a + b;
}

Конфигурации сборки по умолчанию

Итак, теперь, когда мы обновили понимание компиляции .NET и двух «слоев» оптимизации, давайте взглянем на 2 конфигурации сборки, доступные «из коробки»:

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

Внутренности аргументов оптимизации и отладки

Я попытался продемонстрировать данные аргументы из кода Roslyn и mscorlib. Теперь мы имеем следующие классы:

  1. CSharpCommandLineParser
  2. CodeGenerator
  3. ILEmitStyle
  4. debuggerattributes
  5. Optimizer
  6. OptimizationLevel

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

Перечисление OptimizationLevel

OptimizationLevel.Debug отключает все оптимизации для C# и JIT компилятора с помощью DebuggableAttribute.DebuggingModes, который с помощью ildasm, мы можем видеть:

OptimizationLevel.Release включает все оптимизации (DebuggableAttribute.DebuggingModes = ( 01 00 02 00 00 00 00 00 )) что в свою очередь соответсвует DebuggingModes.IgnoreSymbolStoreSequencePoints

При этом уровне оптимизации точки останова могут быть оптимизированы. Что приведет к тому что мы не сможем их поставить и остановиться.

Типы IL

Типы IL кода описаны в классе ILEmitStyle.

На диаграмме выше показано, что тип генерируемого IL кода C# компилятором зависит от OptimizationLevel.
Аргумент debug не меняет его, за исключение аргумента debug+ когда OptimizationLevel установлен в Release.

  • ILEmitStyle.Debug — нету оптимизация IL в дополнение к добавлению nop инструкций для сопоставления точекостановки с IL.
  • LEmitStyle.Release — полная оптимизация.
  • ILEmitStyle.DebugFriendlyRelease — выполняет только те оптимизации, которые не помешаю отладке приложения.

Следующий кусок кода продемонстрирует все это наглядно.

if(optimizations == OptimizationLevel.Debug)
{
    _ilEmitStyle = ILEmitStyle.Debug;
}
else
{
    _ilEmitStyle = IsDebugPlus() ?
    ILEmitStyle.DebugFriendlyRelease :
    ILEmitStyle.Release;
}

Комментарий в исходном файле Optimizer.cs гласит, что они не опускают никаких определенных пользователем локальных переменных (примеры на 28 строчке) и не переносят значения в стек между операторами.

Я рад, что прочитал это, так как я был немного разочарован своими собственными экспериментами в ildasm с debug +, поскольку все, что я видел, это сохранение локальных переменных.

Нет намеренной «деоптимизации», например, добавления команд nop.

Разница между debug, debug:full и debug:pdbonly.

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

Результат остается одним и тем же — pdb файл создается.
Подгланув в CSharpCommandLineParser можем убедиться в этом. И для того чтобы проверить это, мне удалось отладить код с помощью WinDbg для обох аргументов (pdbonly и full).

Они не влияют на оптимизацию кода.
Как плюсом могу отметить что документация на Github более актуальна, но она не открывает свет на специфическое поведение для debug+

А что же такое этот ваш pdb файл?
Все очень просто, данный файл содержит всю необходимую информацию для отладки DLL и EXE. Что помогает сопоставить отладчику IL код с инструкциями в оригинальном C# коде.

Что на счет debug+?

debug+ это особенный аргумент, который не может быть заменен с помощью full и pdbonly. Как многие заметили, данный аргумент соответствует debug:full — это на самом деле не совсем правда. Если мы используем аргумент optimize-, поведение будет таким же, но для optimize+ будет свое собственное уникальное поведение (DebugFriendlyRelease).

debug- или без аргументов?

Значения по умолчанию, которые установлены в CSharpCommandLineParser.cs.

bool debugPlus = false;
bool emitPdb = false;

а значения для debug=:

case "debug-":
    if (value != null)
        break;
 
    bool emitPdb = false;
    bool debugPlus = false;

Таким образом, мы можем с уверенностью сказать, что debug- и отсутствие аргументов отладки, приводет к одному и тому же эффекту — pdb файл не будет создан.

Запрет оптимизации JIT при загрузке модуля (Suppress JIT optimizations)

Флажок в разделе «Options->Debugging->General» это опция для отладчика в Visual Studio и не повлияет на сборки, которые вы создаете.

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

Обычно этот параметр включаю для того чтобы отладить внешние библиотеки или пакеты NuGet.

Если мы хотим подключиться отладчиком Visual Studion к продакшен сборке, которая собрана в релизной конфигурации, то при наличии у нас pdb файла, мы может еще одним способом указать JIT компилятору, о том чтобы он не оптимизировал код. Для этого нужно добавить .ini файл с таким же названием как выполняемая библиотека и указать в нем:

[.NET Framework Debugging Control]
AllowOptimize=0

Что такое Just My Code?

По умолчанию эта настройка уже включена (Options->Debugging→Enable Just My Code) и отладчик думает что оптимизированный код не является пользовательским. Поэтому отладчик никогда не зайдет в такой код.

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

Этот параметр стоит отключать в том случае, когда у вас есть pdb файл.

Взглянем ближе на DebuggableAttribute

Выше я упомянул использование ildasm для изучения манифеста сборок для изучения DebuggableAttribute. Я также написал небольшой PowerShell скрипт для получения более дружественного результата (ссылка на скачивание).

Сборка Debug:

Сборка Release:

Вы можете игнорировать IsJITTrackingEnabled, поскольку он был проигнорирован компилятором JIT с .NET 2.0.
Компилятор JIT всегда будет генерировать информацию для отслеживания во время отладки, чтобы сопоставлять IL с машинным кодом и отслеживать, где хранятся локальные переменные и аргументы функции.

IsJITOptimizerDisabled — просто проверяет DebuggingFlags на наявность DebuggingModes.DisableOptimizations. Он отвечает за включение оптимизация с помощью JIT.

DebuggingModes.IgnoreSymbolStoreSequencePoints — говорит отладчику выработать точки последовательности из IL кода вместо загрузки .pdb файла. Точки последовательности используются для сопоставления местоположений в коде IL с местоположениями в исходном коде C#. Если он включен, то JIT не будет загружать .pdb файл. Я не уверен, почему этот флаг добавляется в оптимизированные сборки компилятором C#.
Также об этом флаге можно почитать здесь

Ключевые понятия

  1. debug- или отсутствие аргумента приводит к тому что не создается .pdb файл.
  2. debug, debug:full и debug:pdbonly приводят к созданию .pdb файла. debug+ делает тоже самое в случае когда установлен флаг optimize-.
  3. debug+ и optimize+ создают такой IL код, который легче отлаживать.
  4. Каждый слой оптимизации ухудшает отладку кода.
  5. С .NET 2.0 компилятор JIT всегда будет генерировать информацию для отслеживания независимо от атрибута IsJITTrackingEnabled.
  6. Будь то сборка через VS или csc.exe, атрибут DebuggableAttribute теперь всегда присутствует.
  7. optimised+ создает бинарники, которые отладчик воспринимает как сторонний код. Это поведение управляется с помощью опции Just My Code, но при ее отключении вы можете получить очень сомнительный опыт отладки.

Теперь у вас есть выбор:

  1. Debug: debug|debug:full|debug:pdbonly optimize+
  2. Release: debug-|no debug argument optimize+
  3. DebugFriendlyRelease: debug+ optimize+
 Нет комментариев    222   1 год   .net   c#   перевод