Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Наконец, имейте в виду, что с точки зрения пользователя объекта защищенные данные расцениваются как закрытые (поскольку пользователь находится "снаружи" семейства). По указанной причине следующий код недопустим:
// Ошибка! Доступ к защищенным данным из клиентского кода невозможен!
Employee emp = new Employee();
emp.empName = "Fred";
На заметку! Несмотря на то что защищенные поля данных могут нарушить инкапсуляцию, определять защищенные методы вполне безопасно (и полезно). При построении иерархий классов обычно приходится определять набор методов, которые предназначены для применения только производными типами, но не внешним миром.
Добавление запечатанного класса
Вспомните, что запечатанный класс не может быть расширен другими классами. Как уже упоминалось, такой прием чаще всего используется при проектировании обслуживающих классов. Тем не менее, при построении иерархий классов вы можете обнаружить, что определенная ветвь в цепочке наследования нуждается в "отсечении", т.к. дальнейшее ее расширение не имеет смысла. В качестве примера предположим, что вы добавили в приложение еще один класс (PtSalesPerson), который расширяет существующий тип SalesPerson. Текущее обновление показано на рис. 6.3.
Класс PtSalesPerson представляет продавца, который работает на условиях частичной занятости. В качестве варианта скажем, что нужно гарантировать отсутствие возможности создания подкласса PtSalesPerson. Чтобы предотвратить наследование от класса, необходимо применить ключевое слово sealed:
sealed class PtSalesPerson : SalesPerson
{
public PtSalesPerson(string fullName, int age, int empId,
float currPay, string ssn, int numbOfSales)
: base(fullName, age, empId, currPay, ssn, numbOfSales)
{
}
// Остальные члены класса...
}
Наследование с типами записей (нововведение в версии 9.0)
Появившиеся в версии C# 9.0 типы записей также поддерживают наследование. Чтобы выяснить как, отложите пока свою работу над проектом Employees и создайте новый проект консольного приложения по имени RecordInheritance. Добавьте в него два файла с именами Car.cs и MiniVan.cs, содержащими следующие определения записей:
// Car.cs
namespace RecordInheritance
{
//Car record type
public record Car
{
public string Make { get; init; }
public string Model { get; init; }
public string Color { get; init; }
public Car(string make, string model, string color)
{
Make = make;
Model = model;
Color = color;
}
}
}
// MiniVan.cs
namespace RecordInheritance
{
//MiniVan record type
public sealed record MiniVan : Car
{
public int Seating { get; init; }
public MiniVan(string make, string model, string color, int seating)
: base(make, model, color)
{
Seating = seating;
}
}
}
Обратите внимание, что между примерами использования типов записей и предшествующими примерами применения классов нет большой разницы. Модификатор доступа protected для свойств и методов ведет себя аналогично, а модификатор доступа sealed для типа записи запрещает другим типам записей быть производными от запечатанных типов записей. Вы также обнаружите работу с унаследованными типами записей в оставшихся разделах главы. Причина в том, что типы записей — это всего лишь особый вид неизменяемого класса (как объяснялось в главе 5). Вдобавок типы записей включают неявные приведения к своим базовым классам, что демонстрируется в коде ниже:
using System;
using RecordInheritance;
Console.WriteLine("Record type inheritance!");
Car c = new Car("Honda","Pilot","Blue");
MiniVan m = new MiniVan("Honda", "Pilot", "Blue",10);
Console.WriteLine($"Checking MiniVan is-a Car:{m is Car}");
// Проверка, является ли MiniVan типом Car
Как и можно было ожидать, проверка того, что m является Car, возвращает true, как видно в следующем выводе:
Record type inheritance!
Checking minvan is-a car:True
Важно отметить, что хотя типы записей представляют собой специализированные классы, вы не можете организовывать перекрестное наследование между классами и записями. Другими словами, классы нельзя наследовать от типов записей, а типы записей не допускается наследовать от классов. Взгляните на приведенный далее код; последние два примера не скомпилируются:
namespace RecordInheritance
{
public class TestClass { }
public record TestRecord { }
// Классы не могут быть унаследованы от записей
// public class Test2 : TestRecord { }
// Записи не могут быть унаследованы от классов
// public record Test2 : TestClass { }
}
Наследование также работает с позиционными типами записей. Создайте в своем проекте новый файл по имени PositionalRecordTypes.cs и поместите в него следующий код:
namespace RecordInheritance
{
public record PositionalCar (string Make, string Model, string Color);
public record PositionalMiniVan (string Make, string Model, string Color)
: PositionalCar(Make, Model, Color);
}
Добавьте к операторам верхнего уровня показанный ниже код, с помощью которого можно подтвердить то, что вам уже известно: позиционные типы записей работают точно так же, как типы записей.
PositionalCar pc = new PositionalCar("Honda", "Pilot", "Blue");
PositionalMiniVan pm = new PositionalMiniVan("Honda", "Pilot", "Blue", 10);
Console.WriteLine($"Checking PositionalMiniVan is-a PositionalCar:
{pm is PositionalCar}");
Эквивалентность с унаследованными типами записей
Вспомните из главы 5, что для определения эквивалентности типы записей используют семантику значений. Еще одна деталь относительно типов записей связана с тем, что тип записи является частью соображения, касающегося эквивалентности. Скажем, взгляните на следующие тривиальные примеры:
public record MotorCycle(string Make, string Model);
public record Scooter(string Make, string Model) : MotorCycle(Make,Model);
Игнорируя тот факт, что унаследованные классы обычно расширяют базовые классы, в приведенных простых примерах определяются два разных типа записей, которые имеют те же самые свойства. В случае создания экземпляров с одинаковыми значениями для свойств они не пройдут проверку на предмет эквивалентности из-за того, что принадлежат разным типам. В качестве примера рассмотрим показанный далее код и результаты его выполнения:
MotorCycle mc = new MotorCycle("Harley","Lowrider");
Scooter sc = new