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

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

Содержание


Инициализация статических полей классов с помощью статических конструкторов
Цепочки конструкторов
Подобный материал:
1   ...   19   20   21   22   23   24   25   26   ...   47

Инициализация статических полей классов с помощью статических конструкторов16




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


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


Чаще всего статический конструктор используется в паттерне Одиночка.

Однако можно обойтись и инициализатором:


public class MySingleton

{

private static readonly MySingleton _theOneAndOnly =

new MySingleton( );


public static MySingleton TheOnly

{

get

{

return _theOneAndOnly;

}

}


private MySingleton( )

{

}


// remainder elided

}


Как и любой инициализатор, инициализатор статического поля вызывается до вызова статического конструктора.


Если без статического конструктора не обойтись:


public class MySingleton

{

private static readonly MySingleton _theOneAndOnly;


static MySingleton( )

{

_theOneAndOnly = new MySingleton( );

}


public static MySingleton TheOnly

{

get

{

return _theOneAndOnly;

}

}


private MySingleton( )

{

}


// remainder elided

}


Случай с необходимостью обработки исключений:


static MySingleton( )

{

try {

_theOneAndOnly = new MySingleton( );

} catch

{

// Attempt recovery here.

}

}


Цепочки конструкторов




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


public class MyClass

{

// collection of data

private ArrayList _coll;

// Name of the instance:

private string _name;


public MyClass() :

this( 0, "" )

{

}


public MyClass( int initialCount ) :

this( initialCount, "" )

{

}


public MyClass( int initialCount, string name )

{

_coll = ( initialCount > 0 ) ?

new ArrayList( initialCount ) :

new ArrayList();

_name = name;

}

}


Не следует выносить этот общий блок кода в специальный метод.


public class MyClass

{

// collection of data

private ArrayList _coll;

// Name of the instance:

private string _name;


public MyClass()

{

commonConstructor( 0, "" );

}


public MyClass( int initialCount )

{

commonConstructor( initialCount, "" );

}


public MyClass( int initialCount, string Name )

{

commonConstructor( initialCount, Name );

}


private void commonConstructor( int count,

string name )

{

_coll = (count > 0 ) ?

new ArrayList(count) :

new ArrayList();

_name = name;

}

}


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


// Not legal, illustrates IL generated:

public MyClass()

{

private ArrayList _coll;

private string _name;


public MyClass( )

{

// Instance Initializers would go here.

object(); // Not legal, illustrative only.

commonConstructor( 0, "" );

}


public MyClass (int initialCount)

{

// Instance Initializers would go here.

object(); // Not legal, illustrative only.

commonConstructor( initialCount, "" );

}


public MyClass( int initialCount, string Name )

{

// Instance Initializers would go here.

object(); // Not legal, illustrative only.

commonConstructor( initialCount, Name );

}


private void commonConstructor( int count,

string name )

{

_coll = (count > 0 ) ?

new ArrayList(count) :

new ArrayList();

_name = name;

}

}


Вызов более общего конструктора устраняет эту проблему. Весь общий код удается «вынести за скобки». Обращение к конструктору базового класса происходит только при вызове последнего конструктора из цепочки. Это происходит из-за того, что в определении конструктора возможно использование либо слова base, либо слова this. Одновременно их использовать нельзя.


Наконец, последним доводом в пользу использования конструктора, а не какого-то метода для инициализации, может служить необходимость инициализации readonly поля.


public class MyClass

{

// collection of data

private ArrayList _coll;

// Number for this instance

private int _counter;

// Name of the instance:

private readonly string _name;


public MyClass()

{

commonConstructor( 0, "" );

}


public MyClass( int initialCount )

{

commonConstructor( initialCount, "" );

}


public MyClass( int initialCount, string Name )

{

commonConstructor( initialCount, Name );

}


private void commonConstructor( int count,

string name )

{

_coll = (count > 0 ) ?

new ArrayList(count) :

new ArrayList();

// ERROR changing the name outside of a constructor.

_name = name;

}

}


Итак, перечислим шаги, которые выполняются, при создании первого экземпляра класса.


  1. Статические поля принимают значение 0.
  2. Вызываются инициализаторы статических полей.
  3. Вызываются статические конструкторы базовых классов.
  4. Вызывается статический конструктор данного класса.
  5. Поля класса принимают значения 0.
  6. Вызываются инициализаторы полей класса.
  7. Вызывается подходящий конструктор базового класса.
  8. Вызывается конструктор класса.



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