Что такое нарезка объектов и почему ее нет в C#?
Если у нас есть два класса class A и class B , и B является производным от A , то, когда мы назначаем объект класса B объекту класса A , все дополнительные атрибуты объекта класса B отсекаются для формирования объекта базового класса ( объект класса А ). Проще говоря, когда дополнительные компоненты производного класса нарезаются или не используются, а приоритет отдается объекту базового класса, это называется нарезкой объекта.
Теперь вам может быть интересно, почему мы должны расставлять приоритеты для объектов, когда все хранится в классе, но именно здесь многие программисты ошибаются; значение памяти хранится в объекте класса, а не в самом классе. Гипотетически, если у нас есть только 20 КБ пространства, куда мы должны добавить объекты как класса A , так и класса B , а объект класса A занимает пространство 20 КБ, тогда объект класса B будет автоматически нарезан.
Пример из языка С++:
C++
// C++ program to demonstrate where the// object slicing can occur#include <iostream>using namespace std;// Base classclass A {public: void fun1() { cout << "Function 1" << endl; }};// Derived Classclass B : public A {public: void fun2() { cout << "Function 2" << endl; }};// Slicing of object is taking place in this functionvoid test(A slicedObject) { slicedObject.fun1(); }int main(){ B fullObject; // object of class B test(fullObject); return 0;} |
Выход:
Function 1
Объяснение: В приведенном выше коде мы создали два класса:
- Класс А
- Класс Б
Класс A : содержит одну функцию с именем fun1 , которая выводит на консоль предложение «Функция 1».
Класс B: Содержит одну функцию с именем fun2, которая выводит на консоль предложение «Функция 2» и наследуется от класса A , поэтому может также использовать fun1 .
Теперь у нас есть проверка функции, которая принимает один параметр типа A. Если мы попытаемся передать объект типа B в функцию, она добьется успеха, но за счет того, что называется нарезкой объекта. Теперь, если мы попытаемся выполнить fun2 в функциональном тесте , это вызовет ошибку компиляции, поскольку не может найти функцию в функциях-членах объекта.
Причина: экземпляр дочернего класса может иметь размер в памяти больше или равный размеру родительского класса, потому что дочерний класс содержит все атрибуты и функции родительского класса, а также может иметь дополнительные атрибуты или функции.
Итак, когда мы попытаемся создать экземпляр родительского класса и назначить ему экземпляр дочернего класса, родительскому объекту будет недостаточно памяти для хранения всех дополнительных атрибутов и функций экземпляра дочернего класса.
Таким образом, он отсекает все дополнительные атрибуты и функции от экземпляра дочернего класса, чтобы сформировать экземпляр родительского класса, который имеет только атрибуты и функции, найденные в родительском классе.
Дополнительные сведения см. в статье «Срез объектов в C++».
Тот же пример на С#:
В приведенном выше коде мы создали тот же пример, что и ранее, но на этот раз на C#.
Мы заметим, что мы по-прежнему не можем использовать notSlicedObject.fun2() , так в чем же разница?
Разница в том, что notSlicedObject является ссылкой на объект типа A , поэтому он может использовать только методы или атрибуты, привязанные к этому типу объекта.
Но мы можем преобразовать тип ссылки, чтобы он ссылался на объект типа B вместо типа A , и таким образом мы можем получить доступ к атрибутам и методам, привязанным к типу B.
Примечание. Приведение ссылочного типа не изменило тип самого объекта, вместо этого произошло единственное изменение — ссылка на этот объект. Сам объект не нужно было отрезать, потому что он никогда не менялся.
Мы изменяем только тип ссылки, которая ссылается на этот объект, поэтому, когда мы говорим, что ссылка ссылается на объект типа A , ссылка имеет доступ только к атрибутам и методам, которые имел бы объект типа A , и когда мы говорим ссылка ссылается на объект типа B , тогда ссылка имеет доступ только к атрибутам и методам, которые имел бы объект типа B.
И обратите внимание, что мы можем выполнять этот тип преобразования или приведения только к классам, между которыми существует отношение наследования, как в приведенном выше примере, в противном случае вы должны явно определить это преобразование.
Для получения дополнительной информации об определяемом пользователем преобразовании в C# см.: Определяемые пользователем операторы преобразования
Почему в C# не выполняется нарезка объектов?
Класс в C# является ссылочным типом, что означает, что при создании объекта в управляемой куче выделяется достаточно памяти для этого конкретного объекта, а переменная содержит только ссылку на местоположение указанного объекта.
Пример:
C#
// Declaring an object of type MyClass.MyClass mc = new MyClass(); |
Здесь объект хранится в куче, а ссылка на этот объект хранится в переменной mc .
Это означает, что когда мы пытаемся присвоить экземпляр дочернего класса экземпляру родительского класса, на самом деле происходит то, что мы присваиваем ссылочное значение экземпляра дочернего класса ссылочной переменной типа родительского класса.
А поскольку ссылки хранят только адрес памяти этого объекта в куче, все ссылки имеют одинаковый размер в памяти (обычно 4 байта для 32-битной архитектуры ЦП и 8 байтов для 64-битной), мы можем легко назначать или приводить тип ссылки, не отсекая объект, потому что мы не сохраняем его значение напрямую, а вместо этого используем ссылки.
Таким образом, когда мы имеем дело с объектами класса, мы не обращаемся к ним напрямую, вместо этого мы обращаемся к ним, используя ссылки или указатели на объекты, и именно по этой причине в C# не происходит нарезки объектов, в отличие от других языков программирования, таких как C++, в которых объект может произойти нарезка.