C# — Ресурсы, c#


Содержание

Управляемые (managed) ресурсы в исполняемых файлах .NET

Авторы: Павел Румянцев
Владислав Чистяков
The RSDN Group
Источник: RSDN Magazine #3-2003

Опубликовано: 17.01.2004
Исправлено: 10.12.2020
Версия текста: 1.0

Что такое ресурсы

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

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

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

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

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

Ресурсы хранятся в виде потоков (stream) или внешних файлов (которые в программе также выглядят как потоки).

Физически ресурсы могут находиться:

  • непосредственно в исполняемом файле (главном файле сборки);
  • в отдельном файле, принадлежащем той же сборке;
  • в другой сборке.

Именно в манифесте определяется, где конкретно находятся ресурсы. С точки зрения программиста ресурсы выглядят как некоторый поток данных, определение формата и интерпретация содержания которого возлагаются на программиста. Где бы ни находился ресурс (в той же сборке или отдельном файле), программный доступ к нему осуществляется одним-единственным способом – в виде потока (System.IO.Stream).

.NET определяет специальный формат ресурсов, изначально предназначенный для хранения данных дизайнеров, таких, как дизайнеры форм библиотек Windows Forms и ASP.NET. Часто этот формат называется в документации «ресурсом». Это иногда приводит к путанице. Чтобы отличить сами ресурсы от конкретного формата, в этой статье данный формат будет называться .resources, так как файлы в этом формате имеют расширение «.resources».

Работа с ресурсами в приложениях

Ресурсы в приложение могут быть добавлены несколькими способами:

  • программно;
  • при помощи утилит командной строки;
  • при помощи Visual Studio .NET.

Программное добавление ресурсов

Добавить ресурс программным способом можно при помощи API, предназначенного для создания сборок, например, System.Reflection.Emit. Разумеется, к этому моменту ресурсы уже должны быть созданы. Если в ресурсы необходимо поместить данные из уже существующего файла, то никаких проблем не возникнет. Если же ресурсы нужно разместить в формате .resources, придется использовать класс ResourceWriter, входящий в пространство имён System.Resources. К формату .resources мы ещё вернёмся и обсудим его более подробно, а пока поговорим о создании ресурсов при помощи ResourceWriter.

У класса ResourceWriter есть два конструктора:

После создания экземпляра класса ResourceWriter для добавления ресурса необходимо воспользоваться одним из методов:

Первым аргументом методов является имя добавляемого ресурса, а вторым – непосредственно добавляемый ресурс.

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

В ресурсы могут добавляться системные типы (в состав которых входят примитивы (например, Int32), String, DateTime, TimeSpan, Decimal), а также произвольные объекты, поддерживающие сериализацию. Здесь следует уточнить, что объекты не добавляются , а сериализуются . Отсюда и вытекает одно из требований – для того, чтобы объект можно было разместить в ресурсах, он должен быть сериализуемым . Таким образом, «тело» ресурса представляет собой ни что иное, как сериализованное представление объекта того или иного типа.

Эти методы формируют данные в оперативной памяти. Запись в файл производится при помощи метода Generate(), не имеющего параметров. После завершения работы с файлом ресурсов ResourceWriter нужно «закрыть» при помощи методов Close или Dispose, которые вызывают метод Generate(). При работе на C# проще всего создавать экземпляр объекта ResourceWriter внутри оператора using.

Вот пример использования класса ResourceWriter:

Он создает в текущем каталоге файл с именем MyRes.resource, в который записывает строку с именем ресурса «str1» и структуру. Перед записью структуры производится боксинг. Использование оператора using позволяет не беспокоиться о вызове метода Close или Generate(), а также защититься от проблем, связанных с исключениями.

Добавление ресурсов из командной строки

Для включения ресурсов в исполняемый файл в компиляторе csc используется опция /resource (сокращённая форма /res), за которой через двоеточие должно быть указано имя файла ресурсов. Следующий пример создает приложение Form1.exe, включая в него в качестве ресурсов файлы Bitmap2.bmp и MyStrings.resources:

В этом случае оба ресурса будут размещены в разделе управляемых (managed) ресурсов исполняемого файла.

Если же возникает необходимость подключить внешний файл ресурсов, не включённый в исполняемый файл, можно воспользоваться опцией /linkresource (сокращённая форма /linkres).

Например, чтобы приложение могло использовать MyStrings.resources как внешний файл, необходимо выдать команду:

Чтобы добавить управляемые (managed) ресурсы в файл, создаваемый при помощи компилятора MC++, необходимо воспользоваться опцией компоновщика /ASSEMBLYRESOURCE.

Для создания файла .resources из командной строки можно использовать утилиту resgen, которой в качестве входных данных можно указать файл формата .resx или текстовый файл, содержащий набор именованных строк. XML-формат файла .resx довольно сложен. Разумеется, создать файл такого формата вручную можно, но это сложно и неудобно. Поэтому вручную имеет смысл создавать только строковые ресурсы, используя для этого специальный инструмент – программу ResGen, которая поставляется вместе с SDK.. В большинстве случаев .resx-файлы создаются VS.NET автоматически, и предназначены для сериализации данных дизайнеров. Использовать такие файлы без IDE типа VS.NET крайне сложно. Для создания .resources-файлов, содержащих только строки, resgen использует более простой формат входного файла.

Чтобы создать строковые ресурсы, необходимо создать файл со строками следующего формата:

Утилита resgen, получив файл такого формата в качестве входного, создаёт другой файл, по умолчанию имеющий расширение .resources. Этот файл может быть добавлен в состав проекта.

Для примера был создан файл MyStrings.txt следующего содержания:

После выполнения команды

будет создан файл MyStrings.resources, который можно включить в проект.

Единственное, что осталось сказать – если вам нужен набор символов, отличный от ASCII (набор символов 1252), входной файл должен быть сохранен в кодировке Unicode или UTF-8. Это легко сделать с помощью редактора VS.NET или notepad.exe из любой версии ОС семейства NT.

Добавление ресурсов в VS.NET

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

Допустим, на винчестере уже приготовлен файл с именем Bitmap1.bmp, содержащий какую-то картинку (имя файла и характер информации в нём могут быть любыми). Чтобы добавить содержимое файла Bitmap1.bmp в исполняемый файл в качестве ресурса, необходимо произвести следующие действия:

В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.

В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add existing item».

В появившемся диалоговом окне выбрать файл, который должен быть добавлен в проект.

После выполнения всех этих действий файл появится в дереве проекта (см. рисунок 1):

В контекстном меню добавленного файла выбрать пункт «Properties», что, в свою очередь, приведёт к появлению окна «Properties», в котором можно будет откорректировать свойства файла.

Выбрать в свойстве «Build Action» значение «Embedded Resource» (см. рисунок 2):

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


Как уже говорилось, в состав ресурсов могут быть добавлены и ресурсы формата .resources. Для этого необходимо произвести следующие действия:

  1. В окне Solution Explorer’а выделить элемент, соответствующий разрабатываемому проекту, после чего нажать правую кнопку мыши.
  2. В появившемся popup-меню выбрать элемент «Add», после чего в очередном popup-меню выбрать элемент «Add new item».
  3. В левой части появившегося диалогового окна выбрать категорию «Resources».
  4. В правой части диалогового окна выбрать «Assembly Resource File».
  5. В появившемся gr >
    Рисунок 3.

В результате этих действий Visual Studio создаст XML-файл, часть содержимого которого можно увидеть на рисунке 4.

Этот файл будет иметь расширение .resx, и при компиляции проекта его содержимое будет преобразовано в .resources и включено в исполняемый файл.

К сожалению, при помощи grid’а (см. рисунок 3) Visual Studio .NET добавить в .resx данные не примитивных типов, например, картинку, практически невозможно, это может быть сделано только при помощи сериализации данных в ресурсы.

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

Использование ресурсов в приложении

Понятно, что ресурсы создаются для того, чтобы впоследствии извлечь из них данные. Доступ к ресурсам осуществляется при помощи метода GetManifestResourceStream. Скажем, для того, чтобы обратиться к ранее добавленному в ресурсы Bitmap1.bmp, можно использовать следующую последовательность операторов:

Res1 – это название пространства имён, используемого приложением по умолчанию. Его к имени ресурса добавляет Visual Studio.NET.

Следующий код считывает из ресурсов содержимое ранее добавленного текстового файла 1.txt:

В этом отрывке кода подразумевается, что файл добавлялся при помощи утилиты командной строки, а не при помощи VS.NET. Обратите внимание, что данные ресурсов представляются с помощью потока (System.IO.Stream), его необходимо читать с помощью одного из Reader’ов. В частности, для этой цели прекрасно подходит StreamReader. Но по умолчанию StreamReader будет работать в формате UTF-8, поэтому если в файле находится текст в кодировке 1251, необходимо указать её явно.

Очевидно, что формат .resource создавался с расчётом на то, что данные из него будут считываться поэлементно, причём поиск отдельного ресурса внутри этого формата будет производиться по имени этого ресурса. Для доступа к ресурсам можно использовать методы класса ResourceManager. Два его основных метода, GetString и GetObject, предназначены для загрузки из ресурсов строк и объектов соответственно. Ниже приведён пример использования объекта класса ResourceManager для загрузки строки, ранее добавленной в .resources:

Этот метод возвращает не просто набор байтов, а экземпляр класса System.String. Метод GetObject находит ресурс-элемент с указанным именем, создаёт новый экземпляр объекта и загружает в него (с помощью BinaryFormatter’а) состояние объекта. Таким образом, можно сказать, что методы ResourceManager загружают объекты из ресурсов по неким логическим именам. Как говорилось ранее, в первую очередь этот механизм был предназначен для загрузки состояния компонентов и их содержимого в дизайнерах, таких, как Windows Forms и ASP.NET. По умолчанию дизайнеры стараются сериализовать состояние компонентов в код, но бывают случаи, когда сериализация в код невозможна. Например, затруднительно (и неразумно) сериализовать в код содержимое картинок. К тому же для того, чтобы дизайнер мог полностью сериализовать в код все свойства компонента, разработчик компонента должен предпринять некоторые действия (реализовать TypeConverter, преобразующий экземпляр объекта в специальный класс-описание экземпляра – InstanceDescriptor). Если такового не имеется, и сериализуемый класс не является компонентом, дизайнер попытается сериализовать состояние экземпляра BinaryFormatter’ом. Если это удастся, он запишет это состояние в .resx-файл, а в код подставит вызов метода ResourceManager.GetObject. При этом ресурсу будет дано имя вида ИмяКомпонента.ИмяСвойства. Например, компонент pictureBox1 (типа System.Windows.Forms.PictureBox) имеет свойство Image типа System.Drawing.Image. Этот тип не поддерживает сериализацию в код, да ему это не особо и требуется, так как основное его содержимое – это картинка. Поэтому дизайнеры сериализуют объекты такого типа в ресурсы (.resx-файлы). После компиляции .resx-файлы превращаются в ресурсы формата .resources с именами, соответствующими именам форм вида ПространствоИменПринятоеПоУмолчанию.ИмяФормы.resources. При этом в самом начале метода InitializeComponent соответствующей формы добавляется код, инициализирующий экземпляр ResourceManager:

а участок кода инициализации компонента (в нашем случае pictureBox1) добавляется в код загрузки объекта из ресурса:

По умолчанию основное содержимое формы, в том числе и строки, сериализуются в код. Если стоит задача создания многоязычного (поддерживающего локализацию) приложения, в свойствах формы нужно переключить свойство Localizable (по умолчанию оно выставлено в false). После этого все свойства формы будут сериализоваться в ресурсы. Если после этого в свойстве Language переключить язык, то для выбранного языка будет создан свой .resx-файл (а впоследствии и свой экземпляр .resources). Эти экземпляры отличаются суффиксом имени. Например, для английского языка используется суффикс «.en», а для русского – «.ru». Свойства Localizable и Language добавляются к дизайнерам верхнего уровня (root designer-ам) специальным расширенным провайдером – LocalizationExtenderProvider.

После компиляции в ресурс приложения записываются нейтральные к культуре ресурсы, а для разных языков создаются отдельные DLL. Эти DLL помещаются в поддиректории, имена которых соответствуют суффиксам языков. Имена DLL формируются следующим образом: ИмяПриложения.resources.dll, например, Res1.resources.dll.

При этом выбор DLL осуществляется в зависимости от культуры установленной системы, подчёркиваю, установленной системы, а не указанного в региональных установках языка. Язык, указанный в региональных установках, отображается в .NET на свойство Thread.CurrentThread.CurrentCulture или (что то же самое) CultureInfo.CurrentCulture. За язык интерфейса отвечает свойство Thread.CurrentThread.CurrentUICulture.

Если вы хотите, чтобы приложение под, скажем, английской версией Windows работало на русском языке, необходимо свойству Thread.CurrentThread.CurrentUICulture присвоить значение CultureInfo.CurrentCulture. Возможно, такие вещи лучше было бы делать настраиваемыми через пользовательский интерфейс.

Если стоит задача прочитать все содержимое ресурсного файла (или ресурса в формате .resources, находящегося в некоторой сборке), можно использовать класс System.Resources.ResourceReader, реализующий интерфейс IResourceReader. Конструкторы этого класса приведены ниже:

В качестве аргументов передаётся имя файла или указатель на поток, содержащий ресурсы. Поток можно получить способом, аналогичным тому, как это делалось выше для ресурса формата bmp, т.е. если в приложение Res1 добавлен ресурс MyRes.resource, чтобы получить поток, содержащий этот ресурс, можно воспользоваться кодом:

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

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

Физическая организация ресурсов в исполняемом файле.

Чтобы определить, где располагаются ресурсы, необходимо проанализировать таблицу ManifestResource 40), входящую в состав метаданных. Так как нам придётся в дальнейшем работать с ней, приведем список её полей:

Первым делом необходимо определить, где находятся те ресурсы, которые необходимо разобрать. Для этого нужно проанализировать поле Implementation. Если его значение не равно нулю, оно содержит закодированный номер строки в таблицах File или AssemblyRef.

ПРИМЕЧАНИЕ

Подробнее о таблицах см. статью «Физическая организация метаданных в исполняемых файлах .NET» в прошлом номере журнала.

Определить, с какой таблицей предстоит работать, можно, проанализировав два младших бита поля Implementation. Если их значение равно нулю, ссылка производится на таблицу Files. Если их значение равно единице, ссылка осуществляется на таблицу AssemblyRef. Сдвинув старшие биты поля Implementation вправо на два разряда, получим номер строки в таблице, на которую производится ссылка. Если же значение поля Implementation равно нулю, ресурсы находятся непосредственно в исследуемом файле. Взглянув на таблицу ManifestResource в файле mscorlib.dll, мы увидим следующую картину (см. рисунок 5).

Рисунок 5. Таблица ManifestResource файла mscorlib.dll.

Понятно, что ресурс, данные о котором расположены по смещению 0x17e44a, находится в теле самой библиотеки, а ресурсы со смещениями 0x17e458 и 0x17e466 находятся в других файлах (но не в других сборках). Естественно, эти файлы должны быть перечислены в таблице File. Взглянув на содержание этой таблицы (см. рисунок 6), мы увидим, что в ней присутствуют ссылки на файлы ресурсов.

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

Как легко догадаться, поле Name таблицы ManifestResource определяет название ресурса. Точнее, не название, а смещение в хипе имён, начиная с которого располагается имя ресурса. И, естественно, значением поля Offset является смещение ресурса относительно того места, начиная с которого ресурсы располагаются в исполняемом файле. Еще раз, в данном случае речь идёт не об RVA, а о смещении.

Итак, можно сказать, что к этому моменту названия ресурсов и «координаты», по которым они находятся, известны. Ничто не мешает построить список ресурсов (см. рисунок 7):

Рисунок 7. Начало списка управляемых ресурсов.

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

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

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

Рисунок 8. Формат хранения данных ресурсов.

Кроме того, скорее всего, в данном случае (рисунок 8) по смещению 0x236с8 находится картинка в формате .png, а, скажем, по смещению 0x281a0 – какие-то данные в формате XML, которые были добавлены в состав ресурсов как отдельные файлы. О содержании .resources пока можно только догадываться.

Формат .resources

Обычно ресурсы в формате .resources имеют имя, оканчивающееся на .resources. Но ничто не мешает программисту добавить ресурс любого другого формата, имя которого будет оканчиваться на .resources. Видимо, чтобы увеличить надежность системы, программисты Microsoft ввели так называемое «магическое число» (MagicNumber) 0xBEEFCACE, записываемое в самом начале ресурса. Таким образом, если значение начального двойного слова тела ресурса равно приведённой выше сигнатуре, можно с большой вероятностью сказать, что его формат – .resources.

Формат .resources начинается с заголовка. Его называют ResourceManagerHeader. Этот заголовок состоит из нескольких полей и начинается сигнатурой, о которой мы говорили выше. Непосредственно за «волшебным числом» следует номер версии заголовка, также занимающий четыре байта. Это поле называется HeaderVersionNumber.

Четвёртое поле – это имя «считывателя» ресурсов, который должен быть использован системой для чтения ресурсов. Если ресурсы записываются стандартными средствами, система использует «System.Resources.ResourceReader, mscorlib». Поле пятое – название «набора» ресурсов, стандартное название этого поля – System.Resources.RuntimeResource.Set, mscorlib. Третье поле заголовка – это суммарная длина обоих имён.

Структура ResourceManagerHeader’а показана ниже (см. рисунок 9):

Рисунок 9. Структура ResourceManagerHeader’а.

Пусть читателя не удивляет тот факт, что на скриншоте не показаны типы переменных LenghtOfName и Name. Дело в том, что поле Size является «сжатым» представлением числа и может занимать один, два, три, четыре и так далее байтов. Какой в этом случае у него тип? Кроме того, Size фактически является составляющей Name’а. Какого типа в этом случае Name? Надеемся, что и без определения типов в данном случае информации больше, чем достаточно.

Непосредственно за заголовком менеджера ресурсов следует заголовок набора ресурсов. Из него мы можем «вытащить» побольше информации, чем из заголовка менеджера ресурсов. В частности, из первого двойного слова этого заголовка можно получить версию заголовка. Второе двойное слово (а это уже интересно!) хранит в себе число ресурсов, входящих в разбираемый файл .resources. А третье слово – это число типов, которым принадлежат ресурсы. Например, в хипе ресурсов все ресурсы могут принадлежать одному типу. Другой пример – все ресурсы могут быть разных типов. Пример третий – несколько ресурсов принадлежит одному типу, несколько – другому, и так далее…

За полем, хранящим число типов, следуют поля, в которых записаны названия типов. Каждому названию типов предшествует длина этого типа. Но, наверное, лучше всё это увидеть, чем пытаться представлять (см. рисунок 10).

Рисунок 10. Заголовок набора ресурсов и названия типов ресурсов.

А вот дальше начинается целая детективная история. За названиями типов следует массив хэшей имён ресурсов. В исходниках SSCLI (файл resourcereader.cs) в одном из комментариев написано, «Note that the name hashes array is aligned to 8 bytes so we can use pointers into it on 64 bit machines». Конечно, комментарии не являются технической документацией, однако код для перехода на эту границу восьми байтов, подтверждает написанное:

Декомпилированный Anakrino код практически совпадает с приведённым выше. Другими словами, в этом месте до границы 8 байтов файл должен дополняться некоторой последовательностью символов ‘P’, ‘A’ и ‘D’. Но этот код не может объяснить ту ситуацию, с которой мы столкнулись при анализе одного из файлов (см. рисунок 11).

Рисунок 11 «Выравнивающие» байты.


Видно, что за именем последнего типа ресурсов, заканчивающегося строго на границе восьми байтов, следуют ещё четыре байта, заполненные «выравнивающими» символами. Пришлось допустить, что код «устарел»… :-)

А дальше, как уже говорилось выше, располагается массив хэшей имён ресурсов. Каждый элемент этого массива занимает четыре байта. Ничего интересного с точки зрения анализа ресурсов этот массив не представляет, но из песни, как говорится, слова не выкинешь (см. рисунок 12):

Рисунок 12. Массив хэшей названий ресурсов.

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

Рисунок 13. Хэши названий, смещения заголовков ресурсов, заголовки ресурсов.

Помимо названий ресурсов в заголовках ресурсов хранятся смещения тел ресурсов относительно начала массива тел ресурсов (этот массив можно назвать «Resource Bodies»).

К этому моменту не получен ответ только на один вопрос – а как определить, к какому типу принадлежит тот или иной ресурс?

Для ответа на этот вопрос достаточно взглянуть на рисунок 14:

Рисунок 14. Структура «тел» ресурсов.

«Тело» ресурса начинается с его типа. В данном случае тип ресурса является индексом имени типа в массиве названий типов (см. рисунок 11). Непосредственно за индексом следует «тело» ресурса. В частности, ресурс, располагающийся по смещению 0х280f1, представляет собой строку «EditRateForm».

Вероятно, полезно будет взглянуть на 16-ричное представление ресурса (см. рисунок 16).

Рассматриваемый ресурс занимает место от 0x5b38c до 0x5b4f4 включительно (данные получены при разборе ресурсов). Видно, что в своём «теле» ресурс хранит данные о той библиотеке, в которой определён его тип, и непосредственно название типа. Логично предположить, что в данном случае перед нами – сериализованное представление объекта.

Заключение

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

Работа с ресурсами проекта(извлечение звукового файла). NET (C #): Справочник по C#

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

Использование кода
1)Щелкните правой кнопкой мыши на имени проекта в solution explorer.
2)Наведите курсор на Add затем выберите Existing Item.

3)Теперь перейдите в каталог(директорию) с вашим звуковым файлом и выберете необходимый вам.
4)Теперь выберите звуковой файл в Solution Explorer, затем щелкните правой кнопкой мыши на нем, выберите Properties и изменить его Build Action (содержание в Embedded Resource)
5)Выполните построение проекта.
6)Теперь, если вы хотите воспроизводить ваш звуковой файл при загрузке конкретной формы, используйте ниже приведённый код в событии Form_Load.

C# — Ресурсы, c#

Доброго времени суток! В этой, и ряде следующих статей, я хочу поговорить об освобождении неуправляемых ресурсов, используемых в объектах классов, написанных на C#. О чем вообще пойдет речь, ведь в C# объекты удаляются сами (точнее, так называемым сборщиком мусора), т.е. программисту не нужно освобождать память из-под каждого созданного оператором new объекта, как это было в C++. Да, это так, но C# программистам всё ещё приходится работать с так называемыми, неуправляемыми ресурсами, например, файлами, или подключениями к базам данных.

Представьте пример, когда мы пишем некий «класс-обертку» над механизмом работы с файлами. Объекты такого класса будут открывать файлы (скорее всего в своих конструкторах), писать/читать информацию в них, ну и в конце концов должны «закрывать» эти файлы. В C++ такие задачи решались без особых трудностей, там были деструкторы.

Дестркутор в C++ — сущность, обратная по назначению конструктору. Конструктор используется для инициализации создаваемого объекта, а деструктор, для его удаления (если хотите, «подчистки» концов). Таким образом, в C++ можно было использовать связку конструктор/деструктор для выделения/освобождения ресурсов (в конструкторе выделять ресурсы, к примеру, открывать файл, а в деструкторе — освобождать их, т.е. закрывать файл).

В C# есть некие аналоги деструкторам C++, это так называемые финализаторы. Давайте рассмотрим пример класса с конструктором и финализатором:

Обратите внимание на выделенную строку. Это и есть объявление финализатора класса. Он имеет такое же имя как и сам класс, но перед ним указывает знак тильды «

». Финализатор в классе может быть только один, и он не должен иметь ни параметров, ни модификатора доступа (public, private и т.п.).

Теперь, мы можем быть уверены, что занятые объектом класса ресурсы будут освобождены! Но есть одно НО (а если на чистоту, то не одно)! Мы не знаем, когда именно будет вызван финализатор конкретного объекта! А вызовется он далеко не сразу. А это значит, что мы не управляем ситуацией, мы ее не контролируем полностью.

Если в C++ мы точно знали, что деструктор вызывается при уничтожении объекта когда мы освобождаем память занятую им через оператор delete, или когда объект выходит из области определения (если он создавался без использования оператора new). В C#, память, занимаемую объектом освобождает сборщик мусора, который живет свой жизнью и работает по определенным, вероятно оптимизированным алгоритмам.

Таким образом, мы получаем следствия:

  • не знаем когда конкретно будут освобождены выделенные в конструкторе ресурсы (они могут быть заняты еще долгое время);
  • не можем использовать никакие другие объекты внутри финализатора (ведь мы не знаем в каком состоянии они будут находиться в момент вызова финализатора).

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

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

Добавить комментарий Отменить ответ

Для отправки комментария вам необходимо авторизоваться.

Получение строки из ресурса в WPF C# в черновиках Tutorial

(Примечание: статья рассчитана на начинающего программиста)

На днях, вернее, сегодня, начал изучать новый для меня вид разработки приложений — Windows Presentation Foundation и сразу столкнулся с огромным наплывом вопросов, первым в списке которых числится «Как получить строку из ресурса»?
Перечитав множество статей в поисковике, я не нашел рабочего для себя кода (разве что для .NET Framework 3.5), а так как пробую писать приложения на VS2012 под .NET Framework 4.5, этот вариант мне не подходит.

Проведя множество времени в поисках оптимального рабочего кода, собрал знания из нескольких источников и добился своего!

Итак, для начала я создал файл ресурсов и внес в него строку, присвоив ей имя «ProductName» и, внимание, изменил параметр модификатора доступа на «public«

После этого на кнопку присваиваю следующий код:

Запускаем приложение, жмем кнопку и мы видим…

Не удалось найти ресурсы, соответствующие указанной культуре или нейтральной культуре.

Что же нам делать? И тут один источник подсказывает нам, что нужно в свойствах файла «Resources.resx» изменить параметр «Build Action» с «Resources» на «Embedded Resources«

Теперь можно скомпилировать и запустить программу без ошибок. Profit.

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

В коде файла «MainWindow.xaml.cs» находим функцию:

Добавляем к ней строку и приводим к виду:

Преимущества и недостатки C# — Учим Шарп #1

Опубликовано shwan в 06.11.2020

Я хочу обсудить с тобой один очень важный вопрос, который достаточно часто упускают из внимания: действительно ли тебе нужно учить язык C#?

Что бы тебе ни рассказывали на всевозможных онлайн курсах, книгах, тренингах и конференциях, о том, как можно стать программистом за 21 день – все это ложь, п*дежь и провокация.

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

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


История языка C#

Я постараюсь долго не задерживаться на этом пункте, но основную информацию расскажу.

Данный язык создан всеми так горячо любимой корпорацией Зла Microsoft в 2000 году. Он очень многое унаследовал от своих родителей (С++ и Java), но и привнес нового.

Возможной версией использования символа хештега принято считать наследование плюсиков от предков, примерно следующим образом: C → C++ → C++++(C#), потому что символ «#» при хорошем воображении можно получить путем объединения 4-х знаков «+».

Язык активно развивается. Регулярно выходят новые версии C#, которые добавляют новые синтаксические конструкции в язык, а также увеличивают его быстродействие и надежность.

Преимущества C#

Данный язык использует объектно-ориентированный подход к программированию во всем. Это означает, что тебе нужно будет описывать абстрактные конструкции на основе предметной области, а потом реализовывать между ними взаимодействие. Данный подход пользуется большой популярностью, потому что позволяет не держать в голове всю информацию, а работать по принципу черного ящика: подал входные данные -> МАГИЯ -> PROFIT.

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

Еще стоит упомянуть, что все это работает на базе платформы .NET Framework. Что это означает? Для многих непосвященных, это просто какая-то приблуда, которую нужно установить на комп, чтобы программа запустилась, но дело обстоит значительно глубже. Написанный тобой код на языке C# транслируется в промежуточный язык (IL), который в свою очередь уже преобразуется в машинный код на твоем компьютере прямо во время выполнения приложения (JIT). Спрашивается, зачем это все? А суть в том, что ты можешь пилить со своим другом Васей на разных языках один и тот же проект и ни одному из вас не придется переучиваться. Но я никогда не видел, чтобы это реально использовали на практике. Но это еще не все. Так как окончательная компиляция из промежуточного кода выполняется в живую на твоей конкретной машине, то возможно увеличение производительности за счет использования специфических команд именно твоего процессора.

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

Ты просто скачиваешь нужное тебе решение из nuget и начинаешь его использовать. В большинстве своем они бесплатны. Сюда же можно отнести большое количество обучающего и справочного материала. Практически на любой свой вопрос ты сможешь найти ответ на стековерфлоу. Ну а на крайний случай всегда можешь спросить в моем телеграм чате для программистов.

Немаловажно наличие хороших инструментов разработки, и здесь все очень хорошо. Абсолютное большинство разработчиков используют интегрированную среду разработки Visual Studio, которая предоставляет over9000 возможностей, использовать которые ты конечно же не будешь.

Но IDE действительно хороша, к тому же имеется ее полнофункциональная бесплатная версия Community.

Еще к плюсам можно отнести строгую типизацию, которая позволяет защититься от дурака, и не так давно появившаяся кросспратформенность в .NET Core (да-да, мелкомягкие потихоньку захватывают линукс).

Недостатки C#

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

C# очень легко дизассемблируется. Это означает, что с большой долей вероятности твой код будет получен и изучен конкурентами. Конечно же, есть специальные инструменты, которые могут усложнить этот процесс, но на 100% защититься от этого практически невозможно.

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

C# не является повсеместно распространенным языком. Большинство программистов сосредоточены в коммерческой Enterprise сфере, что накладывает весьма серьезные ограничения на поиск работы в небольших городах, где кроме Delphi или PHP ничего жизни не видели. К тому же, как бы то ни было, C# в первую очередь ассоциируется с Windows. Вряд ли в обозримом будущем что-то изменится и Винда все также будет продолжать доминировать на рынке, но все же небольшой риск остается, особенно учитывая недавние фейлы с обновлениями в Windows 10.

Перспективы развития C#

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

Компания Microsoft остается одной из крупнейших IT компаний мира, а C# ее флагманский язык программирования, который постоянно развивается и впитывает в себя все новые возможности. Поэтому в обозримом будущем проблем у данного языка возникнуть не должно.

Сферы применения языка C#

В этой области C#, наверное, впереди планеты всей. Хочешь разрабатывать обычные приложения для компьютера – пожалуйста, стандартные WinForms Application и консоль тебе в помощь. Хочешь такие же, но покрасивее? – используй WPF. И специальные приложения для магазина в Windows Store тоже. Веб-приложения? – Легко ASP.NET всегда придет на помощь. На Linux? – тоже не вопрос, .NET Core уже здесь. Мобильное приложение? – Xamarin сделает сразу под все платформы. Хочешь написать игру? – движок Unity показывает себя очень даже неплохо, и при этом также адаптирует игру под различные платформы. Хочешь приблизить апокалипсис с восстанием машин и создаешь искусственный интеллект? – есть целая платформа с кучей инструментов для этого Microsoft AI Platform. Также и для компьютерного зрения и ботов. Я вообще с трудом могу придумать пример того, что невозможно реализовать на C#. Я где-то встречал даже операционную систему написанную на шарпе. Поэтому в этой области все хорошо.

Зарплаты разработчиков C#

Здесь все в целом неплохо. По данным на 2020 год, C# явно не является самым высокооплачиваемым языком, но и не самый низкооплачиваемый тоже. Среднее значение зарплаты для данного языка в России около 90к рублей. Это весьма неплохой результат, но бесспорно есть и более дорогие языки. В целом, уровень заработной платы намного больше зависит от прямоты рук и уровня знаний, поэтому не так важно на каком языке писать, главное делать это хорошо. Но данная цифра задает примерный уровень для Middle разработчика в среднем по России. От этого уже можно отталкиваться.

Итоги

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

В целом это перспективный язык, достаточно простой для освоения начинающим. Но следует помнить, что существует множество альтернатив, которые лучше подходят под некоторые задачи. Например, могу порекомендовать языки C++, Java, Python.

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

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

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

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

C#: сборки (Assemblies)

Сборка представляет собой одиночный файл Portable Executable (PE) с расширением .exe в случае приложения или .dll в случае многократно используемой библиотеки.

Содержимое сборки

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

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

Манифест сборки

Манифест сборки обязательно должен содержать:

  • простое имя сборки
  • номер версии ( AssemblyVersion )
  • открытый ключ и подписанный хэш сборки, если она имеет строгое имя
  • список ссылаемых сборок (включая их версии и открытые ключи)
  • список модулей сборки
  • список типов, определенных в сборке, и модулей, содержащих каждый тип
  • необязательный набор прав доступа, требуемых или отклоняемых сборкой ( SecurityPermission )
  • целевая культура если это подчиненная сборка ( AssemblyCulture )

Также манифест сборки может содержать:

  • полный заголовок и описание ( AssemblyTitle и AssemblyDescription )
  • копирайт и информацию о компании ( AssemblyCompany и AssemblyCopyright )
  • дополнительные сведения о версии ( AssemblyInformationalVersion )
  • дополнительные атрибуты для специальных данных

Некоторые из этих данных выводятся из параметров, переданных компиляторы. Остальные берутся из атрибутов сборки (указаны в скобках):

Атрибуты сборки обычно определяются в одном файле в проекте. Visual Studio, например, автоматически создает для этих целей файл AssemblyInfo.cs в папке Properties в каждом проекте и заполняет его начальной информацией.

Манифест приложения

Манифест приложения — это XML-файл, который сообщает информацию о сборке операционной системе. Если он определен, он обрабатывается перед загрузкой сборки и может повлиять на запуск процесса приложения со стороны ОС.

Корневой элемент манифеста — assembly в пространстве имен urn:schemas-microsoft-com:asm.v1 :


С помощью следующего примера можно затребовать прав администратора для приложения:

Манифест приложения может быть как отдельным файлом, так и встроенным в саму сборку. Как отдельный файл он должен находиться в одной папке с самой сборкой и иметь расширение .manifest (например, для сборки MyApp.exe — MyApp.exe.manifest ).

Встроить манифест в саму сборку (для этого ее надо сначала скомпилировать) можно с помощью утилиты mt такой командой:

Модули

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

Класс Assembly

Класс System.Reflection.Assembly позволяет получить доступ к метаданным сборки во время выполнения. Получить объект сборки можно несколькими способами:

    с помощью свойства Assembly класса Type :

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

Члены класса Assembly :

  • FullName , GetName — возвращает полностью заданное имя или объект AssemblyName
  • CodeBase , Location — местоположение файла сборки
  • Load , LoadFrom , LoadFile — вручную загружает сборку в текущий домен приложения
  • GlobalAssemblyCache — указывает, находится ли сборка в GAC
  • GetSatelliteAssembly — находит подчиненную сборку с заданной культурой
  • GetType , GetTypes — возвращает тип или все типы, определенные в сборке
  • EntryPoint — возвращает метод точки входа в приложение как объект MethodInfo
  • GetModules , ManifestModule — возвращает все модули или главный модуль сборки
  • GetCustomAttributes — возвращает атрибуты сборки

Имена сборок

Идентификатор сборки состоит из четырех частей:

  • простое имя
  • версия («0.0.0.0» если не задана)
  • культура («neutral» для не подчиненных сборок)
  • маркер открытого ключа («null» если сборка не имеет строгого имени)

Простое имя берется из имени файла, в который сборка была первоначально скомпилирована. Расширение отбрасывается, например, простое имя для сборки System.Xml.dll выглядит как System.Xml . Переименование файла не изменяет простое имя сборки.

Номер версии берется из атрибута AssemblyVersion и представляет собой строку, разделенную на 4 части:

Задать номер версии можно так:

Культура берется из атрибута AssemblyCulture и применяется к подчиненным сборкам.

Маркер открытого ключа берется из пары ключей, передаваемых компилятору с помощью параметра /keyfile .

Полностью заданные имена (Fully Qualified Names)

Полностью заданное имя сборки — это строка, включающая все 4 идентифицирующих компонента в следующем формате:

Например, для сборки System.Xml.dll :

Класс AssemblyName

Класс AssemblyName содержит свойства для каждого из 4 компонентов полностью заданного имени. Получить объект AssemblyName можно несколькими способами:

  • создать экземпляр типа AssemblyName , передав ему полностью заданное имя; можно также создать экземпляр не передавая конструктору аргументов, а затем установить все свойства вручную (в этом случае объект AssemblyName будет изменяемым)
  • вызвать метод GetName объекта Assembly
  • вызвать метод AssemblyName.GetAssemblyName , передав ему путь к файлу сборки

Основные свойства и методы объекта AssemblyName :

Свойство Version возвращает объект типа Version , который сам обладает свойствами Major , Minor , Build и Revision . Поскольку версия сборки, задаваемая атрибутом AssemblyVersion , является частью имени сборки, ее изменение в некоторых случаях не желательно. Помимо атрибута AssemblyVersion существуют еще два атрибута задающие версию,но носящие чисто информативный характер и никак не учитываемые средой CLR:

  • AssemblyInformationalVersion — информационная версия — задает версию, отображаемую конечному пользователю. Она видна в диалоговом окне свойств файла в поле Product Version (Версия продукта). Здесь может находиться любая строка, например, 5.1 Beta 2 . Обычно все сборки в приложении имеют одинаковую информационную версию.
  • AssemblyFileVersion — файловая версия — ссылается на номер компоновки (build) сборки. Отображается в диалоговом окне свойств файла в поле File Version (Версия файла). Как и AssemblyVersion должна содержать строку разделенную на 4 части.

Строгие имена (Strong Names)

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

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

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

Ресурсы, где их размещать, и как ссылаться на них в С#

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

В частности, для приложения С# Windows Forms, где было бы целесообразно разместить мои ресурсы значков и каков путь для их ссылки, как только я получу их в правильном месте?

Большое спасибо за любую помощь.

Вы можете добавить файл Resources.resx в свой проект и добавить к нему такие ресурсы, как изображения, строки, файлы. Затем вы можете ссылаться на эти ресурсы через автоматически созданный класс Resources . По умолчанию Visual Studio создаст файл Resources.resx в каталоге Свойства. Все ресурсы, которые вы добавляете в файл ресурсов, будут добавлены в каталог Ресурсы по умолчанию.

Для завершения я хотел изложить некоторые из перечисленных ответов.

Вставить в Resources.resx

Resources.resx должен был быть создан при создании вашего проекта WinForms. Вы можете просмотреть его под Properties в Solution Explorer .

Дважды щелкните Resources.resx , чтобы открыть конструктор. Вы можете скопировать из Windows Explorer и вставить в конструктор ресурсов VS. Этот инструмент достаточно умен, чтобы выяснить, какой тип ресурса он есть, и вы можете увидеть на экране снимок, что похожие типы ресурсов сгруппированы вместе в выпадающем списке строки меню.

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

Вставить в форму через конструктор

Используя конструктор, вы можете вставить ресурс в форме .resx . Выберите элемент управления в дизайнере и откройте окно Properties ( F4 — горячая клавиша по умолчанию). Найдите свойство appropraite, например Icon для формы. Нажмите кнопку эллипсов, чтобы открыть диалог Open File . Перейдите к ресурсу (если вы ввели его в Resources.resx , он действительно будет в папке Resources , который был бы создан при добавлении вашего первого ресурса в Resources.resx — и вы должны использовать первый метод выше), и выберите правильный ресурс.

На изображении ниже вы можете увидеть, что файл «scrape.ico» устанавливается как форма Main Icon .

Этот пример завершит создание строки в конструкторе форм Main , как это.

Сборка мусора и финализаторы


В статье «Типы данных» дана характеристика и назначение двух сегментов оперативной памяти: стека (stack) и управляемой кучи (heap), отмечена эффективность стека при управлении памятью (в его работу вы при всем желании вмешаться не сможете). Работа с кучей, в которой размещаются объекты, предполагает использование автоматических сборщиков мусора (Garbage collector). Сбор мусора — это симуляция бесконечной памяти на машине с конечной памятью.
При создании приложений на C# можно полагать, что исполняющая среда .NET будет сама заботиться об управляемой куче без непосредственного вмешательства со стороны программиста. Вам понравится «золотое правило» по управлению памятью в .NET:
Размещайте объект в управляемой куче с использованием ключевого слова new и забывайте об этом.
Программирование в среде с автоматической сборкой мусора значительно облегчает разработку приложений, обязанности по управлению памятью, по сути, сняты с плеч программиста и возложены на CLR-среду.
Далее на качественном уровне – о сборке мусора, видах ресурсов, финализаторах и деструкторах (для начинающих — не самая актуальная тема, но для полноты изложения).

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

Как работает сборщик мусора?

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

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

Во время сборки мусора эти объекты (а также любые внутренние объектные ссылки, которые они могут содержать) будут исследованы на предмет наличия у них активных корневых элементов. После построения графа все недостижимые объекты помечаются как являющиеся мусором.

После того как объект помечен для уничтожения, они будут удалены из памяти. Оставшееся пространство в куче будет после этого сжиматься до компактного состояния, что, в свою очередь, вынудит CLR изменить набор активных корневых элементов приложения (и лежащих в их основе указателей) так, чтобы они ссылались на правильное место в памяти. И, наконец, указатель на следующий объект тоже будет подстраиваться так, чтобы указывать на следующий доступный участок памяти.

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

Поколения объектов

При попытке обнаружить недостижимые объекты CLR-среда не проверяет буквально каждый находящийся в куче объект. Очевидно, что на это уходила бы масса времени, особенно в более крупных (реальных) приложениях. Для оптимизации процесса каждый объект в куче относится к определенному «поколению». Смысл в применении поколений выглядит довольно просто: чем дольше объект находится в куче, тем выше вероятность того, что он там и будет оставаться. Например, класс, определенный в главном окне настольного приложения, будет оставаться в памяти вплоть до завершения выполнения программы. С другой стороны, объекты, которые были размещены в куче лишь недавно (как, например, те, что находятся в пределах области действия метода), вероятнее всего будут становиться недостижимым довольно быстро. Исходя из этих предположений, каждый объект в куче относится к одному из перечисленных ниже поколений:

Поколение О Идентифицирует новый только что размещенный объект, который еще никогда не помечался как подлежащий удалению в процессе сборки мусора. Поколение 1 Идентифицирует объект, который уже «пережил» один процесс сборки мусора (был помечен как подлежащий удалению в процессе сборки мусора, но не был удален из-за наличия достаточного места в куче). Поколение 2 Идентифицирует объект, которому удалось пережить более одного прогона сборщика мусора.

Сборщик мусора сначала анализирует все объекты, которые относятся к поколению 0. Если после их удаления остается достаточное количество памяти, статус всех остальных (уцелевших) объектов повышается до поколения 1.

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

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

Метод System.Object.Finalize() и финализатор

Назначение этого метода в C# примерно соответствует деструкторам С++, и он вызывается при сборке мусора для очистки ресурсов, занятых ссылочным объектом. Реализация Finalize() из Object на самом деле ничего не делает и игнорируется сборщиком мусора. Обычно переопределять Finalize() необходимо, если объект владеет неуправляемыми ресурсами, которые нужно освободить при его уничтожении. Сборщик мусора не может сделать это напрямую, потому что он знает только об управляемых ресурсах, поэтому полагается на финализацию, определенную вами.

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

Вместо этого для достижения того же эффекта должен применяться синтаксис деструктора (подобно С++). Объясняется это тем, что при обработке синтаксиса финализатора компилятор автоматически добавляет в неявно переопределяемый метод Finalize() приличное количество требуемых элементов инфраструктуры.

Класс System.GC (Garbage Collector)

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

Метод Collect() заставляет сборщик мусора провести сборку мусора. Должен быть перегружен так, чтобы указывать, объекты какого поколения подлежат сборке, а также какой режим сборки использовать (с помощью перечисления GCCollectionMode)

Для проектов с неуправляемым кодом особое значение имеют два следующих метода из класса GC: AddMemoryPressure() и RemoveMemoryPressure(). С их помощью указывается большой объем неуправляемой памяти, выделяемой или освобождаемой в программе.

Особое значение этих методов состоит в том, что система управления памятью не контролирует область неуправляемой памяти. Если программа выделяет большой объем неуправляемой памяти, то это может сказаться на производительности, поскольку системе ничего неизвестно о таком сокращении объема свободно доступной памяти. Если же большой объем неуправляемой памяти выделяется с помощью метода AddMemoryPressure(), то система CLR уведомляется о сокращении объема свободно доступной памяти. А если выделенная область памяти освобождается с помощью метода RemoveMemoryPressure(), то система CLR уведомляется о соответствующем восстановлении объема свободно доступной памяти. Следует, однако, иметь в виду, что метод RemoveMemoryPressure() необходимо вызывать только для уведомления об освобождении области неуправляемой памяти, выделенной с помощью метода AddMemoryPressure().

Сборщик мусора .NET предназначен в основном для того, чтобы управлять памятью вместо разработчиков. Однако в очень редких случаях требуется принудительно запустить сборку мусора с помощью метода GC.Collect(). Примеры таких ситуаций:
1) Приложение приступает к выполнению блока кода, прерывание которого возможным процессом сборки мусора является недопустимым.
2) Приложение только что закончило размещать чрезвычайно большое количество объектов и нуждается в как можно скорейшем освобождении большого объема памяти.

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

Первый подход заключается в переопределении метода System.Object.Finalize() и позволяет гарантировать то, что объект будет очищать себя сам во время процесса сборки мусора (когда бы тот не запускался) без вмешательства со стороны пользователя.

Второй подход предусматривает реализацию интерфейса IDisposable и позволяет обеспечить пользователя объекта возможностью очищать объект сразу же по окончании работы с ним. Однако если пользователь забудет вызвать метод Dispose(), неуправляемые ресурсы могут оставаться в памяти на неопределенный срок.

Оба подхода можно комбинировать и применять вместе в определении одного класса, получая преимущества от обеих моделей. Если пользователь объекта не забыл вызвать метод Dispose(), можно проинформировать сборщик мусора о пропуске финализации, вызвав метод GC.SuppressFinalize(). Если же пользователь забыл вызвать этот метод, объект рано или поздно будет подвергнут финализации и получит возможность освободить внутренние ресурсы. Преимущество такого подхода в том, что при этом внутренние ресурсы будут так или иначе, но всегда освобождаться.

Конструкция Using в C#

(часто встречается при работе с баами данных)

Ключевое слово using упрощает работу с объектами которые реализуют интерфейс IDisposable.
Интерфейс IDisposable содержит один метод .Dispose(), который используется для освобождения ресурсов,
которые захватил объект. При использовании Using не обязательно явно вызывать .Dispose() для объекта.

Пример

При этом компилятор фактически генерирует следующий код:

Заметим, что Using-блоки делают код более читабельным и компактным (некоторый аналог лямбда-операторов).

10 фич в C#, о которых вы определённо должны узнать и начать их использовать

Если вы только начали изучение C# или же решили расширить свои знания, мы нашли для вас 10 фич, знание которых позволит вам избежать ошибок, писать более понятный код и сохранить кучу времени.

1. async / await

Использование паттернов async / await позволяет разблокировать UI / текущий поток во время выполнения блочных операторов. Паттерны async / await позволяют коду продолжить выполнение, даже если что-то блокирует его выполнение (например, веб-запрос).

2. Инициализаторы объектов / массивов / коллекций

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

3. Лямбды, предикаты, делегаты и замыкания

Эти фичи просто незаменимы во многих случаях: например, при использовании LINQ. Поэтому рекомендуем вам убедиться, что вы действительно понимаете, когда и как их использовать.

4. ?? (Оператор объединения с NULL)

x ?? y — возвращает x , если значение отличается от null ; в противном случае возвращает y .

Может быть несколько операторов .

?? также может быть использован для перевода типов null в не null :

5. $”” (Интерполяция строк) — C# 6

Фича в C# 6 позволяет эффективно и элегантно собирать строки:

6. ?.(определяет null) — C# 6

x?.y — доступ к членам, определяемый условием null . Возвращает значение null , если левый операнд имеет значение null .

Больше никаких NullReferenceExceptions!

7. Выражение nameof — C# 6


Может показаться, что выражение nameof не особо полезно, но это не так. При использовании автоматических инструментов рефакторинга (например, ReSharper) иногда может потребоваться обратиться к аргументу метода по его имени:

Вот, как это должно быть:

8. Инициализаторы свойств (property) — C# 6

Инициализаторы свойств позволяют задавать начальные значения для свойств:

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

9. Операторы as и is

Is — совместимость типов. Возвращает значение true, если вычисленный левый операнд может быть приведен к типу, указанному в правом операнде (статический тип).

As — преобразование типов. Возвращает левый операнд, приведенный к типу, заданному правым операндом (статический тип), но as возвращает null , где (T)x вызывает исключение.

10. Ключевое слово yield

Ключевое слово yield позволяет заполнить интерфейс IEnumerable объектами (items). Следующий пример вернет все степени двойки от 2 до 2 в степени 8 (то есть 2, 4, 8, 16, 32, 128, 256):

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

C# — Ресурсы, c#

Большинство ресурсов, которые мы используем в программе C# это управляемые ресурсы .
Управляемые ресурсы — это классы, реализующие IDisposable .

Управляемые ресурсы очищаются сборщиком мусора когда объект удаляется из памяти.

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

// создаем неуправляемый ресурс
IntPtr myHandle1 = CreateFile( «D://1.txt» , GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

// работаем с myHandle1
// .

// освобождаем неуправляемый ресурс
CloseFile(myHandle1);

myHandle1 это неуправляемый ресурс

// создаем неуправляемый ресурс
IntPtr myHandle2 = Marshal.AllocHGlobal(100);

// работаем с myHandle2
// .

// освобождаем неуправляемый ресурс
Marshal.FreeHGlobal(myHandle2);

myHandle2 это неуправляемый ресурс

Неуправляемые ресурсы выгоднее освобождать как можно раньше, еще до удаления объекта из памяти.
Например наша программа открывает файл и лучше, чтобы наша программа как можно раньше закрыла этот файл.
Чтобы другие программы могли использовать.

1) Сборщик мусора смотрит какие объекты не нужны ( это когда нет ссылок на объект ).
2) Сборщик мусора вызовет деструктор у объекта.
3) В деструкторе объекта мы можем освободить неуправляемые ресурсы.
4) Сборщик мусора удалит объект из памяти.

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

Решение!
Мы сами должны освобождать неуправляемые ресурсы и как можно раньше (как только они нам уже не нужны).
Чтобы освободить неуправляемые ресурсы мы должны использовать интерфейс IDisposable .

class MyFile : IDisposable
<
// реализация интерфейса IDisposable
public virtual void Dispose()
<
// очищаем управляемые ресурсы
// .

// очищаем неуправляемые ресурсы
// .
>

MyFile()
<
// очищаем неуправляемые ресурсы
// .
>
>

На заметку 1 Интерфейс IDisposable имеет только один метод Dispose . Реализуя этот метод, вы должны выполнить одно важное обязательство: даже многократный вызов Dispose должен происходить без ошибок.

На заметку 2 Интерфейс IDisposable может быть реализован в class и в struct .

myFile — это управляемый ресурс
myFile реализует интерфейс IDisposable ( FileStream наследуется от IDisposable )

Следовательно, >MyFile должен реализовать IDisposable

ptr — это неуправляемый ресурс

Следовательно, >MyFile должен реализовать IDisposable

Сборщик мусора не вызывает метод Dispose() .
Мы должны сами вызывать метод Dispose() .
Метод Dispose () вызывается пользователем (не сборщиком мусора).

Сборщик мусора имеет информацию об выделенной памяти, а не о состоянии ресурсов. Поэтому нам нужно вызывать метод Dispose для освобождения ресурсов в C#.

MyFile file = new MyFile( «D://a.txt» );

// добавляем текст в файл
file.AddTextToFile( «Hello» );

// закрываем неуправляемые ресурсы
file.Dispose();

using вызовет метод Dispose

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// подключаем библиотеку для работы с файлами
using System.IO;

namespace ConsoleApplication1
<
class MyFile : IDisposable
<
protected FileStream MyFileStream < get; set; >
protected StreamWriter MyStream

// конструктор класса
public MyFile( string filePath)
<
// проверяем существует ли файл
if (!File.Exists(filePath))
MyFileStream = File.Create(filePath); // создаем файл
else
MyFileStream = File.Open(filePath, FileMode.Append); // открываем файл

// открываем поток
MyStream = new StreamWriter(MyFileStream);
>

public void AddTextToFile( string text)
<
// записываем текст в файл
if (MyStream != null)
MyStream.Write(text);
>

// реализация интерфейса IDisposable
public virtual void Dispose()
<
// очищаем управляемые ресурсы
if (MyStream != null)
<
MyStream.Dispose();
MyStream = null;
>

if (MyFileStream != null)
<
MyFileStream.Dispose();
MyFileStream = null;
>

// очищаем неуправляемые ресурсы
// . у нас нет неуправляемых ресурсов
>

MyFile()
<
// очищаем неуправляемые ресурсы
// . у нас нет неуправляемых ресурсов
>

Цукерберг рекомендует:  #создание сайта под ключ - Поиск клиента на заказ Создание сайта под ключ
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих