Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2215 / 889 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 1:

Введение и обзор платформы .NET

Лекция 1: 123456 || Лекция 2 >

Ссылочные типы

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

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

В C# ссылочные типы создаются с помощью ключевого слова class:

class RectRef {public int x, y, cx, cy; }

Упаковка и распаковка

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

ArrayList a = new ArrayList();
for (int i=0; i < 10; i++) {
  Point p;        // Allocate a Point (not in the heap)
  p.x = p.y = i;  // Initialize members in the value type
  a.Add(p);       // here we're boxing the value type...
}

Для добавления в массив нам необходимо преобразовать значение в ссылочный тип, т.к. метод Add принимает на вход только параметры типа Object. Процесс преобразования типа-значения в ссылочный тип называется упаковкой (boxing). Естественно, существует и обратный процесс, который называется распаковкой (unboxing). Отметим, что ссылочный тип существует только в упакованной форме, а тип-значение может находиться как в упакованной, так и в распакованной форме.

Как работает распаковка

Теперь разберемся с обратной операцией, распаковкой:

  1. Проверяется, что исходная ссылочная переменная не равняется null и что она ссылается на значение, полученное упаковкой ожидаемого типа-значения. Если какое-либо из этих условий неверно, то выдается InvalidCallException.
  2. Если же типы совпадают, то возвращается указатель на содержимое ссылочного типа (без учета накладных расходов, связанных с организацией объекта).

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

На следующем слайде мы рассмотрим пример, иллюстрирующий процесс упаковки и распаковки.

Пример упаковки и распаковки

Пример упаковки и распаковки

public static void Main() {
  Int32 v = 5;     // creating unboxed value type variable
  Object o = v;    // o refers to boxed version of v
  v = 123;         // changes the unboxed value to 123
 
  Console.WriteLine (v + "," + (Int32) o); 
                   // displays "123, 5"
}

Вопрос: сколько раз в данном примере производится операция упаковки?

Правильный ответ: операция упаковки производится ровно 3 раза. Дополнительная операция возникает внутри Console.WriteLine, так как оператор ' + ' означает неявный вызов метода Concat, который ожидает переменные типа Object в качестве параметров. Мы же перед выводом на печать приводим объектную переменную к типу Int32 (т.е. к типу-значению). Для того, чтобы тип-значение мог быть использован в методе Concat, он должен быть приведен обратно в ссылочный вид.

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

Console.WriteLine (v + "," + o);

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

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

Упаковка и распаковка в С++

Упаковка и распаковка в С++

#using <mscorlib.dll>
using namespace System;

__value struct V
{
   int i;
};

void Positive(Object*) { };      // expects a managed class

void main()
{
   V v={10};   		    // allocate and initialize
   Object* o = __box(v);   	    // copy to the CLR heap
   Positive( o );   		    // treat as a managed class
   dynamic_cast<V*>(o)- >i = 20;  // update the boxed version
}

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

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

Наконец, большинство остальных языков программирования в .NET требуют явной записи для операций упаковки и распаковки. Например, в примере на managed C++, приведенном на слайде, можно увидеть обе операции: __box(v) приводит тип-значение к ссылочному типу, а dynamic_cast<V*>(o) позволяет изменить именно значение переменной i.

Литература к лекции

  • Д. Пратт "Знакомство с .NET", Русская редакция, 2001
  • J. Richter "Microsoft .NET Framework Delivers the Platform for an Integrated, Service-Oriented Web", MSDN Magazine, Oct/Nov 2000
  • J. Richter "Type Fundamentals", MSDN Magazine, December 2000
  • Т. Арчер "Основы C#", Русская редакция, 2001
Лекция 1: 123456 || Лекция 2 >