Как правильно писать тесты 46 Цикл разработки 46 Структура проекта с тестами 51 Утверждения (Asserts) 52 Утверждения в форме ограничений 54 Категории 56

Вид материалаТесты

Содержание


О минимизации мусора
Упаковка и распаковка
Подобный материал:
1   ...   21   22   23   24   25   26   27   28   ...   47

О минимизации мусора17




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


В этом случае лучше сделать не так:


protected override void OnPaint( PaintEventArgs e )

{

// Bad. Created the same font every paint event.

using ( Font MyFont = new Font( "Arial", 10.0f ))

{

e.Graphics.DrawString( DateTime.Now.ToString(),

MyFont, Brushes.Black, new PointF( 0,0 ));

}

base.OnPaint( e );

}


А так:


private readonly Font _myFont =

new Font( "Arial", 10.0f );


protected override void OnPaint( PaintEventArgs e )

{

e.Graphics.DrawString( DateTime.Now.ToString( ),

_myFont, Brushes.Black, new PointF( 0,0 ));

base.OnPaint( e );

}


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


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


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


Например, если для закрашивания области нужна черная кисть, то ее удобно получить из системного класса Brushes.Black. Это статическое свойство устроено примерно так

private static Brush _blackBrush;

public static Brush Black

{

get

{

if ( _blackBrush == null )

_blackBrush = new SolidBrush( Color.Black );

return _blackBrush;

}

}


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

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

string msg = "Hello, ";

msg += thisUser.Name;

msg += ". Today is ";

msg += System.DateTime.Now.ToString();


Этот пример фактически эквивалентен

string msg = "Hello, ";

// Not legal, for illustration only:

string tmp1 = new String( msg + thisUser.Name );

string msg = tmp1; // "Hello " is garbage.

string tmp2 = new String( msg + ". Today is " );

msg = tmp2; // "Hello " is garbage.

string tmp3 = new String( msg + DateTime.Now.ToString( ) );

msg = tmp3;// "Hello . Today is " is garbage.


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

Для коротких строк лучше использовать специальный статический метод класса String

string msg = string.Format ( "Hello, {0}. Today is {1}",

thisUser.Name, DateTime.Now.ToString( ));


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

StringBuilder msg = new StringBuilder( "Hello, " );

msg.Append( thisUser.Name );

msg.Append( ". Today is " );

msg.Append( DateTime.Now.ToString());

string finalMsg = msg.ToString();


Итак, нужно понимать, что на создание и удаление объектов требуется время и предусматривать его уменьшение.


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




Типы-значения являются контейнерами для данных. Они не полиморфны. С другой стороны, .NET Framework спроектирована так, что во главе иерархии объектов стоит единственный ссылочный тип — System.Object. Эти два положения противоречат друг другу. Для «наведения мостов» между ними среда .NET Framework применяет упаковку (boxing) и распаковку (unboxing). При упаковке тип-значение помещается в неконтролируемый по типу ссылочный объект, чтобы можно было использовать тип-значение там, где ожидается ссылочный объект. При распаковке из оболочки выделяется копия этого типа-значения. Упаковка и распаковка необходимы, чтобы можно было использовать типы-значения там, где ожидается тип System.Object. Но упаковка и распаковка всегда являются просто-таки грабительскими в отношении производительности операциями. Иногда при упаковке и распаковке создаются временные копии объектов, что может привести к появлению в программе так называемых неочевидных ошибок. По возможности следует избегать операций упаковки и распаковки.


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





Все коварство упаковки и распаковки заключается в том, что эти действия производятся автоматически. Компилятор генерирует операторы упаковки и распаковки всякий раз, когда тип-значение применяется вместо ссылочного типа, например вместо System. Object. Кроме того, операции упаковки и распаковки выполняются при использовании типа-значения через переменную типа интерфейса. Не будет никакого уведомления - просто произойдет упаковка. Даже в случае простых операторов выполняется упаковка:


Console.WriteLine("A few numbers:{0}, {1}, {2}",

25, 32, 50);


Console.WriteLine принимает массив ссылок на System.Object. Целые числа являются типами-значениями и должны быть упакованы, чтобы их можно было передать в этот вариант метода WriteLine. Единственный способ принудительно загнать эти три целых аргумента в System.Object - упаковать их. Помимо этого, внутри WriteLine производится проникновение внутрь оболочки, чтобы вызвать метод ToString() находящегося в оболочке объекта. Можно сказать, что генерируется следующая конструкция:


int i =25;

object o = i; // box

Console.WriteLine(o.ToString());


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


object o;

int i = ( int )o; // unbox

string output = i.ToString( );


Вы бы никогда не написали такой код сами. Однако, разрешив компилятору автоматическое конвертирование конкретного типа-значения в System.Object, вы допустили это.


Чтобы избежать этих накладных расходов, конвертируйте свои типы в строковые экземпляры самостоятельно, прежде чем посылать их в метод WriteLine:


Console.WriteLine("A few numbers:{0}, {1}, {2}",

25.ToString(), 32.ToString(), 50.ToString());


В этом коде используется тип integer, а типы-значения (например, integer) никогда не конвертируются неявно в System.Object. Если этого можно избежать, не следует заменять типы-значения типом System.Object.