|
|
|
Примачание: данное описание не претендует на полноту и будет в дальнейшем дополнено
Одним из основных понятий ООП является класс. Соответственно, это понятие занимает центральное место и в идеологии C#. Описывать понятие класса здесь явно не место - мы предполагаем, что читатель уже знаком с каким-либо объектно-ориентированным языком. Возможно мы дополним наш учебник ликбезом по ООП в дальнейшем.
Здесь же мы лишь опишем класс как структуру данных, содержащую члены-данные (такие как константы и поля), функциональные члены (методы, свойства, события, индексаторы, операторы, конструкторы и деструкторы) и вложенные типы. Классы также поддерживают наследование.
Простейшее объявление класса выглядит так:
// Класс объявленный как public, доступен отовсюду, включая другие сборки
// Можно также написать internal - класс будет доступен только из текущей сборки
public class HelloWorldClass
{
public void HelloWorld()
{
Console.WriteLine("Hello, world");
}
}
|
Можно также сделать класс абстрактным:
public abstract class AgentFactory
{
public void WriteInfo()
{
Console.WriteLine("I'm agent factory");
}
public abstract Agent CreateAgent();
}
|
Такие классы могут содержать абстрактные методы (методы, которые не реализованы в текущем классе, но должны быть реализованы в неабстрактных потомках). Фбстрактные классы не могут быть инстанцированы. То есть для использования этого класса, должен быть написан потомок:
public class FootballAgentFactory : AgentFactory
{
public Agent CreateAgent()
{
return new FootballAgent();
}
}
|
Если же вы наоборот не хотите, чтобы от вашего класса кто-то наследовался, объявите его как sealed:
public sealed class IAmTheCoolest
{
public void WhoAmI()
{
Console.WriteLine("I am cool and I don't want any inheritance");
}
}
// Это приведет к ошибке компиляции
public class IAmMoreCoolThanTheCoolest : IAmTheCoolest
{
public void IAmTheCoolest()
{
Console.WriteLine("I'm even cooler that the coolest");
}
}
|
Как вы уже поняли, после двоеточия указывается класс, от которого наследуется данный. Наследоваться в C# можно только от одного класса, но можно реализовывать несколько интерфейсов:
public class Base1
{
}
public class Base2
{
}
public interface Interface1
{
}
public interface Interface2
{
}
// Попытка наследования нескольких классов приведет к ошибке
public class MyClass : Base1, Base2
{
}
// Зато можно реализовывать несколько интерфейсов
public class MyClass2 : Base1, Interface1, Interface2
{
}
|
Если не указан никакой базовый класс, то класс автоматически считается наследником класса System.Object или просто object.
В теле класса объявляются его члены. Далее мы опишем все виды членов класса.
Поле представляет переменную, связанную с данным классом или его экземпляром.
public class MyClass
{
// Указывается что это поле доступно из любого класса, даже из другой сборки
// Также можно указать protected - поле будет доступно только из классов-наследников
// и private - доступно только из текущего класса.
public int a;
// Присваиваем начальное значение в конструкторе
public MyClass()
{
a = 3;
}
}
public class Application
{
public static void Main()
{
MyClass myClass = new MyClass();
// a = 5
Console.WriteLine("a = {0}", myClass.a);
myClass.a += 5;
// a = 10
Console.WriteLine("a = {0}", myClass.a);
}
}
|
Также можно обявить поле как readonly - запрещается запись в поле, кроме как при инициалищации посредством инийиализатора или конструктора.
public class MyClass
{
// Присваиваем начальное значение в инициализаторе
readonly int a = 1;
// Присваиваем начальное значение в конструкторе
public MyClass()
{
a = 3;
}
public MyMethod()
{
a = 5; // Ошибка компиляции
}
}
|
Есть еще модификатор volatile, который запрещает некоторые оптимизации, которые могут привести к непредсказуемым последствиям в многопоточных приложениях.
Модификатор static декларирует принадлежность члена класса всему классу, а не конкретному члену. Доступ к таким членам осуществляется через имя класса, а не его экземпляра.
public class MyStaticMemberClass
{
public static int a = 10;
public MyMethod()
{
a = 5;
}
}
public class Application
{
public static void Main()
{
// a = 10
Console.WriteLine("a = {0}", MyStaticMemberClass.a);
MyStaticMemberClass.a = 2;
// a = 2
Console.WriteLine("a = {0}", MyStaticMemberClass.a);
}
}
|
Методы представляют собой действия, которые можно произвести в связи с данным классом.
public class MyHelper
{
private int a, b;
public MyHelper( int a, int b )
{
this.a = a;
this.b = b;
}
public int Max()
{
return a > b ? a : b;
}
}
public class Application
{
public static void Main()
{
MyHelper helper = new Helper(2, 10);
// MAX(2, 10) = 10
Console.WriteLine("MAX(2, 10) = {0}", helper.Max());
}
}
|
Возможные модификаторы - уже описанные public, static, protected, abstract и еще virtual, override, new и extern.
public class SampleClass
{
public static int a;
public int b;
public static void DoubleA()
{
a *= 2;
}
public void SetB()
{
b = a;
}
}
public class Application
{
public static void Main()
{
SampleClass sc1 = new SampleClass(), sc2 = new SampleClass();
SampleClass.a = 1;
sc1.SetB();
SampleClass.a = 2;
sc2.SetB();
Console.WriteLine("sc1.b = {0}, sc2.b = {1}", sc1.b, sc2.b);
}
}
|
class Base
{
public void MyMethodA()
{
Console.WriteLine("Base.MyMethodA");
}
public virtual void MyMethodB()
{
Console.WriteLine("Base.MyMethodB");
}
}
class Child : Base
{
public new void MyMethodA()
{
Console.WriteLine("Child.MyMethodA");
}
public override void MyMethodB()
{
Console.WriteLine("Child.MyMethodB");
}
}
class Class1
{
static void Main(string[] args)
{
Base childClass = (Base)new Child();
// Base.MyMethodA
childClass.MyMethodA();
// ChildClass.MyMethodB
childClass.MyMethodB();
}
}
|
Методам также можно передавать параметры, которые параметризуют его действия.
Формат списка параметров такой:
модификатор параметра тип параметра имя параметра
Единственной необычной частью является модификатор параметра. Существуют следующие модификаторы параметров:
Также существует возможность декларировать метод как принимающий произвольное число аргументов. Для этого используется следующий синтаксис:
param тип элемента[] имя списка
|
Для пояснения приведем пример:
class Application
{
static void SampleMethod( string text, params string[] args )
{
Console.WriteLine(text);
foreach (string s in args)
{
Console.WriteLine(s);
}
}
public static void Main()
{
SampleMethod("Parameters list:", "Param1", "Param2", "Param3");
}
}
|
Свойства расширяют возможности полей. Они представляют собой типизированные объекты, у которых можно получить значение и (опционально) записать в них новое значение. При выполнении этих действий выполняется определяемый программистом код. Синтаксис определения полей следующий:
модификаторы тип имя
{
get Тело get
get Тело set
}
|
В теле get пишется код, который выполняется при получении значения свойства. В теле set пишется код, который выполняется при установке нового значения. При этом в set доступно присваеваемое значение через ключевое слово value:
public class SampleClass
{
private static int a;
public static int A
{
get
{
return a;
}
set
{
a = value;
}
}
public static void Main()
{
A = 2;
Console.WriteLine("A = {0}", A);
}
}
|
Этот пример демонстрирует распространенный прием инкапсуляции полей.
Одним из важных направлений развития языков программирования является удобство и наглядность кода. Именно стремление упростить структуру кода привело в свое время к перегрузке операторов С++ и свойствам в Object Pascal. Предлагая новый язык специально для своей новой платформы, Microsoft, разумеется, не могла не учесть это сторону вопроса. Поэтому язык включает в себя свойства (properties) и индексаторы (indexers).
При написании приложений часто возникает необходимость создавать объекты, представляющие коллекцию других объектов (они не обязательно явно хранят в себе все эти объекты, но предоставляют доступ к ним). В языках вроде C или Java для этого использовались специальные функции (методы) вроде GetItem( int index ). Конечно, это возможный подход, но часто, когда нужно часто обращаться к элементам коллекции, неудобно каждый раз писать громоздкие конструкции.
Для упрощения синтаксиса доступа к объектам коллекции в C# используются индексаторы. Они предоставляют синтаксис сходный с используемым при адресации массива, за исключением того, что в качестве типа данных индекса может использоваться любой тип.
Для того, чтобы объявить в классе индексатор, нужно использовать следующий синтаксис:
тип this [список_параметров]
|
Где тип - тип возвращаемого значения (тип элемента коллекции), а список параметров - типы и имена параметров, используемых для идентификации элемента коллекции.
Индексатор может иметь любые модификаторы, кроме static, а к параметрам нельзя применять модификаторы out и ref.
Для определения кода индексатора, нужно прописать ему метода доступа - get и, возможно, set (если индексатор у вас позволяет менять элементы). При этом в методе get доступны все параметры индексатора как переменные, а в методе set дополнительно к ним параметр value, представляющий новое значение элемента. Таким образом, индексатор примет вид, аналогичный следующему:
public object this [int index]
{
get
{
return GetItem(index);
}
set
{
SetItem(index, value);
}
}
|
Для полной ясности картины, приведем небольшой пример:
using System;
public CustomIndexerClass
{
// Объявляем внутренний массив, где будут храниться действительные значения
private int internalArray = new int[10];
// Объявляем индексатор
public int this [int index]
{
// Доступ на чтение
get
{
// Проверка границ
if (index < internalArray.GetLowerBound(0) || index > internalArray.GetUpperBound(0))
return 0;
// Врщвращаем значение соответствующего элемента массива
return internalArray[index];
}
set
{
// Проверка границ и запись нового значения
if (index >= internalArray.GetLowerBound(0) && index <= internalArray.GetUpperBound(0))
internalArray[index] = value;
}
}
public class ApplicationClass
{
public static void Main()
{
CustomIndexerClass myIndexer = new CustomIndexerClass();
myIndexer[1] = 10;
myIndexer[5] = 7;
for (int i = 0; i < 10; i++)
Console.WriteLine("myIndexer[{0}] = {1}\n", i, myIndexer[i]);
}
}
}
|
Эта программа создает класс, имеющий индексатор, который просто читает или записывает значения во внутреннем массиве. Главная программа сначала изменяет несколько ячеек массива, пользуясь индексатором, а затем читает все его ячейки последовательно.
Важным моментом здесь является инициализация базового класса. Для выхова конструктора базового класса используется ключевое слово base. Вместо долгого формального описания, просто покажем пример:
class BaseClass
{
int _a;
public int A
{
get
{
return _a;
}
}
public BaseClass( int a )
{
_a = a;
}
}
class InheritedClass : BaseClass
{
public InheritedClass() : base(1)
{
}
}
|
В качестве примера покажем интереснейшую реализацию паттерна Singleton. В разделе "Переменные" был показана стандартная реализация, которую легко перенести на большинство языков. Здесь же мы приведем вариант, использующий особенности C#.
public class Singleton
{
private static Singleton _instance;
public static Singleton Instance
{
get
{
return _instance;
}
}
public static Singleton
{
_instance = new Singleton();
}
}
|
Деструктор - это метод, который вызывается при уничтожении объекта. Так как в .NET CLR используется сборщик мусора, нельзя явно удалить определенный экземпляр. Удаление происходит когда никакой код уже не может воспользоваться этим экземпляром. Деструкторы определяются как методы с именем, совпадающим с именем класса, предваренным знаком ~ (тильда):
public class A
{
~A
{
Console.WriteLine("I'm being descructed");
}
}
public class Application
{
public static void Main()
{
A a = new A();
a = null;
}
}
|
Программа выведет "I'm being destructed"
| << Предыдущая | Следующая >> |
| Контакт | Реклама на сайте | Спонсорам | Веб мастерам |
Лицензионное соглашение
- © 2000-2012 dotSITE
Хостинг .NET предоставлен
PARKING.RU
Поддержку сайта осуществляет Murano Software Inc., Offshore software development