Эта краткая заметка посвящена наследованию в C# и его практичесткому применению в проектировании приложений. За основу нашей программы мы возьмем два класса продукта: продукт на складе и проданный продукт. Базовый RProduct:
- [Serializable]
- class RProduct
- {
- protected int cost;
- public RProduct(string n, int c)
- {
- Name = n;
- cost = c;
- }
- public string Name { get; set; }
- public int Cost
- {
- get
- {
- return cost ;
- }
- }
- }
и его наследника:
- [Serializable]
- class Product : RProduct
- {
- public Product(string n, int c, float d = 1) : base(n, c)
- {
- Disc = d;
- }
- float Disc { get; set; }
- public int Cost
- {
- get
- {
- return Convert.ToInt32(cost * Disc);
- }
- }
- }
Бывает, нужно сделать, чтобы у двух классов было разное поведение, но одинаковые поля. По хорошему надо сделать для них общий абстрактный класс либо интерфейс, но… как его сериализовать? В C# есть классная вещь, как XmlIncludeAttribute, с помощью которого наверное можно было бы решить проблему. Если бы не одно «но»: XmlIncludeAttribute существует для xml-сериализации, а надо бинарную. Да и сериализацию надо делать не просто объекта этого класса, а списка объектов… Недолго думая, оставляем паттерн с двумя классами:
- class RProductList : List<RProduct> { }
- [Serializable]
- class ProductList : List<Product>
- {
- public static implicit operator RProductList(ProductList x)
- {
- var psl = new RProductList();
- psl.AddRange(x);
- return psl;
- }
- }
где первый класс — это список базовых классов продукта, а второй — наследников. Ок
Пришло время проверить на прочность нашу конструкцию. Обратите внимание на перегруженный оператор неявного преобразования типов для RProductList
.
Благодаря технологии наследования мы без проблем преобразуем дочерний тип в родительский без всякой перегрузки операторов:
- var p = new Product(«Утка», 5, 0.6f);
- RProduct rp = p;
Можете проверить в отладчике значение свойства Cost
, если сомневаетесь
Теперь попробуем списки:
- List<RProduct> rps = new List<RProduct>() { rp };
- rps.Add(p);
Дочерний тип без проблем добавился в список родительских. А вот наоборот уже нельзя: получим null или вообще не получим. Кстати, если дочерний класс приведен к родительскому, то вернуть обратно сложностей вызвать не должно:
- List<RProduct> rps = new List<RProduct>() { rp };
- rps.Add(p);
- List<Product> ps = rps.Select(r => (Product)r).ToList();
Поехали дальше. Создадим экземпляр для сериализации:
ProductList psl = new ProductList() { p, new Product("Утка охлажденная", 5, 0.6f) };
и поехали:
- using (var fs = new FileStream(«here.db», FileMode.OpenOrCreate))
- {
- new BinaryFormatter().Serialize(fs, psl);
- Console.WriteLine(«here1»);
- }
- using (var fs = new FileStream(«here.db», FileMode.Open))
- {
- var _psl = (ProductList)new BinaryFormatter().Deserialize(fs);
- RProductList rpsl = _psl;
- Console.Read();
- }
Что мы получаем? Мы сериализовали список экземпляров дочерних классов Product. Затем считали его и благодаря перегрузке оператора неявного преобразования привели к родительскому: _psl
имеет одно поведение (Cost = 3), rpsl
— другое (Cost = 5) — что и требовалось доказать 🙂
Это называется полиморфизм. До новых встреч!
от