Вложенные классы в C#

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

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

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

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

Вложенные классы по нескольким причинам предоставляют отличное решение такой проблемы.

Вложенные классы имеют доступ ко всем членам, видимым содержащему их классу, даже если эти члены являются приватными. Рассмотрим следующий код, который представляет контейнерный класс, включающий экземпляры GeometricShape:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System.Collections;
public abstract class GeometricShape
{
public abstract void Draw();
}
public class Rectangle : GeometricShape {
public override void Draw() {
System.Console.WriteLine( "Rectangle.Draw" );
public class Circle : GeometricShape {
public override void Draw() {
System.Console.WriteLine( "Circle.Draw" ) ;
}
public class Drawing : IEnumerable {
private ArrayList shapes;
private class Iterator : IEnumerator
{
public Iterator ( Drawing drawing ) {
this.drawing = drawing;
this.current = -1;
}
public void Reset () {
current = -1;
}
public bool MoveNextO {
++current;
if ( current < drawing.shapes.Count )  {
return true;
} else {
return false;
}
public object Current {
get {
return drawing.shapes[ current ];
}
private Drawing drawing;
private int current;
}
public Drawing ()
shapes = new ArrayList() ;
public IEnumerator GetEnumerator ()
return new Iterator ( this );
public void Add( GeometricShape shape )
shapes.Add( shape );
}
public class EntryPoint {
static void Main()
{
Rectangle rectangle = new Rectangle ();
Circle circle = new Circle ();
Drawing drawing = new Drawing ();
drawing.Add( rectangle );
drawing.Add( circle );
foreach ( GeometricShape shape in drawing )  { shape.Draw();
}

В этом примере демонстрируется ряд новых концепций, в том числе интерфейсы IE numerable и IEnumerator. Давайте в первую очередь сосредоточим внимание на использовании вложенного класса. В коде видно, что класс Drawing поддерживает метод GetEnumerator, являющийся частью реализации IEnumerable. Он создает и возвращает экземпляр вложенного класса Iterator.

Но вот что интересно. Класс Iterator принимает ссылку на экземпляр содержащего его класса Drawing в виде параметра конструктора. Затем он сохраняет этот экземпляр для последующего использования, чтобы можно было добраться до коллекции shapes внутри объекта drawing. Обратите внимание, что коллекция shapes в классе Drawing объявлена как private.

Это не имеет значения, потому что вложенные классы имеют доступ к приватным членам охватывающего их класса.

Также обратите внимание, что класс Iterator сам по себе объявлен как private. Не вложенные классы могут объявляться только как public или internal и по умолчанию являются internal. К вложенным классам можно применять те же модификаторы доступа, что и к любым другим членам класса.

В данном случае класс Iterator объявлен как private, так что внешний код вроде процедуры Main не может создавать экземпляры Iterator непосредственно.

Это может делать только сам класс Drawing. Возможность создания экземпляров Iterator не имеет смысла ни для чего другого, кроме Drawing.GetEnumerator.

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

1
2
3
4
5
6
7
8
9
public class A {
public class В
{
}
public class EntryPoint {
static void Main() {
А.В b = new A.B () ;
}
}

Иногда имя нового вложенного класса может скрыть имя члена внутри базового класса за счет использования ключевого слова new, подобно тому, как это делается при сокрытии методов. Это бывает очень редко, и в большинстве случаев данной ситуации можно избежать.

Рассмотрим следующий пример:

1
2
3
4
5
6
7
8
public class А {
public void Foo()
{
}
public class В : A {
public new class Foo
{
}

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

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

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100