Введение в программирование на C# 2.0

Интерфейсы

Разбить на страницы
Показывать лекцию целиком

Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных-членов и объявлений ОБЫЧНЫХ функций.

Все без исключения функции — члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою "абстрактность", объявляются без ключевого слова abstract.

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

Объявление интерфейса

using System;

namespace Interface01
{

// Интерфейсы.
// Этот интерфейс характеризуется уникальными
// именами объявленных в нем методов.
interface Ix
{
void IxF0(int xKey);
void IxF1();
}

// Пара интерфейсов, содержащих объявления одноименных методов
// с одной и той же сигнатурой. 
interface Iy
{
void F0(int xKey);
void F1();
}

interface Iz
{
void F0(int xKey);
void F1();
}

// А этому интерфейсу уделим особое внимание.
// Он содержит тот же набор методов, но в производном классе этот 
// интерфейс будет реализован явным образом. 
interface Iw
{
void F0(int xKey);
void F1();
}

// В классе TestClass наследуются интерфейсы...
class TestClass:Ix
	        ,Iy
	        ,Iz
	        ,Iw
{
public int xVal; 

// Конструкторы.
public TestClass()
{
	xVal = 125;
}

public TestClass(int key)
{
	xVal = key;
}


// Реализация функций интерфейса Ix.
// Этот интерфейс имеет специфические названия функций.
// В данном пространстве имен его реализация неявная и однозначная.
public void IxF0(int key)
{
	xVal = key*5;
	Console.WriteLine("IxF0({0})...", xVal);
}

public void IxF1()
{
	xVal = xVal*5;
	Console.WriteLine("IxF1({0})...", xVal);
}

// Реализация интерфейсов Iy и Iz в классе TestClass неразличима.
// Это неявная неоднозначная реализация интерфейсов.
// Однако неважно, чью конкретно функцию реализуем.
// Оба интерфейса довольны...
public void F0(int xKey)
{
	xVal = (int)xKey/5;
	Console.WriteLine("(Iy/Iz)F0({0})...", xVal);
}

public void F1()
{
	xVal = xVal/5;
	Console.WriteLine("(Iy/Iz)F1({0})...", xVal);
}

// А это явная непосредственная реализация интерфейса Iw.
// Таким образом, класс TestClass содержит ТРИ варианта
// реализации функций интерфейсов с одной и той же сигнатутой.
// Два варианта реализации неразличимы.
// Третий (фактически второй) вариант реализации
// отличается квалифицированными именами.  
void Iw.F0(int xKey)
{
	xVal = xKey+5;
	Console.WriteLine("Iw.F0({0})...", xVal);
}

void Iw.F1()
{
	xVal = xVal–5;
	Console.WriteLine("Iw.F1({0})...", xVal);
}

public void bF0()
{
	Console.WriteLine("bF0()...");
}
}
class Class1
{
static void Main(string[] args)
{

TestClass x0 = new TestClass();
TestClass x1 = new TestClass(5);

x0.bF0();

// Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию
// интерфейса Ix.
x0.IxF0(10);
x1.IxF1();

// Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию
// интерфейсов Iy и Iz. 
x0.F0(5); 
x1.F1();
    
// А вот вызов функций с явным приведением к типу интерфейса.
// Собственный метод класса bF0() при подобных преобразованиях
// не виден.
(x0 as Iy).F0(7);
(x1 as Iz).F1();

// А теперь настраиваем ссылки различных типов интерфейсов
// на ОДИН И ТОТ ЖЕ объект – представитель класса TestClass.
// И через "призму" интерфейса всякий раз объект будет 
// выглядеть по-разному.
Console.WriteLine("==========Prism test==========");

Console.WriteLine("==========Ix==========");
Ix ix = x1;
ix.IxF0(5);
ix.IxF1();

Console.WriteLine("==========Iy==========");
Iy iy = x1;
iy.F0(5);
iy.F1();
Console.WriteLine("==========Iz==========");
Iz iz = x1;
iz.F0(5);
iz.F1();

Console.WriteLine("==========Iw==========");
Iw iw = x1;
iw.F0(10);
iw.F1();
}
}
}

Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. При этом имеет место еще один вариант полиморфности: объект, представленный ссылкой-интерфейсом, способен проявлять различную функциональность в зависимости от реализации функций наследуемого интерфейса в классе данного объекта. И здесь все определяется спецификой данной конкретной реализации:

using System;

namespace Interface02
{
// Объявляются два интерфейса, каждый из которых содержит объявление
// одноименного метода с единственным параметром соответствующего типа.

interface ICompare0
{
bool Eq(ICompare0 obj);
} 

interface ICompare1
{
bool Eq(ICompare1 obj);
}
// Объявляются классы, наследующие оба интерфейса.
// В каждом из классов реализуются функции интерфейсов. 
// В силу того, что объявленные в интерфейсах методы – одноименные,
// в классах применяется явная реализация методов интерфейсов.
// Для классов-наследников интерфейсы представляют собой всего лишь
// базовые классы. Поэтому в соответствующих функциях сравнения
// допускается использование операции as.
//____________________________________________________________.  	
class C0:ICompare0,ICompare1
{

public int commonVal;
int valC0;
public C0(int commonKey, int key)
{
commonVal = commonKey;
valC0 = key;
}

// Метод реализуется для обеспечения сравнения объектов
// СТРОГО одного типа – C0.
bool ICompare0.Eq(ICompare0 obj)
{
C0 test = obj as C0;
if (test == null) return false;
if (this.valC0 == test.valC0)
	return true;
else
	return false;
}


// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
C1 test = obj as C1;
if (test == null) return false;
if (this.commonVal == test.commonVal)
	return true;
else
	return false;
}
}
//____________________________________________________________.  	

class C1:ICompare0,ICompare1
{
public int commonVal;
string valC1;

public C1(int commonKey, string key)
{
commonVal = commonKey;
valC1 = string.Copy(key);
}

// В классе C1 при реализации функции интерфейса ICompare0 реализован
// метод сравнения, который обеспечивает сравнение как объектов типа C1,
// так и объектов типа C0.
bool ICompare0.Eq(ICompare0 obj)
{
C1 test;

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

test = obj as C1;
 if (test == null) 
	return ((ICompare1)this).Eq((ICompare1)obj);

if (this.valC1.Equals(test.valC1))
  return true;
else
return false;
}

// Метод реализуется для обеспечения сравнения объектов разного типа.
bool ICompare1.Eq(ICompare1 obj)
{
 C0 test = obj as C0;
 if (test == null) return false;
 if (this.commonVal == test.commonVal)
  return true;
 else
return false;
}
}

//===============================================================
// Место, где порождаются и сравниваются объекты.  
class Class1
{
static void Main(string[] args)
{
  C0 x1 = new C0(0,1);
  C0 x2 = new C0(1,1);
    
// В выражениях вызова функций - членов интерфейсов НЕ ТРЕБУЕТСЯ 
//	явного приведения значения параметра к типу интерфейса.
// Сравнение объектов - представителей одного класса (C0).
   if ((x1 as ICompare0).Eq(x2))
	 Console.WriteLine("Yes!");
   else                     
	 Console.WriteLine("No!");

  C1 y1 = new C1(0,"1");
   C1 y2 = new C1(1,"1");

// Сравнение объектов - представителей одного класса (C1).
   if ((y1 as ICompare0).Eq(y2))
	Console.WriteLine("Yes!");
   else                     
	Console.WriteLine("No!");

// Попытка сравнения объектов - представителей разных классов.
    if (((ICompare0)x1).Eq(y2))
	Console.WriteLine("Yes!");
    else                     
	Console.WriteLine("No!");


   if ((x1 as ICompare1).Eq(y2))
	Console.WriteLine("Yes!");
    else                     
	Console.WriteLine("No!");


   if (((ICompare1)y2).Eq(x2))
	Console.WriteLine("Yes!");
    else                     
	Console.WriteLine("No!");

// Здесь будет задействован метод сравнения, реализованный
// в классе C0 по интерфейсу ICompare0, который не является универсальным.
// Отрицательный результат может быть получен не только
// по причине неравенства значений сравниваемых величин,
// но и по причине несоответствия типов  операндов.
   Console.WriteLine("__________x2==y2__________");
   if ((x2 as ICompare0).Eq(y2))
	Console.WriteLine("Yes!");
   else                     
	Console.WriteLine("No!");

// А здесь вероятность положительного результата выше, поскольку в классе
// C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ.
// И это значит,
// что данный метод никогда не вернет отрицательного значения по причине
// несоответствия типов операндов. 
  Console.WriteLine("__________y2==x2__________");
   if ((y2 as ICompare0).Eq(x2))
	Console.WriteLine("Yes!");
   else                     
	Console.WriteLine("No!");
}
}
}

Реализация сортировки в массиве. Интерфейс IComparable

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

Класс Array является основой для любого массива и для любого массива предоставляет стандартный набор методов для создания, манипулирования (преобразования), поиска и сортировки на множестве элементов массива.

В частности, варианты метода Sort() обеспечивают реализацию механизмов сортировки элементов ОДНОМЕРНОГО массива объектов (в смысле представителей класса Object ).

Сортировка элементов предполагает:

  • ПЕРЕБОР всего множества (или его части) элементов массива;
  • СРАВНЕНИЕ (значений) элементов массива в соответствии с определенным критерием сравнения.
  • При этом критерии для сравнения и алгоритмы сравнения могут задаваться либо с помощью массивов ключей, либо реализацией интерфейса IComparable, который специфицирует характеристики (тип возвращаемого значения, имя метода и список параметров) методов, обеспечивающих реализацию данного алгоритма. Далее будут рассмотрены различные варианты метода Sort.

  • Вариант метода Sort сортирует полное множество элементов одноразмерного массива с использованием алгоритма, который реализуется методами, специфицированными стандартным интерфейсом сравнения:
    public static void Sort(Array);
  • Реализованный на основе интерфейса алгоритм способен распознавать значения элементов массива и сравнивать эти элементы между собой, если это элементы предопределенных типов.

    Таким образом, никаких проблем не существует, если надо сравнить и переупорядочить массивы элементов, для которых известно, КАК СРАВНИВАТЬ значения составляющих массив элементов. Относительно ..., System.Int16, System.Int32, System.Int64, ...,System.Double,... всегда можно сказать, какой из сравниваемых элементов больше, а какой меньше.

  • Сортировка элементов массива (массива целевых элементов) также может быть произведена с помощью вспомогательного массива ключей:
    public static void Sort(Array, Array);
  • Суть подобной сортировки заключается в том, что между элементами массива ключей и элементами сортируемого массива устанавливается соответствие. В частном случае, если количество элементов массива ключей оказывается равным количеству элементов сортируемого массива, это соответствие оказывается взаимнооднозначным. При этом сортируются элементы массива ключей. Предполагается, что используемый в этом случае алгоритм сравнения способен обеспечить необходимое сравнение.

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

    using System;
    namespace SortArrays
    {
    class Class1
    {
    static void Main(string[] args)
     {
    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив. 
    int[] keys = {10,1,2,3,4,5,6,7,8,9}; // Массив ключей.
    for (int i = 0; i < 10; i++) Console.WriteLine("{0}: {1}", i, iArr[i]);
    // Сортировка массива с использованием ключей.
    System.Array.Sort(keys,iArr);
    Console.WriteLine("============================================");
    for (i = 0; i < 10; i++) Console.WriteLine("{0}: {1}", i, iArr[i]);
     }
    }
    }

    В процессе сортировки массива ключей элемент со значением ключа 10 перемещается в конец массива. При этом нулевой элемент целевого массива со значением 0 перемещается на последнюю позицию.

    Целевой массив и массив ключей вида при сортировке

    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив.
    int[] keys = {9,8,7,6,5,4,3,2,1,0}; // Массив ключей.

    обеспечивают размещение элементов целевого массива в обратном порядке.

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

    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив.
    int[] keys = {3,2,1,0}; // Массив ключей.

    А такие наборы значений целевого и ключевого элементов массива при сортировке с использованием массива ключей обеспечивают изменение порядка целевых элементов массива с индексами 4, 5, 6, 7.

    int[] iArr = {0,1,2,3,4,5,6,7,8,9};
    int[] keys = {0,1,2,9,8,7,6,10,11,12};

    Но самое главное и самое интересное в методе сортировки с использованием ключевого массива – это то, что в этом никаким образом не участвуют значения элементов целевого массива.

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

    int[][] iArr = new int[3][]{
    new int[]{0},
    new int[]{0,1},
    new int[]{0,1,2,3,4,5,6,7,8,9}
     };
    int[] keys = {3,2,1};
  • Вариант метода, который позволяет организовать сортировку подмножества элементов целевого массива, начиная с элемента, заданного вторым параметром метода, и включая количество элементов, заданное третьим параметром:
    public static void Sort(Array,int,int);
  • Вариант метода сортировки, основанный на сортировке элементов ключевого массива. Обеспечивает сортировку подмножества массива. Принципы выделения подмножества аналогичны рассмотренному выше варианту метода сортировки:
    public static void Sort(Array,Array,int,int);
  • Сортировка на основе сравнения пар объектов — членов одномерного массива с использованием интерфейса сравнения:
    public static void Sort(Array,IComparer);
    
    // Сортировка элементов с использованием стандартного Компарера.
    // iArr – одномерный массив целочисленных значений.
    // System.Collections.Comparer.DefaultInvariant – свойство,
    // которое обеспечивает ссылку на объект "стандартный Компарер",
    // реализующий предопределенные алгоритмы сравнения.
    try
    {
    System.Array.Sort(iArr,System.Collections.Comparer.DefaultInvariant);
    }
    catch (Exception ex)
    {
    Console.WriteLine(ex.ToString());
    }
  • System.Collections.Comparer.DefaultInvariant – свойство, которое обеспечивает доступ к объекту, реализующему обобщенный метод сравнения.

    Для любого класса на основе интерфейса IComparer можно создать собственный класс-КОМПАРЕР, в котором можно будет реализовать специфический для данного класса сортируемых элементов метод сравнения (interface implemented by each element of the Array).

    using System;
    using System.Collections;
    
    namespace SortArrays
    {
    // Данные для массива элементов.
    // Подлежат сортировке в составе массива методом Sort.
    // Главная проблема заключается в том, что никто из тех, кто обеспечивает
    // встроенную сортировку, ни в классе Array, ни в классе Points не знает,
    // как воспринимать и каким образом сравнивать между собой
    // объекты-представители класса Points. Объекты p0 и p1
    // со значениями координат (7,5) и (5,7) – кто из них "больше"?
    class Points
    {
    // Конечно, можно говорить о введении аксиом для установления отношения
    // порядка между элементами множества объектов – представителей класса
    // Points и о реализации соответствующих методов сравнения;
    // также можно было бы позаботиться при объявлении класса о том,
    // чтобы просто не допускать к стандартной сортировке
    // объекты классов, в которых не определено
    // отношение порядка. Однако в .NET используется другой подход.
    public int x;
    public int y;
    public Points (int key1, int key2)
    {
     x = key1;
     y = key2;
    }
    // Вычисляется расстояние от начала координат.
    public int R
    {
    get
    {
     return (int)(Math.Sqrt(x*x+y*y));
    }     
    }   
    }
        
    // Предполагается, что отношение порядка над элементами
    // множества объектов – представителей соответствующего
    // класса может быть введено в любой момент. Для этого
    // достаточно иметь четкое представление о критериях
    // установления порядка на множестве объектов – представителей
    // данного класса и иметь доступ к значениям соответствующих
    // полей объектов.
    // Стандартные библиотечные средства .NET позволяют
    // реализовать (доопределить) соответствующие интерфейсы
    // (заготовки) для реализации методов сравнения.
    // Ниже объявляется вариант процедуры сравнения,
    // использование которого позволяет реализовать стандартные
    // алгоритмы сортировки, применяемые в классе Array.
    // ...КОМПАРЕР...   
    
    class myComparer: IComparer
    {
    // Предлагаемый метод сравнения возвращает разность расстояний
    // двух точек (вычисляется по теореме Пифагора) от начала координат
    // – точки с координатами (0,0). Чем ближе точки к началу координат
    // – тем они меньше. 
    	
    int IComparer.Compare(object obj1, object obj2)
    {
        return ((Points)obj1).R - ((Points)obj2).R; 
    }
    }
    
    // После реализации соответствующего интерфейса объект-КОМПАРЕР
    // обеспечивает реализацию стандартного алгоритма сортировки.
    class Class1
    {
    
    static void Main(string[] args)
    {
        // Объект-генератор "случайных" чисел. 
    	Random rnd = new Random();
    
    	int i;
        // Массив Points.
        Points[] itArr = new Points[10];
    	
       // Создали Компарер, способный сравнивать пары
    	// объектов-представителей класса Points.  
    	myComparer c = new myComparer();
    	
    Console.WriteLine("============================================");
    
    	// Проинициализировали массив объектов – представителей класса Points. 
    	for (i = 0; i < 10; i++)
    	{
    		itArr[i] = new Points(rnd.Next(0,10),rnd.Next(0,10));
    	}
    
    	for (i = 0; i < 10; i++)
    	{
    		Console.WriteLine("{0}: {1},{2}", i, itArr[i].x, itArr[i].y);
    	}
    
    	// Сортируются элементы массива типа Points.
    	// Условием успешной сортировки элементов массива является реализация
    	// интерфейса IComparer. Если Компарер не сумеет справиться с
    	// поставленной задачей – будет возбуждено исключение.
    	try
    	{
    		System.Array.Sort(itArr,c);
    	}
    	catch (Exception ex)
    	{
    			Console.WriteLine(ex);			
    	}
    
    	Console.WriteLine("============================================");
    	for (i = 0; i < 10; i++)
    	{
    		Console.WriteLine("{0}: {1},{2}", i, itArr[i].x, itArr[i].y);
    	}
    
    	Console.WriteLine("============================================");
    }
    }
    }

    Интерфейс IDisposable. Освобождение ресурсов

    Совмещение освобождения ресурсов с удалением объекта-владельца данного ресурса называется недетерминированным освобождением. Зависящее от деятельности сборщика мусора недетерминированное освобождение ресурсов не всегда является оптимальным решением:

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

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

    В .NET с этой целью используется интерфейс IDisposable с методом Dispose.

    public interface IDisposable
    {
     void Dispose();
    }

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

    Следующие правила определяют в общих чертах рекомендации по использованию метода Dispose.

    В методе Dispose освобождаются любые ресурсы, которыми владеет объект данного типа и которые можно освободить.

    Если для освобождения ресурсов, которыми владеет объект, не был вызван метод Dispose, неуправляемые ресурсы должны освобождаться в методе Finalize.

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

    Метод Dispose должен освобождать все ресурсы, удерживаемые данным объектом и любым объектом, которым владеет данный объект.

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

    using System;
    
    namespace TestD00
    {
    public class MemElem : IDisposable
    {
    bool dFlag;
    int i;
    public MemElem(int iKey)
    {
    dFlag = true;
    i = iKey;
    }
    
    ~MemElem() // Деструктор (он же Finalizer)
    {
    Console.WriteLine("{0} disposed : {1}!", i, (dFlag == true) ? "yes" : "no");
    // Если ресурсы не освобождены – вызывается Dispose. 
    if (!dFlag) Dispose();
    }
    // Детерминированное управление ресурсом:
    // имитация активности.
    public void Dispose()
    {
    if (dFlag==true)
    {
     Console.WriteLine("Dispose is here!");
     dFlag = false;
    } 
    else
    {
     Console.WriteLine("Dispose was here!");
    }  
    }               
    }
        
    class Program
    {
    static void Main(string[] args)
    {
    IDisposable d;
    MemElem mmm = null;
    int i;
    for (i = 0; i < 10; i++)
    {
    MemElem m = new MemElem(i);
    if (i == 5) 
    {
     d = m as IDisposable;
     if (d!=null) d.Dispose();
     mmm = m;
     GC.SuppressFinalize(mmm); // И закрыли для кода деструктора.
    }
    }
    
    Console.WriteLine("-----0-----");
    GC.Collect();
    Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));
    // Заменить false на true. Осознать разницу.
    Console.WriteLine("-----1-----");
    // А теперь вновь разрешили его удалить...
    // Пока существует хотя бы одна ссылка – GC все равно не будет
    // уничтожать этот объект. 
    if (mmm != null) GC.ReRegisterForFinalize(mmm);
    mmm = null; // Вот потеряли ссылку на объект.
    // Теперь GC может уничтожить объект. Если разрешена финализация - 
    // будет выполнен код деструктора. Возможно, что отработает метод
    // Dispose. Если финализация запрещена -
    // код деструктора останется невыполненным.
    Console.WriteLine("-----2-----");
    GC.Collect();
    // Заменить false на true. Осознать разницу.
    Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));
    Console.WriteLine("-----3-----");
    }
    }
    }
    Страницы:

    Фактически это те же самые абстрактные классы, НЕ СОДЕРЖАЩИЕ объявлений данных-членов и объявлений ОБЫЧНЫХ функций.

    Все без исключения функции — члены интерфейса – абстрактные. Поэтому интерфейс объявляется с особым ключевым словом interface, а функции интерфейса, несмотря на свою "абстрактность", объявляются без ключевого слова abstract.

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

    Объявление интерфейса

    using System;
    
    namespace Interface01
    {
    
    // Интерфейсы.
    // Этот интерфейс характеризуется уникальными
    // именами объявленных в нем методов.
    interface Ix
    {
    void IxF0(int xKey);
    void IxF1();
    }
    
    // Пара интерфейсов, содержащих объявления одноименных методов
    // с одной и той же сигнатурой. 
    interface Iy
    {
    void F0(int xKey);
    void F1();
    }
    
    interface Iz
    {
    void F0(int xKey);
    void F1();
    }
    
    // А этому интерфейсу уделим особое внимание.
    // Он содержит тот же набор методов, но в производном классе этот 
    // интерфейс будет реализован явным образом. 
    interface Iw
    {
    void F0(int xKey);
    void F1();
    }
    
    // В классе TestClass наследуются интерфейсы...
    class TestClass:Ix
    	        ,Iy
    	        ,Iz
    	        ,Iw
    {
    public int xVal; 
    
    // Конструкторы.
    public TestClass()
    {
    	xVal = 125;
    }
    
    public TestClass(int key)
    {
    	xVal = key;
    }
    
    
    // Реализация функций интерфейса Ix.
    // Этот интерфейс имеет специфические названия функций.
    // В данном пространстве имен его реализация неявная и однозначная.
    public void IxF0(int key)
    {
    	xVal = key*5;
    	Console.WriteLine("IxF0({0})...", xVal);
    }
    
    public void IxF1()
    {
    	xVal = xVal*5;
    	Console.WriteLine("IxF1({0})...", xVal);
    }
    
    // Реализация интерфейсов Iy и Iz в классе TestClass неразличима.
    // Это неявная неоднозначная реализация интерфейсов.
    // Однако неважно, чью конкретно функцию реализуем.
    // Оба интерфейса довольны...
    public void F0(int xKey)
    {
    	xVal = (int)xKey/5;
    	Console.WriteLine("(Iy/Iz)F0({0})...", xVal);
    }
    
    public void F1()
    {
    	xVal = xVal/5;
    	Console.WriteLine("(Iy/Iz)F1({0})...", xVal);
    }
    
    // А это явная непосредственная реализация интерфейса Iw.
    // Таким образом, класс TestClass содержит ТРИ варианта
    // реализации функций интерфейсов с одной и той же сигнатутой.
    // Два варианта реализации неразличимы.
    // Третий (фактически второй) вариант реализации
    // отличается квалифицированными именами.  
    void Iw.F0(int xKey)
    {
    	xVal = xKey+5;
    	Console.WriteLine("Iw.F0({0})...", xVal);
    }
    
    void Iw.F1()
    {
    	xVal = xVal–5;
    	Console.WriteLine("Iw.F1({0})...", xVal);
    }
    
    public void bF0()
    {
    	Console.WriteLine("bF0()...");
    }
    }
    class Class1
    {
    static void Main(string[] args)
    {
    
    TestClass x0 = new TestClass();
    TestClass x1 = new TestClass(5);
    
    x0.bF0();
    
    // Эти методы представляют собой неявную ОДНОЗНАЧНУЮ реализацию
    // интерфейса Ix.
    x0.IxF0(10);
    x1.IxF1();
    
    // Эти методы представляют собой неявную НЕОДНОЗНАЧНУЮ реализацию
    // интерфейсов Iy и Iz. 
    x0.F0(5); 
    x1.F1();
        
    // А вот вызов функций с явным приведением к типу интерфейса.
    // Собственный метод класса bF0() при подобных преобразованиях
    // не виден.
    (x0 as Iy).F0(7);
    (x1 as Iz).F1();
    
    // А теперь настраиваем ссылки различных типов интерфейсов
    // на ОДИН И ТОТ ЖЕ объект – представитель класса TestClass.
    // И через "призму" интерфейса всякий раз объект будет 
    // выглядеть по-разному.
    Console.WriteLine("==========Prism test==========");
    
    Console.WriteLine("==========Ix==========");
    Ix ix = x1;
    ix.IxF0(5);
    ix.IxF1();
    
    Console.WriteLine("==========Iy==========");
    Iy iy = x1;
    iy.F0(5);
    iy.F1();
    Console.WriteLine("==========Iz==========");
    Iz iz = x1;
    iz.F0(5);
    iz.F1();
    
    Console.WriteLine("==========Iw==========");
    Iw iw = x1;
    iw.F0(10);
    iw.F1();
    }
    }
    }

    Преимущества программирования с использованием интерфейсов проявляются в том случае, когда ОДНИ И ТЕ ЖЕ ИНТЕРФЕЙСЫ наследуются РАЗНЫМИ классами. При этом имеет место еще один вариант полиморфности: объект, представленный ссылкой-интерфейсом, способен проявлять различную функциональность в зависимости от реализации функций наследуемого интерфейса в классе данного объекта. И здесь все определяется спецификой данной конкретной реализации:

    using System;
    
    namespace Interface02
    {
    // Объявляются два интерфейса, каждый из которых содержит объявление
    // одноименного метода с единственным параметром соответствующего типа.
    
    interface ICompare0
    {
    bool Eq(ICompare0 obj);
    } 
    
    interface ICompare1
    {
    bool Eq(ICompare1 obj);
    }
    // Объявляются классы, наследующие оба интерфейса.
    // В каждом из классов реализуются функции интерфейсов. 
    // В силу того, что объявленные в интерфейсах методы – одноименные,
    // в классах применяется явная реализация методов интерфейсов.
    // Для классов-наследников интерфейсы представляют собой всего лишь
    // базовые классы. Поэтому в соответствующих функциях сравнения
    // допускается использование операции as.
    //____________________________________________________________.  	
    class C0:ICompare0,ICompare1
    {
    
    public int commonVal;
    int valC0;
    public C0(int commonKey, int key)
    {
    commonVal = commonKey;
    valC0 = key;
    }
    
    // Метод реализуется для обеспечения сравнения объектов
    // СТРОГО одного типа – C0.
    bool ICompare0.Eq(ICompare0 obj)
    {
    C0 test = obj as C0;
    if (test == null) return false;
    if (this.valC0 == test.valC0)
    	return true;
    else
    	return false;
    }
    
    
    // Метод реализуется для обеспечения сравнения объектов разного типа.
    bool ICompare1.Eq(ICompare1 obj)
    {
    C1 test = obj as C1;
    if (test == null) return false;
    if (this.commonVal == test.commonVal)
    	return true;
    else
    	return false;
    }
    }
    //____________________________________________________________.  	
    
    class C1:ICompare0,ICompare1
    {
    public int commonVal;
    string valC1;
    
    public C1(int commonKey, string key)
    {
    commonVal = commonKey;
    valC1 = string.Copy(key);
    }
    
    // В классе C1 при реализации функции интерфейса ICompare0 реализован
    // метод сравнения, который обеспечивает сравнение как объектов типа C1,
    // так и объектов типа C0.
    bool ICompare0.Eq(ICompare0 obj)
    {
    C1 test;
    
    // Попытка приведения аргумента к типу C1.
    // В случае успеха – сравнение объектов по специфическому признаку,
    // который для данного класса представлен строковой переменной valC1.
    // В случае неуспеха приведения
    // (очевидно, что сравниваются объекты разного типа)
    // предпринимается попытка по второму сценарию
    // (сравнение объектов разных типов).
    // Таким образом, в рамках метода, реализующего один интерфейс,
    // используется метод второго интерфейса. Разумеется, при явном 
    // приведении аргумента к типу второго интерфейса. 
    
    test = obj as C1;
     if (test == null) 
    	return ((ICompare1)this).Eq((ICompare1)obj);
    
    if (this.valC1.Equals(test.valC1))
      return true;
    else
    return false;
    }
    
    // Метод реализуется для обеспечения сравнения объектов разного типа.
    bool ICompare1.Eq(ICompare1 obj)
    {
     C0 test = obj as C0;
     if (test == null) return false;
     if (this.commonVal == test.commonVal)
      return true;
     else
    return false;
    }
    }
    
    //===============================================================
    // Место, где порождаются и сравниваются объекты.  
    class Class1
    {
    static void Main(string[] args)
    {
      C0 x1 = new C0(0,1);
      C0 x2 = new C0(1,1);
        
    // В выражениях вызова функций - членов интерфейсов НЕ ТРЕБУЕТСЯ 
    //	явного приведения значения параметра к типу интерфейса.
    // Сравнение объектов - представителей одного класса (C0).
       if ((x1 as ICompare0).Eq(x2))
    	 Console.WriteLine("Yes!");
       else                     
    	 Console.WriteLine("No!");
    
      C1 y1 = new C1(0,"1");
       C1 y2 = new C1(1,"1");
    
    // Сравнение объектов - представителей одного класса (C1).
       if ((y1 as ICompare0).Eq(y2))
    	Console.WriteLine("Yes!");
       else                     
    	Console.WriteLine("No!");
    
    // Попытка сравнения объектов - представителей разных классов.
        if (((ICompare0)x1).Eq(y2))
    	Console.WriteLine("Yes!");
        else                     
    	Console.WriteLine("No!");
    
    
       if ((x1 as ICompare1).Eq(y2))
    	Console.WriteLine("Yes!");
        else                     
    	Console.WriteLine("No!");
    
    
       if (((ICompare1)y2).Eq(x2))
    	Console.WriteLine("Yes!");
        else                     
    	Console.WriteLine("No!");
    
    // Здесь будет задействован метод сравнения, реализованный
    // в классе C0 по интерфейсу ICompare0, который не является универсальным.
    // Отрицательный результат может быть получен не только
    // по причине неравенства значений сравниваемых величин,
    // но и по причине несоответствия типов  операндов.
       Console.WriteLine("__________x2==y2__________");
       if ((x2 as ICompare0).Eq(y2))
    	Console.WriteLine("Yes!");
       else                     
    	Console.WriteLine("No!");
    
    // А здесь вероятность положительного результата выше, поскольку в классе
    // C1 метод интерфейса ICompare0 реализован как УНИВЕРСАЛЬНЫЙ.
    // И это значит,
    // что данный метод никогда не вернет отрицательного значения по причине
    // несоответствия типов операндов. 
      Console.WriteLine("__________y2==x2__________");
       if ((y2 as ICompare0).Eq(x2))
    	Console.WriteLine("Yes!");
       else                     
    	Console.WriteLine("No!");
    }
    }
    }

    Реализация сортировки в массиве. Интерфейс IComparable

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

    Класс Array является основой для любого массива и для любого массива предоставляет стандартный набор методов для создания, манипулирования (преобразования), поиска и сортировки на множестве элементов массива.

    В частности, варианты метода Sort() обеспечивают реализацию механизмов сортировки элементов ОДНОМЕРНОГО массива объектов (в смысле представителей класса Object ).

    Сортировка элементов предполагает:

  • ПЕРЕБОР всего множества (или его части) элементов массива;
  • СРАВНЕНИЕ (значений) элементов массива в соответствии с определенным критерием сравнения.
  • При этом критерии для сравнения и алгоритмы сравнения могут задаваться либо с помощью массивов ключей, либо реализацией интерфейса IComparable, который специфицирует характеристики (тип возвращаемого значения, имя метода и список параметров) методов, обеспечивающих реализацию данного алгоритма. Далее будут рассмотрены различные варианты метода Sort.

  • Вариант метода Sort сортирует полное множество элементов одноразмерного массива с использованием алгоритма, который реализуется методами, специфицированными стандартным интерфейсом сравнения:
    public static void Sort(Array);
  • Реализованный на основе интерфейса алгоритм способен распознавать значения элементов массива и сравнивать эти элементы между собой, если это элементы предопределенных типов.

    Таким образом, никаких проблем не существует, если надо сравнить и переупорядочить массивы элементов, для которых известно, КАК СРАВНИВАТЬ значения составляющих массив элементов. Относительно ..., System.Int16, System.Int32, System.Int64, ...,System.Double,... всегда можно сказать, какой из сравниваемых элементов больше, а какой меньше.

  • Сортировка элементов массива (массива целевых элементов) также может быть произведена с помощью вспомогательного массива ключей:
    public static void Sort(Array, Array);
  • Суть подобной сортировки заключается в том, что между элементами массива ключей и элементами сортируемого массива устанавливается соответствие. В частном случае, если количество элементов массива ключей оказывается равным количеству элементов сортируемого массива, это соответствие оказывается взаимнооднозначным. При этом сортируются элементы массива ключей. Предполагается, что используемый в этом случае алгоритм сравнения способен обеспечить необходимое сравнение.

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

    using System;
    namespace SortArrays
    {
    class Class1
    {
    static void Main(string[] args)
     {
    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив. 
    int[] keys = {10,1,2,3,4,5,6,7,8,9}; // Массив ключей.
    for (int i = 0; i < 10; i++) Console.WriteLine("{0}: {1}", i, iArr[i]);
    // Сортировка массива с использованием ключей.
    System.Array.Sort(keys,iArr);
    Console.WriteLine("============================================");
    for (i = 0; i < 10; i++) Console.WriteLine("{0}: {1}", i, iArr[i]);
     }
    }
    }

    В процессе сортировки массива ключей элемент со значением ключа 10 перемещается в конец массива. При этом нулевой элемент целевого массива со значением 0 перемещается на последнюю позицию.

    Целевой массив и массив ключей вида при сортировке

    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив.
    int[] keys = {9,8,7,6,5,4,3,2,1,0}; // Массив ключей.

    обеспечивают размещение элементов целевого массива в обратном порядке.

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

    int[] iArr = {0,1,2,3,4,5,6,7,8,9}; // Целевой массив.
    int[] keys = {3,2,1,0}; // Массив ключей.

    А такие наборы значений целевого и ключевого элементов массива при сортировке с использованием массива ключей обеспечивают изменение порядка целевых элементов массива с индексами 4, 5, 6, 7.

    int[] iArr = {0,1,2,3,4,5,6,7,8,9};
    int[] keys = {0,1,2,9,8,7,6,10,11,12};

    Но самое главное и самое интересное в методе сортировки с использованием ключевого массива – это то, что в этом никаким образом не участвуют значения элементов целевого массива.

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

    int[][] iArr = new int[3][]{
    new int[]{0},
    new int[]{0,1},
    new int[]{0,1,2,3,4,5,6,7,8,9}
     };
    int[] keys = {3,2,1};
  • Вариант метода, который позволяет организовать сортировку подмножества элементов целевого массива, начиная с элемента, заданного вторым параметром метода, и включая количество элементов, заданное третьим параметром:
    public static void Sort(Array,int,int);
  • Вариант метода сортировки, основанный на сортировке элементов ключевого массива. Обеспечивает сортировку подмножества массива. Принципы выделения подмножества аналогичны рассмотренному выше варианту метода сортировки:
    public static void Sort(Array,Array,int,int);
  • Сортировка на основе сравнения пар объектов — членов одномерного массива с использованием интерфейса сравнения:
    public static void Sort(Array,IComparer);
    
    // Сортировка элементов с использованием стандартного Компарера.
    // iArr – одномерный массив целочисленных значений.
    // System.Collections.Comparer.DefaultInvariant – свойство,
    // которое обеспечивает ссылку на объект "стандартный Компарер",
    // реализующий предопределенные алгоритмы сравнения.
    try
    {
    System.Array.Sort(iArr,System.Collections.Comparer.DefaultInvariant);
    }
    catch (Exception ex)
    {
    Console.WriteLine(ex.ToString());
    }
  • System.Collections.Comparer.DefaultInvariant – свойство, которое обеспечивает доступ к объекту, реализующему обобщенный метод сравнения.

    Для любого класса на основе интерфейса IComparer можно создать собственный класс-КОМПАРЕР, в котором можно будет реализовать специфический для данного класса сортируемых элементов метод сравнения (interface implemented by each element of the Array).

    using System;
    using System.Collections;
    
    namespace SortArrays
    {
    // Данные для массива элементов.
    // Подлежат сортировке в составе массива методом Sort.
    // Главная проблема заключается в том, что никто из тех, кто обеспечивает
    // встроенную сортировку, ни в классе Array, ни в классе Points не знает,
    // как воспринимать и каким образом сравнивать между собой
    // объекты-представители класса Points. Объекты p0 и p1
    // со значениями координат (7,5) и (5,7) – кто из них "больше"?
    class Points
    {
    // Конечно, можно говорить о введении аксиом для установления отношения
    // порядка между элементами множества объектов – представителей класса
    // Points и о реализации соответствующих методов сравнения;
    // также можно было бы позаботиться при объявлении класса о том,
    // чтобы просто не допускать к стандартной сортировке
    // объекты классов, в которых не определено
    // отношение порядка. Однако в .NET используется другой подход.
    public int x;
    public int y;
    public Points (int key1, int key2)
    {
     x = key1;
     y = key2;
    }
    // Вычисляется расстояние от начала координат.
    public int R
    {
    get
    {
     return (int)(Math.Sqrt(x*x+y*y));
    }     
    }   
    }
        
    // Предполагается, что отношение порядка над элементами
    // множества объектов – представителей соответствующего
    // класса может быть введено в любой момент. Для этого
    // достаточно иметь четкое представление о критериях
    // установления порядка на множестве объектов – представителей
    // данного класса и иметь доступ к значениям соответствующих
    // полей объектов.
    // Стандартные библиотечные средства .NET позволяют
    // реализовать (доопределить) соответствующие интерфейсы
    // (заготовки) для реализации методов сравнения.
    // Ниже объявляется вариант процедуры сравнения,
    // использование которого позволяет реализовать стандартные
    // алгоритмы сортировки, применяемые в классе Array.
    // ...КОМПАРЕР...   
    
    class myComparer: IComparer
    {
    // Предлагаемый метод сравнения возвращает разность расстояний
    // двух точек (вычисляется по теореме Пифагора) от начала координат
    // – точки с координатами (0,0). Чем ближе точки к началу координат
    // – тем они меньше. 
    	
    int IComparer.Compare(object obj1, object obj2)
    {
        return ((Points)obj1).R - ((Points)obj2).R; 
    }
    }
    
    // После реализации соответствующего интерфейса объект-КОМПАРЕР
    // обеспечивает реализацию стандартного алгоритма сортировки.
    class Class1
    {
    
    static void Main(string[] args)
    {
        // Объект-генератор "случайных" чисел. 
    	Random rnd = new Random();
    
    	int i;
        // Массив Points.
        Points[] itArr = new Points[10];
    	
       // Создали Компарер, способный сравнивать пары
    	// объектов-представителей класса Points.  
    	myComparer c = new myComparer();
    	
    Console.WriteLine("============================================");
    
    	// Проинициализировали массив объектов – представителей класса Points. 
    	for (i = 0; i < 10; i++)
    	{
    		itArr[i] = new Points(rnd.Next(0,10),rnd.Next(0,10));
    	}
    
    	for (i = 0; i < 10; i++)
    	{
    		Console.WriteLine("{0}: {1},{2}", i, itArr[i].x, itArr[i].y);
    	}
    
    	// Сортируются элементы массива типа Points.
    	// Условием успешной сортировки элементов массива является реализация
    	// интерфейса IComparer. Если Компарер не сумеет справиться с
    	// поставленной задачей – будет возбуждено исключение.
    	try
    	{
    		System.Array.Sort(itArr,c);
    	}
    	catch (Exception ex)
    	{
    			Console.WriteLine(ex);			
    	}
    
    	Console.WriteLine("============================================");
    	for (i = 0; i < 10; i++)
    	{
    		Console.WriteLine("{0}: {1},{2}", i, itArr[i].x, itArr[i].y);
    	}
    
    	Console.WriteLine("============================================");
    }
    }
    }

    Интерфейс IDisposable. Освобождение ресурсов

    Совмещение освобождения ресурсов с удалением объекта-владельца данного ресурса называется недетерминированным освобождением. Зависящее от деятельности сборщика мусора недетерминированное освобождение ресурсов не всегда является оптимальным решением:

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

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

    В .NET с этой целью используется интерфейс IDisposable с методом Dispose.

    public interface IDisposable
    {
     void Dispose();
    }

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

    Следующие правила определяют в общих чертах рекомендации по использованию метода Dispose.

    В методе Dispose освобождаются любые ресурсы, которыми владеет объект данного типа и которые можно освободить.

    Если для освобождения ресурсов, которыми владеет объект, не был вызван метод Dispose, неуправляемые ресурсы должны освобождаться в методе Finalize.

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

    Метод Dispose должен освобождать все ресурсы, удерживаемые данным объектом и любым объектом, которым владеет данный объект.

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

    using System;
    
    namespace TestD00
    {
    public class MemElem : IDisposable
    {
    bool dFlag;
    int i;
    public MemElem(int iKey)
    {
    dFlag = true;
    i = iKey;
    }
    
    ~MemElem() // Деструктор (он же Finalizer)
    {
    Console.WriteLine("{0} disposed : {1}!", i, (dFlag == true) ? "yes" : "no");
    // Если ресурсы не освобождены – вызывается Dispose. 
    if (!dFlag) Dispose();
    }
    // Детерминированное управление ресурсом:
    // имитация активности.
    public void Dispose()
    {
    if (dFlag==true)
    {
     Console.WriteLine("Dispose is here!");
     dFlag = false;
    } 
    else
    {
     Console.WriteLine("Dispose was here!");
    }  
    }               
    }
        
    class Program
    {
    static void Main(string[] args)
    {
    IDisposable d;
    MemElem mmm = null;
    int i;
    for (i = 0; i < 10; i++)
    {
    MemElem m = new MemElem(i);
    if (i == 5) 
    {
     d = m as IDisposable;
     if (d!=null) d.Dispose();
     mmm = m;
     GC.SuppressFinalize(mmm); // И закрыли для кода деструктора.
    }
    }
    
    Console.WriteLine("-----0-----");
    GC.Collect();
    Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));
    // Заменить false на true. Осознать разницу.
    Console.WriteLine("-----1-----");
    // А теперь вновь разрешили его удалить...
    // Пока существует хотя бы одна ссылка – GC все равно не будет
    // уничтожать этот объект. 
    if (mmm != null) GC.ReRegisterForFinalize(mmm);
    mmm = null; // Вот потеряли ссылку на объект.
    // Теперь GC может уничтожить объект. Если разрешена финализация - 
    // будет выполнен код деструктора. Возможно, что отработает метод
    // Dispose. Если финализация запрещена -
    // код деструктора останется невыполненным.
    Console.WriteLine("-----2-----");
    GC.Collect();
    // Заменить false на true. Осознать разницу.
    Console.WriteLine("Total Memory: {0}", GC.GetTotalMemory(false));
    Console.WriteLine("-----3-----");
    }
    }
    }
    Вернуться к учебному плану