Архивы рубрики: Алгоритмы и функции

1.5 Манипуляции строками

На данный момент, мы могли начать уставать от всех этих чисел и математики, ввода и вывода. Хочется сделать программу, которая делла бы хоть что-нибудь существенное, да? Ну, не надо беспокоиться мы скоро начнем использовать изученный материал. Нам осталось чуть-чуть… Самую малость… Мы уже знаем как управляться с числами, самое время узнать что можно делать с текстом!
Манипуляция строками (или «текстом») еще удобнее чем использование класса «Math» с числами. Нам не нужен отдельный класс для выполнения специальных функций потому, что каждая строчная переменая переменная уже имеет в себе все, что нам нужно. Например, следующий код выводит в консоль строку состоящюю только из заглавных букв:
string test = » Тестируем различные функции СТРОК!»;
Console.WriteLine(test.ToUpper());
А вот и результат:
ТЕСТИРУЕМ РАЗЛИЧНЫЕ ФУНКЦИИ СТРОК!
А также мы можем сохранить результат конверсии в другой строковой переменной и использовыть ее позже:
string testUpper = test.ToUpper();
Console.WriteLine(testUpper);
Можем перезаписать значение переменной на конверсию ее же самой:

testUpper = testUpper.ToUpper();
Просто надо не забывать использовать «=» при присваивании значения конверсии. Следующий код не делает абсолютно ничего:
testUpper.ToUpper();
Также как использование ReadLine() без присваивания будет бесполезно, использование ToUpper() без присваивания тоже не сохранит результата… Вернее он уйдет в небытие… У ToUpper() есть близнец ToLower(). Интересно что он делает?
Не будем беспокоиться насчет такого глупого вопроса, поэтому продолжим — анализ строк. Можем использовать свойство строки «Length» для того, чтобы узнать количество символов в ней:
string name = «Bartholomew»;
int length = name.Length;
Console.WriteLine(«Длина имени: » + length);
Можно использовать функции StartsWith() и EndsWith() для того, чтобы узнать начинается или заканчивается ли строка определеной последовательностью символов. Смотрим:
Console.WriteLine(«Начинается на \»Ba\»?\t» + name.StartsWith(«Ba»));
Console.WriteLine(«Начинается на \»Bart\»?\t» + name.StartsWith(«Bart»));
Console.WriteLine(«Начинается на \»Barter\»?\t» + name.StartsWith(«Barter»));
Console.WriteLine(«Оканчивается на \»mew\»?\t» + name.EndsWith(«mew»));
Первые два утверждения выведут в консоли «True», ведь «Bartholomew» содержит и «Ba», и «Bart». Но, так как оно не начинается с «Barter», то результат третьего утверждения будет «False». Четвертая строка также выдаст «True» — «Bartholomew» оканчивается на «mew». Нам может быть интересно, каким же образом С# знает что писать в консоли — «True» или «False»… Дело в том, что BeginsWith() и EndsWith() возвращают значение такого типа, с которым мы еще не знакомы — «bool» (булевые). Это специальный тип переменных, который может принять только два значения — «True» или «False». Мы увидим в следующей главе, что они очень полезны, а пока просто проигнорируем их… Еще одна забавная вещь в строках — это возможность поиска отдельных слов и букв в них. Смотрим:
Console.WriteLine(«Первый индекс ‘B’: » + name.IndexOf(«B»));
Console.WriteLine(«Первый индекс ‘a’: » + name.IndexOf(«a»));
Console.WriteLine(«Первый индекс ‘r’: » + name.IndexOf(«r»));
Console.WriteLine(«Последний индекс ‘o’: » + name.LastIndexOf(«o»));
В результате получим:
Первый индекс ‘B’: 0
Первый индекс ‘a’: 1
Первый индекс ‘r’: 2
Последний индекс ‘o’: 7
На всякий случай oбъясняю: индекс означает позицию. Как видим, методы IndexOf() возвращают позицию искомого символа в строке(можно искать сразу несколько символов или текст, а не просто один символ как сейчас).
Надо заметить, что позиция(индекс) «В» является 0. Вторая буква, «а» считается на позиции 1 и т.д. Здесь, в С#, отсчет начинается с 0, а не с 1. Придется смириться с этим… LastIndexOf() работает примерно также, только он возвращает позицию последнего искомого символа/строки. Ну, теперь ниже мы можем находить первый и последние индексы в тексте. А что если нам надо найти тот, что между ними? В этом случае, вызываем метод IndexOf(), но уже предоставляем точку отсчета…
int firstIndex = name.IndexOf(«o»);
Console.WriteLine(«Первый индекс’o’: » + firstIndex);

int secondIndex = name.IndexOf(«o», firstIndex+1);
Console.WriteLine(«Второй индекс’o’: » + secondIndex);

int thirdIndex = name.IndexOf(«o», secondIndex+1);
Console.WriteLine(«Третий индекс’o’: » + thirdIndex);
Получим:
Первый индекс ‘o’: 5
Второй индекс ‘o’: 7
Третий индекс ‘o’: -1
вызов IndexOf() такой же как и в первый раз. Во втором случае же мы используем индекс, полученный ранее. Наверное интересно, почему к нему прибавлена единица? Потому что мы хотим продолжить поиск после первого индекса. Если начнем новый поиск с той же позиции, то опять получим первый индекс.
А что же насчет «-1», который мы получили при третьем вызове функции? Ну, «-1» имеет специальное значение в сфере программирования. Его часто используют для обозначения «неприменимый», или «вне досягаемости». И так как в «Bartholomew» нет третьего «о», становится прекрасно ясно, почему же мы получили результат «вне досягаемости».
А теперь давайте попробуем пару трюков из форматирования. Мы уже знаем как использовать «\t», поэтому попробуем кое-что более зощренное…
string season1 = «весна», season2 = «лето», season3 = «осень», season4 = «зима»;
Console.WriteLine(season1.PadLeft(10, ‘.’));
Console.WriteLine(season2.PadLeft(10, ‘.’));
Console.WriteLine(season3.PadLeft(10, ‘.’));
Console.WriteLine(season4.PadLeft(10, ‘.’));
Код выдаст следующий результат:
…..весна
……лето
…..осень
……зима
В этом случае, в PadLeft() мы передали 2 параметра: число и символ. (Заметим, что единичные символы заключены в одинарные кавычки, а строки в двойные). Число — это нужная нам длина строки. Как видим, каждая строка длиной в 10 символов. Если строка короче 10 символов, метод «набивает» строку, добавляя слева дополнительные символы, до тех пор, пока строка не достигнет нужной длины. Добавляется тот символ, который мы передали в функции. Существует также метод PadRight() , который делает то же самое, только справа.
Противоположное «набивке» — урезание. Можно использовать TrimStart() и TrimEnd() чтобы убрать нежелательные символы с начала и конца строки, или Trim() для удаления их с обоих концов. Они настолько просты, что понять можно с первого взгляда:
string rubbish = «……… привет…..»;
Console.WriteLine(rubbish);
Console.WriteLine(rubbish.TrimEnd(‘.’));
Console.WriteLine(rubbish.TrimStart(‘.’));
Console.WriteLine(rubbish.Trim(‘.’));
А вот и результат:
………привет…..
………привет
привет…..
привет
Просто, не правда ли? Мы передаем в Trim() тот символ, от которого хотим избавиться и оставляем всю грязную работу на С#. Последнее с чем нужно познакомимся — это подстрока. В программировании много случаев, когда надо «отрезать» определенный «кусочек» строки. Сделать это можно с помощью Substring(). Например:
string name2 = name.Substring(0, 5);
string name3 = name.Substring(4, 2);
string name4 = name.Substring(5, name.Length-5);
Console.WriteLine(name2);
Console.WriteLine(name3);
Console.WriteLine(name4);
Так как переменная «name» у нас все еще «Bartholomew», то получим следующий результат:
Barth
ho
olomew
Сначала мы передали в Substring() 0 — позицию старта и 5 — длину подстроки. Поэтому она начинается с первой буквы в стоке и продолжается следующими 4 буквами (всего 5 — ровно столько, сколько запросили). Вот почему результат — «Barth».
Нужно быть осторожным при использовании подстрок. Если запросите ее с параметрами больше длины строки, программа умрет. Например, «Bartholomew» состоит из 11 символов (можно использовотать метод Length, если так охота проверить). если бы мы запросили подстроку, начиная с 10 символа, а затем обрезая следующие 8 , программа умрет. Ведь после десятого есть только один символ!
Еще одна забавная вещь — возможность вставлять специальные символы в строку. Используем «\u», вместе с кодом спец. символа. Например, «\u263a» вставит «:)» в текст. Чтобы узнать коды всех спец символов можно воспользоваться утилитой «Character Map» из Start Menu\All Programs\Accsessories\System Tools\
Эти пять уроков, если их прочитать могут дать достаточно сильную основу для базовых навыков. Самое время создать программу, которая действительно будет что-то делать! Но сначала пример кода:
using System;

namespace E_StringManipulation
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// Конвертируем в строчные и заглавные буквы
string test = «Testing the various functions of STRINGS!»;
string testUpper = test.ToUpper();
string testLower = test.ToLower();
Console.WriteLine(test);
Console.WriteLine(testUpper);
Console.WriteLine(testLower);

// Находим длину, части слова
string name = «Bartholomew»;
Console.WriteLine(Environment.NewLine + «Name = » + name);
Console.WriteLine(«Длина имени: » + name.Length);
Console.WriteLine(«Начинается на \»Ba\»?\t» + name.StartsWith(«Ba»));
Console.WriteLine(«Начинается на \»Bart\»?\t» + name.StartsWith(«Bart»));
Console.WriteLine(«Начинается на \»Barter\»?\t» + name.StartsWith(«Barter»));
Console.WriteLine(«Оканчивается на \»mew\»?\t» + name.EndsWith(«mew»));

// Находим позицию текста в строке
Console.WriteLine(Environment.NewLine + «Первый индекс ‘B’: » + name.IndexOf(«B»));
Console.WriteLine(«Первый индекс ‘a’: » + name.IndexOf(«a»));
Console.WriteLine(«Первый индекс ‘r’: » + name.IndexOf(«r»));

int firstIndex = name.IndexOf(«o»);
Console.WriteLine(«Первый индекс ‘o’: » + firstIndex);

int secondIndex = name.IndexOf(«o», firstIndex + 1);
Console.WriteLine(«Второй индекс ‘o’: » + secondIndex);

int thirdIndex = name.IndexOf(«o», secondIndex + 1);
Console.WriteLine(«Третий индекс ‘o’: » + thirdIndex);

Console.WriteLine(«Последний индекс ‘o’: » + name.LastIndexOf(«o»));

// «Набивка»
string season1 = «весна», season2 = «лето», season3 = «осень», season4 = «зима»;
Console.WriteLine(Environment.NewLine + season1.PadLeft(10, ‘.’));
Console.WriteLine(season2.PadLeft(10, ‘.’));
Console.WriteLine(season3.PadLeft(10, ‘.’));
Console.WriteLine(season4.PadLeft(10, ‘.’));

// «Отрезание»
string rubbish = «………привет…..»;
Console.WriteLine(Environment.NewLine + rubbish);
Console.WriteLine(rubbish.TrimEnd(‘.’));
Console.WriteLine(rubbish.TrimStart(‘.’));
Console.WriteLine(rubbish.Trim(‘.’));

// Подстрока
string name2 = name.Substring(0, 5);
string name3 = name.Substring(4, 2);
string name4 = name.Substring(5, name.Length — 5);
Console.WriteLine(Environment.NewLine + name2);
Console.WriteLine(name3);
Console.WriteLine(name4);

// А теперь попросим пользователя нажать «Enter» для выхода из программы
Console.WriteLine(«Программа завершена. Нажмите \»Enter\» для выхода…»);
Console.ReadLine();

}
}
}

 

1.4 Math в C# (CSharp)

Математика — основа любой компьютерной программы. К счастью, ее довольно легко освоить. Вот простой пример сложения двух чисел:
int answer = 5 + 10;
Еще можно сложить две численные переменные, как буд-то они и есть числа:
int num1 = 15;
int num2 = 10;
int addAnswer = num1 + num2;
Остальные обычные операции работают так же. Мы используем «*» для умножения и «/» для деленеия:
int subAnswer = num1 — num2;
int mulAnswer = num1 * num2;
int divAnswer = num1 / num2;

Стоит опасаться деления, т.к. попытка деления на 0 может привести к краху программы. Конечно, целые не могут содержать в себе дроби, поэтому остаток будет просто отброшен при передаче ответа. Если нам нужна дробь, мы используем такой тип данных, где присутствует дробь, например double. А что, если нам нужен действительный остаток, а не его дробное представление? И на это есть свой прием:
int modAnswer = num1 % num2;
Оператор «%» в английском называется modulus. Он находит остаток от деления одного числа на другое. Например, 11 % 5 в результате вернет 1 (ведь 5 содержится в 11 два раза. Остаток 1). Замечу, что мы не ограничены количеством операций. Следующий код замечательно сработает:
int bigAnswer = (num1 — num2) * num2 + num1;
Надо запомнить, что сначала выполнается выражение в скобках, потом умножение(или деление), а затем сложение(или вычитание). Так принято в математике, поэтому не буду углублятся в это.
.NET framework содержит библиотеку с обычными математическими функциями, известную как библиотека «Math». Напримет, мы имеем два числа и хотим знать, которое из них больше. Просто делаем так:
int biggestNumber = Math.Max(num1, num2);
… теперь «biggestNumber» будет содержать значение либо num1 или num2, вернее , набольшее. Все точно так же работает и с наименьшим числом:
int smallestNumber = Math.Min(num1, num2);
А что насчет округления? Это конечно хорошо, иметь точные данные в переменных, но обычно глазу не очень приятно видеть такие числа как 42.4351433462 . А округляем мы так:
double num3 = 94.537;
double twoDecimalPlaces = Math.Round(num3, 2));
Эта команда округлит значение num3 до двух чисел после запятой, т.е. получим 94.54. Первое значени, которое мы передали, «num3» — это число, которое нам надо округлить. «2» определяет то количество цифр после запятой, которое мы хотим увидеть в результате. Если бы нужно было округлить число до близжайшего целого, мы бы просто вписали «0» :
double noDecimalPlaces = Math.Round(num3, 0));
А результат, конечно же будет 95. Кстати, если нам когда-нибудь понадобится использовать число Пи (РІ), то для этого есть специальное средство:
double valueOfPI = Math.PI;
Так как Пи не меняется, то нам не обязательно держать его значение в переменной. Можно использовать Math.PI в случае надобности, вот так:
double circumference = 50 * Math.PI;
Вычисление степеней тоже просто. Следующий код прямо выводит значение степеней двойки (без использования переменных) :
Console.WriteLine(«2 в степени 2 = » + Math.Pow(2, 2));
Console.WriteLine(«2 в степени 3 = » + Math.Pow(2, 3));
Console.WriteLine(«2 в степени 4 = » + Math.Pow(2, 4));
И еще кое-что очень важное! Обратим внимание на следующий код:
double badAnswer = 12 / 5;
Можно подумать, что если мы используем double для хранения результата деления, то она будет содержать и дробную часть. На самом деле — нет. Из-за того, что мы делим два целых , 12 и 5, С# думает что мы хотим произвести деление целых, не обращая внимание на тип переменной, в которой будет ответ. Так или иначе, мы можем заставить С# принимать эти числа как double-и, во так:
double goodAnswer = 12.0 / 5.0;
Хотя это не меняет значение чисел, это все-таки меняет то, как С# видит их. Из-за того, что мы добавили десятичную точку, С# теперь думает, что мы работаем с double. А что если :
goodAnswer = 12 / 5.0;
Не смотря на то, что один из чисел операции — целое, С# все равно будет обращаться с ней как с дробным, а не целым делением. Если выражение содержит несколько членов разных типов, С# автоматически будет использовать самый точный из возможных типов деления (опираясь на типы переменных в выражении). В данном случае самый точный тип — double, поэтому С# обращается с обоими членами как с double-и.
А как начет переменных? Ну, следующий код даст тот же «плохой» результат, как и раньше:
int numX = 12;
int numY = 5;
badAnswer = numX / numY;
Из-за того, что обе переменных целые, происходит «целое» деление и остаток будет потерян. Чтобы этого избежать, мы должны «приведение» («cast») для хотя бы одного члена выражения. Следующий код заставляет С# думать, что numX — double, значит и действия проводит как над дробями:
goodAnswer = (double)numX / numY;
Чаще всего в замешательство приводят ошибки именно такого типа: мы проводим «целое» деление, не осознавая этого. Так что, всегда надо быть внимательным к типу данных при делении чисел!
А теперь самое время для примерного кода, в котором можно увидеть все, что мы сегодня узнали:
using System;

namespace D_Math
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
// Объявим два числа
int num1 = 15;
int num2 = 10;
// Произведем над ними математические действия(сохраним результаты)
int addAnswer = num1 + num2;
int subAnswer = num1 — num2;
int mulAnswer = num1 * num2;
int divAnswer = num1 / num2;
int modAnswer = num1 % num2;
int bigAnswer = (num1 — num2) * num2 + num1;
// Выведем результат
Console.WriteLine(«number1 == {0}, и number2 == {1}…», num1, num2);
Console.WriteLine(«Результат сложения: » + addAnswer);
Console.WriteLine(«Результат вычитания: » + subAnswer);
Console.WriteLine(«Результат умножения: » + mulAnswer);
Console.WriteLine(«Результат деления: » + divAnswer);
Console.WriteLine(«Остаток деления: » + modAnswer);
Console.WriteLine(«Результат \»(num1 — num2) * num2 + num1\»: » + bigAnswer);

// Обычное сравнение чисел (не сохраняем результаты)
Console.WriteLine(«Наибольшее число: » + Math.Max(num1, num2));
Console.WriteLine(«Наименьшее число: » + Math.Min(num1, num2));

// Округление
double num3 = 94.537;
Console.WriteLine(Environment.NewLine + «Оригинальное число:\t» + num3);
Console.WriteLine(«Округляем до…»);
Console.WriteLine(«2 цифры после запятой:\t» + Math.Round(num3, 2));
Console.WriteLine(«1 цифра после запятой:\t» + Math.Round(num3, 1));
Console.WriteLine(«0 цифр после запятой:\t» + Math.Round(num3, 0));

// Пи? — Нет ничего легче!
Console.WriteLine(Environment.NewLine + «Пи = » + Math.PI);

// Степени
Console.WriteLine(Environment.NewLine + «2 в степени 1 = » + Math.Pow(2, 1));
Console.WriteLine(«2 в степени 2 = » + Math.Pow(2, 2));
Console.WriteLine(«2 в степени 3 = » + Math.Pow(2, 3));
Console.WriteLine(«2 в степени 4 = » + Math.Pow(2, 4));

// Простенький калькулятор
Console.Write(Environment.NewLine + «Введите 1-е число для умножения: «);
double mul1 = Convert.ToInt32( Console.ReadLine() );
Console.Write(«Введите 2-е число: «);
double mul2 = Convert.ToInt32( Console.ReadLine() );
Console.WriteLine(«{0} * {1} = {2}», mul1, mul2, (mul1 * mul2));

// Опасно! Неправильное деление! Не влезай, убъет!
Console.WriteLine(Environment.NewLine + «12 / 5 = ?»);
double badAnswer = 12 / 5;
double goodAnswer = 12.0 / 5.0;
Console.WriteLine(«Неправильый результат: {0}\n
Правильый результат: {1}»,
badAnswer, goodAnswer);

// А теперь попросим пользователя нажать «Enter» для выхода из программы
Console.WriteLine(«Программа завершена. Нажмите \»Enter\» для выхода…»);
Console.ReadLine();
}
}
}

 

Домены приложений в .NET Framework

Домены приложений (application domains) — важное нововведение в .NET, предназначенное для снижения накладных расходов, связанных с запуском приложений, которые должны быть изолированы друг от друга, но при этом нуждаются во взаимодействии между собой. Классический пример — приложения Web-сервера, которые могут совместно отвечать на множество запросов браузеров, а потому должны, вероятно, иметь экземпляры компонента, ответственного за параллельное обслуживание таких запросов.
Во времена, предшествовавшие появлению .NET, приходилось выбирать между тем, чтобы позволить этим экземплярам разделять один и тот же процесс (в результате рискуя прекращением работы всего Web-сайта в случае возникновения проблем с одним использующимся экземпляром) и изолированием этих экземпляров в отдельных процессах, что приводило к росту накладных расходов и снижению производительности.
До последнего времени изоляция кода была возможна только между процессами. Когда запускается новое приложение, оно работает в контексте процесса. Windows изолирует процессы друг от друга посредством адресных пространств. Идея состоит в том, что каждому процессу предоставляется 4Гбайт виртуальной памяти, в которой он сохраняет свои данные и исполняемый код (4Гбайт — это для 32-разрядных систем, 64-разрядные системы используют больший обьем памяти). Windows вводит дополнительный уровень переадресации, благодаря которому эта виртуальная память отображается на определенную область реальной физической памяти или дисковое пространство. Каждый процесс получает свое отображение, при этом гарантируется, что не случится никакого перекрытия между реальными физическими областями памяти, выделенными каждому из них (Рис.1).

Рис.1 Отображение виртуальной памяти процессов на физическую память

В общем случае любой процесс может обратиться к памяти только по адресу виртуальной памяти — процессы не имеют прямого доступа к физической памяти. А потому попросту невозможно одному процессу получить доступ к памяти, выделенной для другого. Это гарантирует, что никакой код, который «плохо» себя ведет, не сможет повредить ничего вне собственного адресного пространства.
Но процессы не только служат для изоляции экземпляров работающего кода друг от друга. В системах Windows NT/2000/XP/2003 они также формируют элементы, которым назначаются права доступа и привилегии безопасности. Каждый процесс снабжен своим собственным маркером доступа (security token), который указывает системе, какие именно действия разрешено выполнять этому процессу.
Хотя процессы удобны для обеспечения безопасности, все же они имеют серьезный недостаток, связанный с производительностью. Часто бывает так, что множество процессов работают вместе и потому нуждаются в общении друг с другом. Очевидный пример этого — это когда процесс вызывает компонент COM, который является исполняемым, а потому запускает свой собственный процесс. То же случается в COM, когда используется «заместитель» (surrogate). Поскольку процесс на может разделить память с другими, должен быть применен сложный процесс маршалинга для копирования данных между процессами. В результате ощутимо страдает производительность. Если вам нужно, чтобы компоненты работали вместе без снижения производительности, то вам следует использовать компоненты, основанные на DLL, чтобы все работало в одном адресном пространстве, подвергаясь риску, что один плохо себя ведущий компонент разрушит все остальные. Домены приложений спроектированы как способ разделения компонентов без сопутствующих проблем снижения производительности, связанных с передачей данных между процессами. Идея заключается в том, что каждый процесс разделяется на несколько доменов приложений. Каждый домен приложения приблизительно соответствует отдельному приложению, и каждый поток исполнения запускается в определенном домене приложения. (Рис.2).

Рис.2 Домены приложений

Если разные исполняемые программы запускаются в одном пространстве процесса, то вполне понятно, что они могут легко разделять общие данные, потому что теоретически в состоянии напрямую видеть данные друг друга. Однако, хотя такая возможность существует впринципе, CLR обеспечивает, чтобы это не случилось на практике, инспектируя код каждого работающего приложения, дабы гарантировать, что код не выйдет за пределы области своих собственных данных. На первый взгляд этот трюк кажется почти невозможным — в конце концов, как вы можете знать, что собирается делать программа, до того как запустите ее?
На самом деле, обычно это сделать возможно, благодаря строгой защите типов IL. В большинстве случает, если только код не применяет небезопасных средств, такх как указатели, то используемые им типы данных гарантируют, что не произойдет никаких неправильныз обращений к памяти. Например типы массивов в .NET предусматривают проверку границ, чтобы не допустить никаких действий за границами массива. Если функциунирующее приложение нуждается во взаимодействии или разделении данных с другим работающим приложением, оно должно делать это посредством вызова удаленных служб (remote services).
Код, верифицированный на предмет того, что он не может обращаться к памяти вне домена приложения (кроме как явным использованием удаленных механизмов), назвают безопасным к типам в памяти (memory safe-type). Такой код может быть безопасно запущен параллельно с другим безопасным к типам кодом в разных доменах приложений в пределах одного процесса.

 

Сборка мусора в .NET Framework

Сборщик мусора — это ответ .NET на вопросы управления памятью, в частности, на вопрос о том, что делать с восстановлением памяти, которую запрашивает приложение. До настоящего времени на платформе Windows использовалось две технологии освобождения памяти, выделенной системой по динамическим запросам:
Возложение этой задачи на код приложения.
Поддержка счетчиков ссылок на обьекты.
Возложение ответственности за освобождение памяти на код приложения — это техника, используемая низкоуровневыми высокопроизводительными языками, такими как С++. Это эффективно и обладает тем преимуществом, что ресурсы никогда не бывают заняты дольше, чем это необходимо. Однако большой недостаток такого подхода состоит в том, что он часто порождает ошибки. Код, запрашивающий память, также обязан явно информировать систему, когда он перестает в ней нуждаться. Однако об этом очень легко забыть, что приводит к утечкам памяти.

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

Поддержка счетчиков ссылок — это техника, которая используется в COM. Идея заключается в том, что каждый компонент COM поддерживает счетчик клиентов, которые в данный момент ссылаются на него. Когда значение счетчика падает до нуля, компонент может уничтожить себя и освободить ассоциированную с ним память и ресурсы. При таком подходе проблема сводится к обязанности клиента «корректно себя вести» и вовремя извещать компонент о том, что он завершил с ним работать. Стоит одному клиенту не сделать этого, и обьект останется в памяти. Иногда эта проблема потенциально более серьезна, чем простая утечка памяти в С++, потому что COM-компонент может существовать внутри своего собственного процесса, а это значит, что он никогда не будет удален системой.

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

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

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