У вас вопросы?
У нас ответы:) SamZan.net

Лекция 10. Классы. Определение классов в С В языке С для описания классов используется ключевое слово clss

Работа добавлена на сайт samzan.net:

Поможем написать учебную работу

Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 28.12.2024

Лекция 10. Классы.

Определение классов в С#

В языке С# для описания классов используется ключевое слово class. При этом необходимо задействовать следующую основную конструкцию:

class myClass

{

 //члены класса

}

Этот код определяет класс с именем myClass. После того как мы определили класс, мы вольны создавать экземпляры этого класса в нашем проекте везде, где имеется доступ к этому определению. По умолчанию классы определяются как internal (внутренние), что означает, что доступ к ним будет иметь только код текущего проекта. Мы можем указать это явно, воспользовавшись ключевым словом определения доступа internal (хотя и не обязаны этого делать):

internal class MyClass

{

   // члены класса

}

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

public class MyClass

{

  // члены класса

}

Обратите внимание, что классы, которые объявляются самостоятельно, не могут быть частными или защищенными. Соответствующие модификаторы — private и protected —можно использовать только для описания классов, являющихся членами других классов.

Кроме ключевых слов этих двух модификаторов доступа, для описания класса можно использовать ключевое слово abstract (абстрактный; создавать экземпляры такого класса запрещено, он может только наследоваться, в нем допускается наличие абстрактных членов) или sealed (изолированный; такой класс не может наследоваться). Эти ключевые слова являются взаимоисключающими. Таким образом, абстрактный класс должен определяться следующим образом:

public abstract class MyClass

{

         // члены класса, среди которых могут быть абстрактные

}

В данном случае MyClass является общим абстрактным классом, хотя также возможны и внутренние абстрактные классы. Изолированные классы определяются следующим образом:

public sealed class MyClass

{

         // члены класса

}

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

public class MyClass : MyBase

{

      // члены класса

}

Заметьте, что в определении класса в С# допускается наличие только одного базового класса, и если данный класс наследуется от некоторого абстрактного класса, то в нем должны быть реализованы все наследуемые абстрактные члены (если, конечно, этот класс сам не является абстрактным).

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

public class MyBase

{

       // члены класса

}

internal class MyClass : MyBase

{

       // члены класса

}

А такой код не пройдет компиляцию:

internal class MyBase

{

    // члены класса

}

public class MyClass : MyBase

{

     // члены класса

}

Если базовый класс не задан, то класс наследуется только от базового класса System.Object (для которого в  С # используется синоним object). System.Object безусловно является корневым в иерархии наследования для всех классов. Мы будем изучать этот фундаментальный класс ниже.

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

Например, мы можем добавить в класс MyClass интерфейс:

public class MyClass : IMyInterface

{

    // члены класса

}

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

Следующее объявление неверно, поскольку базовый класс MyBase не является первым вхождением списка наследования:

public class MyClass : IMyInterface, MyBase

{

       // члены класса

}

Правильный способ задать и базовый класс, и интерфейс следующий:

public class MyClass : MyBase, IMyInterface

{

    // члены класса

}

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

public class MyClass : MyBase, IMyInterface, IMySecondInterface

{

     // члены класса

}

Для того чтобы вы смогли быстро освежить свою память, ниже приводится таблица допустимых в определениях классов комбинаций модификаторов доступа:

Модификатор

Значение

Отсутствует либо internal

Класс доступен только в рамках текущего проекта

public

Класс доступен отовсюду

abstract или internal abstract

Класс доступен только в рамках текущего проекта, не допускает создания экземпляров, может только наследоваться

public abstract

Класс доступен отовсюду, не допускает создания экземпляров, может только наследоваться

sealed или internal sealed

Класс доступен только в рамках текущего проекта, не может наследоваться, допускает только создание экземпляров

public sealed

Класс доступен отовсюду, не может наследоваться, допускает только создание экземпляров

Определение интерфейсов

Интерфейсы объявляются тем же способом, что и классы, только вместо ключевого слова Class используется ключевое слово Interface. Например:

interface IMyInterface

{

     // члены интерфейса

}

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

public interface IMyInterface

{

       // члены интерфейса

}

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

Наследование для интерфейсов определяется аналогично наследованию для классов. Основное отличие здесь в том, что мы можем использовать интерфейсы с множественными базами, например:

public interface IMyInterface : IMyBaseInterface, IMyBaseInterface2

{

// члены интерфейса

}

Интерфейсы, как и классы, наследуются от System.object. Этот механизм допускает полиморфизм интерфейсов. Однако, как отмечалось ранее, нельзя создать экземпляр интерфейса таким же способом, как и экземпляр класса.

Пример программы:

namespace Ch09Ex01

{

   public abstract class MyBase

  {

   }

internal class MyClass : MyBase

{

}

public interface IMyBaseInterface

{

}

internal interface IMyBaseInterface2

 }

internal interface IMyInterface : IMyBaseInterface, IMyBaseInterface2

{

}

internal sealed class MyComplexClass : MyClass, IMyInterface

{

}

// <summary>

/// Summary description for Glass 1.

/// </summary>

class Classl

{

static void Main(string [] args)

{

MyComplexClass myObj = new MyComplexClass();

Console.WriteLine(myObj.ToString());

}

  }

}

В данном проекте описаны классы и интерфейсы, обладающие иерархией наследования, показанной на рисунке cправа. Мы включили сюда Сlass1, так как его описание аналогично описанию остальных наших классов, хотя он и не входит в основную иерархию классов. Метод Main (), находящийся в этом классе, является точкой входа в приложение; этот момент обсуждался раньше.

MyBase и IMyBaseInterface определены как общие, поэтому они доступны из других проектов. Остальные классы и интерфейсы являются внутренними и доступны только в рамках настоящего проекта. В программе вызывается метод ToString() объекта myObj, который является Экземпляром Класса MyComplexClass:

MyComplexClass myObj = new MyComplexClass();

Console.WriteLine(myObj .ToString() ) ;

Это один из наследуемых от System.Object методов (класс System.Object не изображен на диаграмме просто для краткости). ToString() возвращает имя класса данного объекта в виде строки.

System.Object

Поскольку все классы наследуются от System.Object, то они обладают доступом ко всем его защищенным и общим членам. Поэтому имеет смысл ознакомиться с тем, что нам доступно. System.Object содержит следующие методы:

Метод

Возвращае-

мый  тип

Виртуаль-

ный

Стати-

ческий

Описание

Object()

Отсутствует

Нет

Нет

Конструктор типа System.Object. Автоматически вызывается конструкторами производных типов

-Object()

(также известен под

именем Finalize () —

см. следующий раздел)

Отсутствует

Нет

Нет

Деструктор для типа System.Object. Автоматически вызывается деструкторами производных типов, сам по себе вызван быть не может

Equals(object)

bool

Да

Нет

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

на предмет одинакового значения,

этот метод может быть переопределен

Equals(object, object)

bool

Нет

Да

Сравнивает два объекта, передаваемых ему в качестве параметров, на предмет того, равны ли они. Эта проверка выполняется с помощью метода Equals (object). Заметьте, что, если оба объекта обладают нулевыми ссылками, возвращается значение true

ReferenceEquals

(object,

object)

bool

Нет

Да

Сравнивает два переданных ему объекта, определяя, являются ли они ссылками на один и тот же экземпляр

ToString()

String

Да

Нет

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

MemberwiseClone()

object

Нет

Нет

Копирует объект посредством создания нового экземпляра объекта и копирования всех членов. Обратите внимание, что

такое копирование членов не  приводит к созданию новых экземпляров этих членов. Любые члены ссылочного типа в новом

объекте будут ссылаться на те же объекты, на которые они ссы-лаются в исходном классе. Рас-сматриваемый метод является

защищенным, поэтому его можно

использовать внутри класса или внутри производных классов

GetType()

System.туре

Нет

Нет

Возвращает тип объекта в виде объекта System.Type

GetHashCode()

int

Да

Нет

Используется как функция хеши-рования для объектов. Хеш-функция — это функция, воз-вращающая значение, которое позволяет идентифицировать

объект в некоторой сжатой форме

Эти методы являются основными, они должны поддерживаться всеми типами объектов в .NET Framework, хотя, возможно, некоторые из них вам никогда не придется использовать (или только при определенных обстоятельствах, как, например, GetHashCode ()).

Метод GetType() полезен при использовании полиморфизма, поскольку он позволяет выполнять разные коды для объектов разных типов, а не один и тот же код для всех объектов, как это часто бывает. Например, если у нас имеется функция, которой передается параметр типа object (это означает, что мы можем передавать ей практически все, что угодно), то можно предусмотреть выполнение в ней специальных работ в случае поступления объектов конкретного типа. Воспользовавшись сочетанием GetType() с Typeof() (оператор С#, который преобразовывает имя класса в объект system.туре), мы получаем возможность выполнять сравнения примерно следующим образом:

if (typeof(myObj) == typeof(MyComplexClass))

{

//Объект myObj является экземпляром класса MyComplexClass

       }

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

Конструкторы и деструкторы

Когда мы описываем какой-либо класс в С#, то в большинстве случаев нет необходимости описывать соответствующие ему конструкторы и деструкторы, поскольку объект базового класса System.object обеспечивает их реализацию по умолчанию.

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

class MyClass

{

       public MyClass ()

       {

             // Код конструктора

      }

}

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

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

class MyClass

{

         private MyClass ()

     {

         // Код конструктора

      }

}

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

class MyClass

{

        public MyClass ()

        {

          //Код конструктора, использующийся по умолчанию

         }

                            public MyClass(int myInt)

                           {

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

           // умолчанию (используется параметр my Int}

         }

}

Количество задаваемых конструкторов не ограничено.

Деструкторы определяются с помощью несколько иного синтаксиса. Деструктор, используемый в .NET (и предоставляемый классом System.Object), называется Finalize(), однако для объявления деструктора используется другое имя. Вместо того чтобы переопределять Finalize(), мы используем следующий код:

class MyClass

{

  ~MyClass ()

 {

            // тело деструктора

        }

    }

Код, заключенный в деструкторе, будет выполняться при сборке мусора, позволяя освобождать удерживаемые ресурсы. После вызова деструктора происходят явные вызовы деструкторов базовых классов, включая вызов Finalize() в корневом классе System.Object. Такой способ позволяет .NET Framework гарантировать выполнение, поскольку переопределение Finalize() означало бы, что необходимы явные вызовы базовых классов, а это таит в себе потенциальную опасность.

Последовательность выполнения конструкторов

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

Для того чтобы создать экземпляр производного класса, необходимо создать экземпляр его базового класса. В свою очередь, чтобы создать экземпляр этого базового класса, требуется создать экземпляр базового класса этого базового класса — и так далее до System.Object. В результате, какой бы конструктор ни использовался для создания класса, System.Object.Object () всегда будет вызываться первым.

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

Рассмотрим следующую иерархию объектов:

public class MyBaseClass

{

     public MyBaseClass()

 {

 }

public MyBaseClass(int i)

{

          }

          }

public class MyDerivedClass : MyBaseClass

   {

public MyDerivedClass()

      {

      }

public MyDerivedClass(int i)

    {

     }

public MyDerivedClass(int i, int j)

{

}}

Если мы попытаемся создать экземпляр класса MyDerivedClass следующим образом:

    MyDerivedClass myObj = new MyDerivedClass() ;

то это приведет к такой последовательности событий:

• Выполнится конструктор System.Object.Object().

• Выполнится конструктор MyBaseClass.MyBaseClass().

• Выполнится конструктор MyDerivedClass.MyDerivedClass().

Если же мы попытаемся создать экземпляр класса таким образом:

MyDerivedClass myObj = new MyDerivedClass(4);

то соответствующая последовательность будет иметь следующий вид:

• Выполнится конструктор System.Object.Object().

• Выполнится конструктор MyBaseClass.MyBaseClass(int i).

• Выполнится конструктор MyDerivedClass.MyDerivedClass(int i).

Наконец, если мы воспользуемся следующим вариантом:

MyDerivedClass myObj = new MyDerivedClass(4, 8) ;

то произойдет вот что:

• Выполнится конструктор System.Object.Object( ) .

• Выполнится конструктор MyBaseClass.MyBaseClass () .

Выполнится конструктор MyDerivedClass.MyDerivedClass(int i, int j ) .

При таком подходе мы получаем возможность поместить код, ответственный за обработку параметра int i, в MyBaseClass (int i ) , подразумевая, что конструктору MyDerivedClass(int i, int j) достанется меньше работы — ему придется обрабатывать только параметр int j . (Подобные рассуждения строятся на основе предположения, что параметр int i в обоих случаях имеет один и тот же смысл, что может и не выполняться, хотя на практике при таком способе оформления обычно выполняется.) С# при необходимости позволяет нам задать именно такой тип поведения.

Для этого требуется просто указать конструктор базового класса в определении конструктора нашего производного класса следующим образом:

public class MyDerivedClass : MyBaseClass

public MyDerivedClass (int i, int j) : base(i)

Ключевое слово base указывает .NET, что в процессе создания экземпляра следует использовать конструктор базового класса с сигнатурой, совпадающей с заданной. В данном случае мы задействуем единственный параметр int, поэтому будет вызван MyBaseClass (int i) . Конструктор MyBaseClass () не будет вызываться, т. е. последовательность событий будет такой же, как и в последнем примере,— что, собственно, в данном случае и требовалось.

Существует возможность с помощью этого же ключевого слова задавать литеральные значения для конструкторов базового класса, допустим, применяя конструктор класса MyDerivedClass, использующийся по умолчанию, для вызова конструктора класса MyBaseClass, использующегося не по умолчанию:

public class MyDerivedClass : MyBaseClass

{

  public MyDerivedClass() : base(5)

  {

   }

   . . .

}

В этом случае последовательность событий будет иметь такой вид:

• Выполнится конструктор System.Object.Object().

• Выполнится конструктор MyBaseClass.MyBaseClass (int i).

• Выполнится конструктор MyDerivedClass.MyDerivedClass() .

Кроме ключевого слова base, в этом контексте может использоваться еще одно ключевое слово: this. Оно указывает  процессу создания экземпляра в .NET на необходимость использовать конструктор не по умолчанию для текущего класса, прежде чем будет вызван указанный конструктор. Например:

public class MyDerivedClass : MyBaseClass

public MyDerivedClass () : this(5, 6)

public MyDerivedClass (int i, int j) : base(i)

Это приведет к такой последовательности событий:

• Выполнится конструктор System.Object.Object().

• Выполнится конструктор MyBaseClass.MyBaseClass (int i).

• Выполнится конструктор MyDerivedClass. MyDerivedClass (int i, int j).

• Выполнится конструктор MyDerivedClass .MyDerivedClass ().

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

Инструменты ООП в Visual Studio.NET

Поскольку ООП является основополагающим понятием .NET Framework, то в VS предусмотрены определенные инструменты, облегчающие разработку приложений с использованием ООП. В этом разделе будут рассмотрены некоторые из них.

Окно Class View

Окно Solution Explorer делит экранное пространство с окном Class View (окно просмотра классов, в котором отображается иерархия классов приложения. Кроме того, названное окно позволяет моментально узнать характеристики используемых классов. Существует несколько режимов просмотра этой информации, причем по умолчанию используется

режим Sort By Type (упорядочивание по типу). Для проекта, который был взят в качестве примера в предшествующем разделе, это

окно будет иметь вид, подобный представленному на рисунке слева.

В окне Class View может использоваться большое количество разнообразных символов, среди которых (см. рис. ниже)

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

Рис.2

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

Помимо просмотра этой информации, в данном окне можно получить доступ к коду большинства элементов. Двойной щелчок мышью по элементу или двойное нажатие правой кнопки мыши и выбор пункта меню Go To Definition (переход к определению) позволяет перейти непосредственно к тому коду, который описывает данный элемент, если этот код является доступным. В ином случае — например, если он находится в каком-либо базовом типе, к которому нет доступа,— мы перейдем в окно Object Browser (браузер объектов).

Окно Object Browser

Окно Object Browser — это расширенная версия окна Class View, которая позволяет просматривать другие классы, доступные нашему проекту, и даже совершенно посторонние классы. Переход в это окно осуществляется либо автоматически (например, в ситуациях, подобных описанной в предшествующем разделе), либо вручную через View | Other Windows | Object Browser. Это окно появляется в основном окне и позволяет осуществлять просмотр так же, как и в окне Class View.

В окне Object Browser (см. рис.), в отличие от окна Class View, классы и члены классов изображаются в различных местах, при этом в их число включаются все модули .NET, на которые ссылается данный проект. Здесь можно просматривать, например, вхождения пространства имен System.

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

///< Summary >

/// Краткое описание класса Class1.

/// </summary>

class Class1

{

static void Main(string[] args)

{

   MyComplexClass myObj = new MyComplexClass() ;

  Console.WriteLine(myObj.ToString());

}

}

Добавление классов

В состав VS входят инструменты, которые позволяют ускорять выполнение некоторых наиболее распространенных задач, и некоторые из них применимы для ООП. Один из таких инструментов позволяет быстро добавлять в проект новые классы при минимуме информации, которую необходимо набирать. Доступ к этому инструменту можно получить либо через пункт меню File | Add New Item..., либо с помощью двойного щелчка мышью на имени проекта в окне Solution Explorer и выбора подходящего элемента. В обоих вариантах появляется диалоговое окно, которое позволяет выбрать тип элемента, который требуется добавить. Для того чтобы добавить класс, необходимо выбрать вхождение Class в окне, расположенном справа, ввести имя файла, в котором будет содержаться этот класс, и затем нажать Open.  

В результате создаваемому классу будет присвоено имя в соответствии с именем, выбранным для файла (см. рис. слева). В примере, были  добавлены  описания классов в файл Class1.cs вручную. Однако часто хранение различных классов в разных файлах позволяет упростить контроль за ними. Когда мы откроем проект ch09Ex0i, мы увидим, что в  файле MyNewClass.cs появится следующий код:

using System;

namespace Ch09Ex01

{

/// <summary>

  /// Краткое описание класса MyNewClass

  /// </summary>

  public class MyNewClass

  {

       public MyNewClass{)

       {

        //

       // Следует сделать: вставить сюда логику конструктора

       //

     }

  }

}

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

Проекты, представляющие собой библиотеки классов

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

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

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

Пример.  

1. Создайте новый проект с именем ChO9ClassLib в директории С:\BegCSharp\Chapter9, воспользовавшись диалоговым окном New Project .

2. Переименуйте файл Сlass1.cs В MyExternalClass.cs (это можно сделать, щелкнув правой кнопкой мыши по имени файла в окне Solution Explorer и выбрав опцию Rename (переименовать)).

3. Измените код в MyExternalClass.cs так, чтобы он отражал данное изменение имени класса. Для этого запишем код так:

public class MyExternalClass

{

  public MyExternalClass()

   {

    // Следует сделать: вставить сюда логику конструктора

   }

}

4. Добавьте в проект новый класс, использовав в качестве имени файла MyInternalClass.cs.

5. Модифицируйте код таким образом, чтобы класс MyInternalClass стал внутренним:

internal class MyInternalClass

{

 public MyInternalClass()

 {

  // Следует сделать: вставить сюда логику конструктора

  }

}

6. Откомпилируйте проект (обратите внимание, что в этом проекте не имеется точки входа, поэтому его невозможно запустить как обычный проект — вместо этого вы можете просто построить его, воспользовавшись опцией меню Build | Build).

7. Создайте новое консольное приложение с именем chO9ExO2 в директории C:\BegCSharp\Chapter9.

8. Выберите пункт меню Project | Add Reference... либо выберите ту же самую опцию, дважды щелкнув правой кнопкой мыши по пункту References в окне Solution Explorer.

9. Щелкните по кнопке Browse..., перейдите в директорию C:\BegCSharp\Chapter9\ChO9ClassLib\bin\Debug\  и дважды щелкнуть мышкой по  ChO9ClassLib.dll.

10. Нажмите ОК.

11. После того как выполнение операции будет завершено, убедитесь, что в окне Solution Explorer добавилась новая ссылка (см. рис. слева).

12. Откройте окно Object Browser и посмотрите,

какие объекты

содержатся

в новой ссылке

(см. рис. справа).

13. Внесите следующие изменения в Class1.es:

using System;

using ChO9ClassLib;

           namespace ChO9ExO2

         {

/// <summary>

/// Краткое описание класса Classl.

/// </summary>

class Classl

 {

      static void Main(string[] args)

         {

            MyExternalClass myObj = new MyExternalClass();

            Console.WriteLine(myObj.ToString());

          }

     }

}

14. Запустите приложение.

В этом практикуме было создано два проекта, один из которых является библиотекой классов, а второй — консольным приложением. Проект, представляющий собой библиотеку классов,— ChO9ClassLib — включает два класса: MyExternalClass, являющийся общедоступным и MyInternalClass, к которому имеется только внутренний доступ. Консольное приложение — chO9ExO2 — содержит простой код, использующий библиотеку классов. Для того, что бы использовать классы из ChO9ClassLib, в приложение ChO9ExO2 была добавлена ссылка на DLL, которая была создана проектом библиотеки классов.

Для этого просто указана соответствующая DLL. Для задач, решаемых в данном примере, оказалось достаточно указать на выходной файл библиотеки классов, однако с тем же успехом мы могли бы скопировать этот файл в такое место, где он был бы локальным по отношению к chO9ExO2. Это позволило бы продолжать разработку библиотеки классов, не затрагивая консольное приложение. Для того чтобы заменить старую версию модуля на новую, потребуется записать вновь созданную DLL на место старой.

После добавления ссылки была получена возможность просмотреть перечень доступных классов с помощью Object Browser. Поскольку один из двух классов — MyInternalClass — является внутренним, то он не виден: этот класс недоступен внешним проектам. Напротив, класс MyExtemalClass доступен, и он используется в консольном приложении.

Модно  попробовать заменить код в консольном приложении на код, который попытается использовать внутренний класс следующим образом:

static void Main (string [] args)

{

MyInternalClass myObj = new MyInternalClass();

Console.WriteLine(myObj .ToStringO );

}

При попытке откомпилировать такой код будет получено следующее сообщение об ошибке:

C:\BegCSharp\Chapter9\ChO9ExO2\Class1.cs(13):<Ch09ClassLib.MylnternalClass>

(C:\BegCSharp\Chapter9\ChO9ExO2\Classl.cs(13):класс'ChO9ClassLib.MyInternalClass

is inaccessible due to its protection level недоступен из-за уровня его защиты)

Этот способ использования классов из внешних модулей является ключевым при программировании на С# в .NET Framework. Фактически так мы поступаем со всеми классами в .NET Framework, поскольку работа с ними организована аналогичным образом.

Сравнение интерфейсов и абстрактных классов

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

Итак, сначала о сходстве.

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

Теперь о различиях.

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

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

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

Базовый класс — Train (поезд) — содержит основное определение поезда, куда включается, например, размер колес и тип двигателя (который может быть паровым, дизельным и т . д . ) . Этот класс, однако, является абстрактным, поскольку в природе не существует такой вещи, как "обобщенный" поезд. Для создания "конкретного" поезда нужно задать характеристики, присущие данному поезду. Это, в свою очередь, требует создания производных классов вроде PassengerTrain (пассажирский поезд), FreightTrain (грузовой поезд) и 424DoubieBogey (424 колесные пары).

Аналогичным образом может быть определено семейство автомобилей: с помощью базового класса Саr (автомобиль) и производных от него классов Compact (компакт), Suv и Pickup (пикап), Cаr и Train могут даже быть производными от одного общего базового класса (vehicle — средство передвижения), как показано на следующем ниже рисунке. Некоторые из этих классов на более низких уровнях иерархии могут обладать одинаковыми характеристиками, которые определяются их предназначением, а не тем, производными от какого класса они являются. Например, PassengerTrain,

Compact, Suv и Pickup могут перевозить пассажиров, поэтому у них может быть интерфейс IPassengerCarrier (перевозчик пассажиров). FreightTrain и PickUp могут осуществлять перевозку тяжелых грузов, поэтому они могут обладать интерфейсом IHeavyLoadCarrier (перевозчик тяжелых грузов).

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

Типы структур

Структуры и классы очень похожи, хотя структуры представляют собой значимые типы, а классы — ссылочные типы. Что же это может означать на самом деле? Самый простой способ разобраться в этом — рассмотреть некоторый пример.

1. Создать новое консольное приложение с именем Сh09Ex03 в директории C:\BegCSharp\Chapter9.

2. Измените код следующим образом:

namespace Сh09Ex03

{

 class MyClass

 {

   public int val;

  }

 struct myStruct

 {

   public int val;

  }

/// <summary>

/// Summary description for Class1.

/// </summary>

class Classl

{

  static void Main(string[] args)

  {

     MyClass objectA = new MyClass ();

MyClass objectB = objectA;

objectA. val = 10;

objectB.val = 20;

myStruct structA = new myStruct();

myStruct structB = structA;

structA.val = 30;

structB.val =40;

Console. WriteLine(“objectA. val = {0}", objectA.val);

Console.WriteLine(“objects.val = {0}”, objectB.val);

Console.WriteLine(“structA.val = {0}”, structA.val);

Console.WriteLine(“structB.val = {0}”, structB.val) ;

Console.ReadLine();

}

              }

          }

3. Запустите приложение.

В этом приложении содержатся два определения типов, одно из которых описывает структуру с именем myStruct, обладающую единственным общим полем типа int с именем val, а второе описывает класс с именем MyClass, который содержит аналогичное поле.  Далее мы выполняем одинаковые операции над экземплярами обоих типов:

• Объявляем переменную данного типа.

• Создаем новый экземпляр переменной данного типа.

• Объявляем вторую переменную данного типа.

• Присваиваем первую переменную второй переменной.

• Присваиваем значение полю val первого экземпляра переменной.

• Присваиваем значение полю val второго экземпляра переменной.

• Выводим значение поля val обеих переменных.

Хотя мы выполняем над переменными разных типов одни и те же операции, окончательные результаты оказываются различными. При выводе значений поля val обнаруживается, что обе переменные типа object обладают одинаковым значением, в то время как переменные типа структуры имеют различающиеся значения.

Итак, что же произошло?

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

Структуры — это значимые типы. В переменных не хранится никаких ссылок на структуру, вместо этого в них хранится сама структура. Поэтому, когда одна структура присваивается другой переменной типа MyStruct, происходит копирование всей информации из одной структуры в другую. Это поведение идентично тому, которое мы наблюдали ранее для переменных простых типов, таких как тип int.

Поэтому в качестве конечного результата мы получаем две переменные, содержащие разные структуры.

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

Сравнение неглубокого и глубокого копирования

Копирование объектов из одной переменной в другую по значению, а не по ссылке (т. е. копирование тем же способом, который используется при копировании структур) иногда оказывается делом весьма сложным. Поскольку конкретный объект может содержать большое количество ссылок на другие объекты — например, в виде своих полей, этот процесс может потребовать выполнения очень большого количества действий. Простое копирование каждого члена одного объекта в другой может не сработать, потому что некоторые из этих членов сами по себе могут иметь ссылочные типы. В .NET Framework все это учитывается. Почленное копирование простых объектов достигается за счет использования метода MemberwiseClone(), который наследуется от System.object. Данный метод является защищенным, однако не составляет никакого труда определить общий метод на объекте, который его использует. Копирование, которое обеспечивается этим методом, известно под названием неглубокого копирования — в том смысле, что при его осуществлении не принимаются во внимание те члены, которые представляют собой ссылочные типы. Это означает, что ссылочные члены во вновь создаваемом объекте будут

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

Существует интерфейс, который мы можем реализовать и который позволяет выполнить эту процедуру стандартным образом: ICloneable. Если мы собираемся его применить, нам необходимо реализовать единственный метод, содержащийся в нем,— Clone(). Этот метод возвращает значение типа System.Object. Мы можем использовать совершенно произвольные способы обработки для получения этого объекта, реализуя тело этого метода так, как нам требуется. Другими словами, мы можем реализовывать процедуру глубокого копирования в зависимости от нашего желания (например, мы при необходимости можем реализовать и процедуру неглубокого копирования).

PAGE  19




1. Савинков Н.А.html
2.  Хуан Жэньхао- Ньюс Пресс
3.  Политология- предмет структура методы и функции политология в широком смысле слова ~ это интеграционн
4. модульным принципом в программировании
5. сосудистой системы с декомпенсацией различные болезни почек с почечной недостаточностью болезни крови з
6. Реферат- Лексикализация внутренней формы слова
7. Группировка и учет затрат по калькуляционным статьям
8. Ядро заряжено положительно заряд ядра определяет химический элемент к которому относят атом
9. сложными ножками в осенне зимний период Все мастера знают что в осенне ~ зимний период кожа ступней ну
10. а философия науки вырабатывает нормативную методологию на основе которой историк реконструирует ldquo;внут
11. ПОЯСНИТЕЛЬНАЯ ЗАПИСКА К КУРСОВОЙ РАБОТЕ по дисциплине Психология ЮУрГУ~050100
12. КЕМБРИДЖ ЙОРК ЭДИНБУРГ БЛЭР ЛОХНЕСС СТЕРЛИНГ КАРЛАЙЛ ОЗЕРНЫЙ КРАЙ ЧЕСТЕР КАР
13. Принципы трудового права
14. Социально-психологические проблемы руководства и лидерства
15.  256 с Оглавление Предмет методики развития речи детей Сущность методики и ее методологическая основа
16. Тема1- Статистика населения 27 стр
17. Статья- Процессы глобализации и тенденции ее развития
18. на тему Основные принципы и методика составления консолидированной отчетности на примере
19. Взаимодействие видов транспорта
20. ДИПЛОМНАЯ РАБОТА на тему- СОСТОЯНИЕ И ПЕРСПЕКТИВЫ СОТРУДНИЧЕСТВА РФ И ЮНЕСКО ПО ОСНОВНЫМ Н