p.BearFruit; // вызывает реализацию базового класса
Tree t = new Tree;
t.BearFruit; // вызывает реализацию класса наследника
((Plant)t).BearFruit; // вызывает реализацию базового класса,
// используя наследника.
}
}
}
Выполнение
этого примера создает следующий вывод:
Generic plant fruit
Tree fruit is:->Mango
Generic plant fruit
Необходимо отметить, что существует различие между сокрытием метода и обыкновенным полиморфизмом. Полиморфизм всегда будет предоставлять для вызова метод класса-наследника.
Примечание. Во время написания этой книги автор обнаружил, что сокрытие метода компилируется без ошибок и предупреждений, даже когда ключевое слово
new
не было использовано.
Чтобы предоставить функциональность переопределения, используются модификаторы
virtual
и
override
. Все методы в базовом классе, которые будут переопределяться, должны применить ключевое слово
virtual
. Чтобы реально переопределить их, в классе-наследнике используется ключевое слово
override
. Ниже представлен пример класса
Tree
, измененный для вывода переопределенной функциональности:
class Tree Plant {
public Tree {}
public override void Fruit {
Console.WriteLine("Tree fruit is:->Mango");
}
}
Компиляция и выполнение этого создают следующий вывод.
Generic plant fruit
Tree fruit is:->Mango
Tree fruit is:->Mango
Как можно видеть, вызывается самый последний переопределенный метод
Fruit
независимо от использования cтратегии преобразования
((Plant)t).BearFruit
, которая применялась ранее для ссылки на метод
Fruit
базового класса. Модификатор new может также использоваться для сокрытия любого другого типа наследованных из базового класса членов аналогичной сигнатуры.
Чтобы помешать случайному наследованию класса, используется ключевое слово
sealed
. В приведенном выше призере можно изменить объявление
Plant
на
public sealed class Plant
и в этом случае
Tree
больше не сможет от него наследовать.
C# не имеет модификатора
native
. В Java использование
native
указывает, что метод реализован на зависимом от платформы языке. Это требует чтобы метод был абстрактным, так как реализация должна находиться в другом месте. Ближайшим к этому типу функциональности является модификатор
extern
. Использование
extern
предполагает, что код реализуется вовне (например, некоторой собственной DLL). Однако в отличие от Java, нет необходимости использовать ключевое слово
abstract
в соединении с ним. Фактически это приведет к ошибке, так как они означают две похожие, но различные вещи. Ниже класс
Plant
из предыдущего примера показывает, как можно использовать
extern
:
public class Plant : IfruitHaver {
public extern int See;
public Plant{}
public void Fruit {
Console.WriteLine("Generic plant fruit");
}
}
Это не имеет большого смысла без использования атрибута
DllImport
для определения
внешней реализации. Более подробно атрибуты будут рассмотрены позже в приложении. Дальнейший код делает соответствующие изменения, предполагая, что функция
требует этого от методов, на которых он используется
Пока не существует версии C# ключевых слов
transient
,
volatile
или
synchronized
. Однако существует ряд способов, предоставляемых SDK .NET для имитации некоторой их функциональности. C# использует атрибут
NonSerialized
, связанный с полями класса, для предоставления механизма, аналогичного по функциональности модификатору Java
transient
, этот атрибут однако опротестован, поэтому может быть изменен в будущих версиях.
Синхронизация в C# несколько усложнена (более трудоемка) по сравнению с ее аналогом в Java. В общем, любой поток выполнения может по умолчанию получить доступ ко всем членам объекта. Имеется, однако ряд способов синхронизации кода в зависимости от потребностей разработчика с помощью использования таких средств, как
Monitors
. Они предоставляют возможность делать и освобождать блокировки синхронизации на объектах
SyncBlocks
, которые содержат блокировку используемую для реализации синхронизированных методов и блоков кода; список ожидающих потоков выполнения, используемых для реализации функциональности монитора
ReaderWriterLock
, который определяет образец одного писателя для многочисленных читателей; примитивы синхронизации
Mutex
, предоставляющие межпроцессную синхронизацию; и
System.Threading.Interlocked
, который может использоваться для предоставлении синхронизированного доступа к переменным через несколько потоков выполнения.
Первый шаг к синхронизации в C# состоит в ссылке на сборку
System.EnterpriseServices.dll
. Инструкция
lock(<expression>) {// блок кода}
является единственным, связанным с синхронизацией, ключевым словом в C#. Оно может применяться, также как в Java, для получения взаимно исключающего доступа к блокировке объекта
<ref>
. Все попытки получить доступ к
<expression>
будут блокированы, пока поток выполнения с блокировкой не освободит ее. Обычно используется выражение либо
this
, либо
System.Type
, соответствующее
Type
представленного объекта. Их использование будет защищать переменные экземпляра выражения в то время, как использование
System.Type
будет защищать статические переменные.
Ключевые слова обработки ошибок: catch, finally, throw, throws, try
Эти модификаторы являются одинаковыми в обоих языках, за исключением инструкции
throws
, которая отсутствует в C#. Пугающая вещь в отношении инструкции
throws
из Java состоит в том, что она позволяет потребителям компонента с помощью относительно простого синтаксиса использовать компонент, не зная какие исключения он может порождать. Можно удовлетвориться заверениями, что компилированный код обрабатывает все, имеющие отношение к делу, исключения, так как компилятор будет иначе отказывать и информировать обо всех не перехваченных исключениях. Функциональность такого рода отсутствует в C# в настоящее время. Предоставление метода потребителям сборки, желающем знать, когда порождаются исключения, должно будет привести к хорошей практике документирования или некоторому умелому программированию атрибутов.