Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







Методы: конструкторы, операторы, преобразования и параметры





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

Конструкторы экземпляров и классы (ссылочные типы)

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

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

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

Иначе говоря, в классе есть экземплярные конструкторы, которые определены в самом классе. Невозможность наследования означает, что к конструктору экземпляра нельзя применить следующие модификаторы: virtual, new, override, sealed и abstract. Если определить класс без явно заданных конструкторов, многие компиляторы (в том числе компилятор С#) создадут конструктор по умолчанию (без параметров), реализация которого просто вызывает конструктор без параметров базового класса.

К примеру, такое определение класса:

public class SomeType { }

 

идентично определению:

public class SomeType

{

public SomeType(): base() { }

}

 

Для абстрактных классов компилятор создаст конструктор по умолчанию с модификатором protected, в противном случае область действия будет public. Если в базовом классе нет конструктора без параметров, производный класс должен явно вызвать конструктор базового класса, иначе компилятор вернет ошибку. Для статических классов (sealed и abstract) компилятор не создает конструктор по умолчанию.

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

Многие компиляторы, включая С#, генерируют вызов конструктора базового класса автоматически, поэтому вам, как правило, об этом можно не беспокоиться. В конечном счете всегда вызывается открытый конструктор объекта System.Object без параметров. Этот конструктор ничего не делает — просто возвращает управление по той простой причине, что в System.Object не определено никаких экземплярных полей данных и поэтому конструктору просто нечего делать.

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

Внимание! Нельзя вызывать какие-либо виртуальные методы конструктора, которые могут повлиять на создаваемый объект. Причина проста: если вызываемый виртуальный метод переопределен в типе, экземпляр которого создается, выполнится реализация производного типа, но к этому моменту еще не завершилась инициализация всех полей в иерархии. В таких обстоятельствах последствия вызова виртуального метода непредсказуемы.

 

С# предлагает простой синтаксис, позволяющий инициализировать поля во время создания объекта ссылочного типа:

internal sealed class SomeType

{

private Int32 m_x = 5;

}

 

При создании объекта SomeType его поле т_х инициализируется значением 5. Вы можете спросить: как это происходит? Изучив IL-код метода-конструктора этого объекта (этот метод также фигурирует под именем.ctor), вы увидите код, записывающий в поле m_х значение 5 и вызывающий конструктор базового класса. Иначе говоря, компилятор С# допускает удобный синтаксис, позволяющий инициализировать поля экземпляра при их объявлении. Компилятор транслирует этот синтаксис в метод-конструктор, выполняющий инициализацию. Это значит, что нужно быть готовым к разрастанию кода. Вот пример. Представьте себе такой класс:

internal sealed class SomeType

{

private Int32 m_x = 5;

private String m_s = "Hi there";

private Double m_d = 3.14159;

private Byte m_b;

// Это конструкторы

public SomeType() {... }

public SomeType(Int32 x) {... }

public SomeType(String s) {...; m_d = 10; }

}

 

Генерируя IL-код для трех методов-конструкторов из этого примера, компилятор помещает в начало каждого из методов код, инициализирующий поля mjx, m_s и m_d. Затем он добавляет к методу код, расположенный внутри методов-конструкторов. Например, IL-код, сгенерированный для конструктора с параметром типа String, состоит из кода, инициализирующего поля mjx, m_s и m_d, и кода, перезаписывающего поле m_d значением 10. Заметьте: поле m_b гарантированно инициализируется значением 0, даже если нет кода, инициализирующего это поле явно.

Поскольку в показанном выше классе определены три конструктора, компилятор трижды генерирует код, инициализирующий поля т_х, m_s и m_d: по разу для каждого из конструкторов. Если имеется несколько инициализируемых экземплярных полей и множество перегруженных методов-конструкторов, стоит подумать о том, чтобы определить поля, не инициализируя их; создать единственный конструктор, выполняющий общую инициализацию и заставить каждый метод-конструктор явно вызывать конструктор, выполняющий общую инициализацию.

Этот подход позволит уменьшить размер генерируемого кода. Вот пример использования способности С# явно заставлять один конструктор вызывать другой конструктор за счет использования зарезервированного слова this:

internal sealed class SomeType

{

// Здесь нет кода, явно инициализирующего поля

private Int32 m_x;

private String m_s;

private Double m_d;

private Byte m_b;

/* Этот конструктор содержит код, инициализирующий поля

значениями по умолчанию. Он должен вызываться всеми остальными

конструкторами*/

public SomeType()

{

m_x = 5;

m_s = "Hi there";

m_d = 3.14159;

m_b = Oxff;

}

/* Этот конструктор инициализирует поля значениями по умолчанию,

а затем изменяет значение m_х*/

public SomeType(Int32 x): this()

{

m_х = х;

}

/* Этот конструктор инициализирует поля значениями по умолчанию,

а затем изменяет значение m_s*/

public SomeType(String s): this()

{

m_s = s;

}

/* Этот конструктор инициализирует поля значениями по умолчанию,

а затем изменяет значение m_х и m_s*/

public SomeType(Int32 x, String s): this()

{

m_x = x;

m_s = s;

}

}

 







ЧТО ПРОИСХОДИТ, КОГДА МЫ ССОРИМСЯ Не понимая различий, существующих между мужчинами и женщинами, очень легко довести дело до ссоры...

Что вызывает тренды на фондовых и товарных рынках Объяснение теории грузового поезда Первые 17 лет моих рыночных исследований сводились к попыткам вычис­лить, когда этот...

ЧТО ТАКОЕ УВЕРЕННОЕ ПОВЕДЕНИЕ В МЕЖЛИЧНОСТНЫХ ОТНОШЕНИЯХ? Исторически существует три основных модели различий, существующих между...

Что делает отдел по эксплуатации и сопровождению ИС? Отвечает за сохранность данных (расписания копирования, копирование и пр.)...





Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2024 zdamsam.ru Размещенные материалы защищены законодательством РФ.