public class MyBaseClass { public virtual void DoSomething()
{
Console.WriteLine("Базовая реализация") ;
} } public class MyDerivedClass : MyBaseClass {
new public void DoSomething ()
{
Console.WriteLine("Производная реализация") ; } }
Метод базового класса при этом совершенно не обязательно должен быть виртуальным, однако от этого ничего не изменится, и приведенный здесь код отличается от предыдущего варианта только одной строкой Результат выполнения этого кода как при виртуальном методе, так и в противном случае будет следующим:
Несмотря на то, что базовая реализация является скрытой, к ней, как и раньше, имеется доступ через базовый класс.
Вызов переопределенных или скрытых методов базового класса.
Независимо от того, переопределяем ли мы некоторый член или делаем его скрытым, по-прежнему остается возможность доступа к члену базового класса внутри данного класса. Существует множество различных ситуаций, когда это оказывается полезным, например:
Если требуется скрыть наследуемый общий член от пользователей производного класса, но необходимо иметь доступ к его функциональным возможностям внутри класса.
Если требуется что-либо добавить к реализации наследуемого виртуального члена, а не просто заменить ее на новую переопределенную реализацию.
Для достижения подобного эффекта можно воспользоваться ключевым словом base, которое указывает на реализацию базового класса, содержащуюся внутри производного класса (аналогично тому, как оно используется в управляющих конструкторах, с которыми вы познакомились в предыдущей главе). Например:
public class MyBaseClass
{
public virtual void DoSomethmg ()
{
// Базовая реализация
} }
public class MyDerivedClass MyBaseClass { public override void DoSomething () {
// Реализация в производном классе, расширяющая реализацию в базовом классе base.DoSomething(),
// Дополнительная реализация в производном классе
} }
В представленном коде выполняется версия DoSomethmg (), содержащаяся в классе MyBaseClass (базовом по отношению к MyDerivedClass), из которой вызывается версия DoSomethmg(), содержащаяся внутри класса MyDerivedClass.
Поскольку ключевое слово base работает с экземплярами объектов, то будет ошибкой использовать его внутри статического члена.
Кроме ключевого слова base, в предыдущей главе мы также использовали ключевое слово this. Так же, как и base, this может применяться внутри членов класса, и так же, как и base, оно относится к экземпляру объекта. Экземпляр объекта, на который указывает this, является текущим экземпляром объекта (отсюда следует, что это ключевое слово нельзя использовать в статических членах, поскольку они не являются составной частью экземпляра объекта).
Наиболее ценной функцией ключевого слова this является возможность передавать некоторому методу ссылку на текущий экземпляр объекта, например:
public void doSomethmg () {
MyTargetClass inyObj = new MyTargetClass (); myObj.doSomethmgWith(this); }
В данном примере класс MyTargetClass, экземпляр которого создается, обладает методом DoSomethmgWith(). Ему передается единственный параметр, имеющий тип, совместимый с тем классом, в котором содержится этот метод. Тип параметра может совпадать с типом данного класса, с типом класса, наследником которого является данный класс, может являться интерфейсом, реализованном в данном классе, или же (естественно) быть типом System.Object.
Помимо описания типов как классов в пространствах имен их можно описывать внутри других классов. В таком случае появляется возможность использовать в определениях весь спектр модификаторов доступности, а не только public и internal, а также применять ключевое слово new для перевода определения типа, наследуемого от базового класса, в скрытое состояние.
В следующем коде, кроме класса MyClass, описывается также вложенный класс С именем myNestedClass:
public class MyClass {
public class myNestedClass
{
public int nestedClassField, } }
Если потребуется создать экземпляр класса myNestedClass откуда-нибудь извне класса MyClass, то его имя должно квалифицироваться
MyClass.myNestedClass myObj = new MyClass.myNestedClass();
Однако вполне возможно, что мы окажемся не в состоянии выполнить такие действия, если вложенный класс объявлен как частный или обладает каким-либо другим уровнем доступности, несовместимым с кодом в той точке, в которой осуществляется попытка создания соответствующего экземпляра
Основной причиной этого является необходимость описания классов, которые являются частными по отношению к содержащему их классу, так что никакой другой код в данном пространстве имен не будет иметь к ним доступа
7.3. Вопросы для повторения
1. Определение членов класса.
2. Определение полей, методов и свойств.
3. Назначение ключевого слова this.
4. Вложенные определения типов.
8. 1.Потоки
Любой ввод и вывод информации в .NET Framework включает в себя использование потоков – абстрактных представлений последовательного устройства. Последовательное устройство – это нечто, обеспечивающее как последовательное хранение данных, так и последовательный доступ к ним – по одному байту в каждый конкретный момент времени. В качестве такого устройства могут выступать расположенный на диске файл, принтер, область памяти, а также любой другой объект, который допускает последовательное считывание и запись информации. Рассматривая такое устройство как абстрактное, мы можем скрыть назначение/ источник потока. Такой уровень абстракции делает возможным многократное использование кода и позволяет создавать подпрограммы более общего назначения. Таким образом, аналогичный код может передаваться и повторно использоваться, когда приложение считывает данные из файлового входного потока и когда оно считывает информацию из сетевого входного потока. Кроме того, используя понятие потока, мы получаем возможность абстрагироваться от физических особенностей конкретного устройства. Поэтому при считывании информации из файлового потока нам не приходится волноваться по поводу работы головок дискового устройства или заниматься вопросами распределения памяти.
Выходной поток используется, когда данные записываются на некое внешнее устройство. Это может быть файл на физическом диске, сетевой сервер, принтер или какая-либо другая программа. Понимание программирования потоков открывает множество дополнительных возможностей. В этой главе мы ограничим обсуждение рамками записи в файлы на диске.
Входной поток используется для считывания данных в память или в переменные, к которым наша программа имеет доступ. Наиболее распространенной формой входного потока, с которой нам приходилось работать, является клавиатура. Входной поток может поступать практически от любого устройства, но мы сосредоточимся на чтении дисковых файлов. Понятия, применимые для чтения/записи дисковых файлов, оказываются применимыми практически для всех остальных устройств, что позволяет получить общее представление о потоках и познакомиться с фундаментальным подходом, который может быть использован в самых различных ситуациях.
8.1.1.Классы для ввода и вывода
Пространство имен System.IO содержит в себе все классы, которые будут рассматриваться в этой главе. В пространстве имен System.IO хранятся классы, предназначенные для считывания данных из файлов и записи данных в файлы; для того чтобы получить в программе доступ к этим классам, необходимо сослаться на это пространство имен. Как можно увидеть на следующей диаграмме, в пространстве имен содержится не так уж много классов, но мы будем рассматривать только классы, являющиеся основными при вводе и выводе файлов:
File – широко используемый на практике класс, который обладает большим количеством статических методов, позволяющих переносить, копировать и удалять файлы.
Directory – класс, который обладает большим количеством статических методов, позволяющих переносить, копировать и удалять директории.
Path – класс, позволяющий выполнять манипуляции над именами путей.
FileInfo – представляет физический файл, расположенный на диске, и обладает методами, позволяющими выполнять манипуляции над этим файлом. Для любых операций чтения/записи, выполняемых над файлом, необходимо создавать объект Stream.
DirectoryInfo – представляет физическую директорию, расположенную на диске, и обладает методами, позволяющими выполнять манипуляции над этой директорией.
FileStream – представляет файл, который допускает либо считывание, либо запись, либо и то, и другое одновременно. Этот файл может считываться и записываться как асинхронно, так и синхронно.
StreamReader – считывает символьную информацию из потока и может создаваться на базе класса Fiiestream.
StreamWriter – записывает символьную информацию в поток и может создаваться на базе класса Fiiestream.
FileSystemWatcher – это один из наиболее сложных классов, который используется для слежения за состоянием файлов и директорий и генерирует события в моменты, когда изменяется их местоположение, что позволяет перехватывать их внутри приложения. Такая функциональная возможность отсутствовала при программировании в среде Windows, но теперь, с появлением .NET Framework, реагировать на события, происходящие в файловой системе, становится намного проще.
8.1.2.Классы File и Directory
Оба класса предоставляют большое количество методов для осуществления манипуляций над самой файловой системой, а также над файлами и директориями, которые хранятся в этой файловой системе. Эти методы являются статическими и позволяют перемещать файлы, запрашивать и вносить изменения в атрибуты, также создавать объекты FileStream. Как мы узнали в главе 8, статические методы могут вызываться классами без создания их экземпляров.
Некоторые из наиболее полезных статических методов класса File приведены в таблице 9.1. В таблице 9.2 представлены некоторые полезные статические методы класса Directory:
Таблица 9.1 – Основные методы класса File | |
Метод | Описание |
Сору () | Копирует файл в указанное место. |
Create() | Создает файл в соответствии с заданным путем. |
Delete() | Уничтожает файл. |
Open () | Возвращает объект FileStream по заданному пути. |
Move () | Перемещает файл в новое место. При этом имеется возможность задать для нового файла другое имя. |
8.1.3.Класс FileInfo