C# Language
наследование
Поиск…
Синтаксис
- класс DerivedClass: BaseClass
- класс DerivedClass: BaseClass, IExampleInterface
- класс DerivedClass: BaseClass, IExampleInterface, IAnotherInterface
замечания
Классы могут наследовать непосредственно только из одного класса, но (вместо этого или в одно и то же время) могут реализовывать один или несколько интерфейсов.
Структуры могут реализовывать интерфейсы, но не могут явно наследовать от любого типа. Они неявно наследуют от System.ValueType
, который, в свою очередь, наследуется непосредственно из System.Object
.
Статические классы не могут реализовывать интерфейсы.
Наследование от базового класса
Чтобы избежать дублирования кода, определите общие методы и атрибуты в общем классе в качестве базы:
public class Animal
{
public string Name { get; set; }
// Methods and attributes common to all animals
public void Eat(Object dinner)
{
// ...
}
public void Stare()
{
// ...
}
public void Roll()
{
// ...
}
}
Теперь, когда у вас есть класс, который представляет Animal
вообще, вы можете определить класс, который описывает особенности конкретных животных:
public class Cat : Animal
{
public Cat()
{
Name = "Cat";
}
// Methods for scratching furniture and ignoring owner
public void Scratch(Object furniture)
{
// ...
}
}
Класс Cat получает доступ не только к методам, описанным в его определении, но также ко всем методам, определенным в общем базовом классе Animal
. Любое животное (независимо от того, было ли это кошкой) могло съесть, посмотреть или бросить. Однако Животное не могло бы царапать, если только это не было кошкой. Затем вы можете определить другие классы, описывающие других животных. (Например, Гофер с методом уничтожения цветников и ленивца без каких-либо дополнительных методов).
Наследование от класса и реализация интерфейса
public class Animal
{
public string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : Animal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string MakeNoise()
{
return "Nyan";
}
}
Наследование от класса и реализация нескольких интерфейсов
public class LivingBeing
{
string Name { get; set; }
}
public interface IAnimal
{
bool HasHair { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
//Note that in C#, the base class name must come before the interface names
public class Cat : LivingBeing, IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
HasHair = true;
}
public bool HasHair { get; set; }
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
Тестирование и навигационное наследование
interface BaseInterface {}
class BaseClass : BaseInterface {}
interface DerivedInterface {}
class DerivedClass : BaseClass, DerivedInterface {}
var baseInterfaceType = typeof(BaseInterface);
var derivedInterfaceType = typeof(DerivedInterface);
var baseType = typeof(BaseClass);
var derivedType = typeof(DerivedClass);
var baseInstance = new BaseClass();
var derivedInstance = new DerivedClass();
Console.WriteLine(derivedInstance is DerivedClass); //True
Console.WriteLine(derivedInstance is DerivedInterface); //True
Console.WriteLine(derivedInstance is BaseClass); //True
Console.WriteLine(derivedInstance is BaseInterface); //True
Console.WriteLine(derivedInstance is object); //True
Console.WriteLine(derivedType.BaseType.Name); //BaseClass
Console.WriteLine(baseType.BaseType.Name); //Object
Console.WriteLine(typeof(object).BaseType); //null
Console.WriteLine(baseType.IsInstanceOfType(derivedInstance)); //True
Console.WriteLine(derivedType.IsInstanceOfType(baseInstance)); //False
Console.WriteLine(
string.Join(",",
derivedType.GetInterfaces().Select(t => t.Name).ToArray()));
//BaseInterface,DerivedInterface
Console.WriteLine(baseInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(derivedType)); //True
Console.WriteLine(derivedInterfaceType.IsAssignableFrom(baseType)); //False
Расширение абстрактного базового класса
В отличие от интерфейсов, которые могут быть описаны как контракты для реализации, абстрактные классы действуют как контракты для расширения.
Абстрактный класс не может быть инстанцирован, он должен быть расширен, и полученный класс (или производный класс) может быть затем создан.
Абстрактные классы используются для предоставления общих реализаций
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
}
public class Mustang : Car
{
// Simply by extending the abstract class Car, the Mustang can HonkHorn()
// If Car were an interface, the HonkHorn method would need to be included
// in every class that implemented it.
}
В приведенном выше примере показано, как любой класс, расширяющий Автомобиль, автоматически получит метод HonkHorn с реализацией. Это означает, что любому разработчику, создающему новый автомобиль, не нужно будет беспокоиться о том, как он будет показывать его.
Конструкторы в подклассе
Когда вы создаете подкласс базового класса, вы можете построить базовый класс, используя : base
после параметров конструктора подкласса.
class Instrument
{
string type;
bool clean;
public Instrument (string type, bool clean)
{
this.type = type;
this.clean = clean;
}
}
class Trumpet : Instrument
{
bool oiled;
public Trumpet(string type, bool clean, bool oiled) : base(type, clean)
{
this.oiled = oiled;
}
}
Наследование. Последовательность вызовов конструкторов
Рассмотрим, что у нас есть класс Animal
которого есть дочерний класс Dog
class Animal
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
class Dog : Animal
{
public Dog()
{
Console.WriteLine("In Dog's constructor");
}
}
По умолчанию каждый класс неявно наследует класс Object
.
Это то же самое, что и приведенный выше код.
class Animal : Object
{
public Animal()
{
Console.WriteLine("In Animal's constructor");
}
}
При создании экземпляра класса Dog
будет вызываться конструктор базовых классов по умолчанию (без параметров), если нет явного вызова другого конструктора в родительском классе . В нашем случае сначала будет называться конструктор Object's
, а затем Animal's
и в конце конструктора Dog's
.
public class Program
{
public static void Main()
{
Dog dog = new Dog();
}
}
Выход будет
В конструкторе Animal
В конструкторе Dog
Вызовите конструктор родителя явно.
В приведенных выше примерах наш конструктор класса Dog
вызывает конструктор по умолчанию класса Animal
. Если вы хотите, вы можете указать, какой конструктор должен вызываться: можно вызвать любой конструктор, определенный в родительском классе.
Рассмотрим эти два класса.
class Animal
{
protected string name;
public Animal()
{
Console.WriteLine("Animal's default constructor");
}
public Animal(string name)
{
this.name = name;
Console.WriteLine("Animal's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
class Dog : Animal
{
public Dog() : base()
{
Console.WriteLine("Dog's default constructor");
}
public Dog(string name) : base(name)
{
Console.WriteLine("Dog's constructor with 1 parameter");
Console.WriteLine(this.name);
}
}
Что здесь происходит?
У нас есть 2 конструктора в каждом классе.
Что означает base
?
base
- ссылка на родительский класс. В нашем случае, когда мы создаем экземпляр класса Dog
подобный этому
Dog dog = new Dog();
Среда выполнения сначала вызывает Dog()
, который является конструктором без параметров. Но его тело не работает сразу. После круглых скобок конструктора мы имеем такой вызов: base()
, что означает, что когда мы вызываем конструктор Dog
по умолчанию, он, в свою очередь, вызывает конструктор по умолчанию родителя. После запуска конструктора родителя он вернется и, наконец, запустит тело конструктора Dog()
.
Таким образом, вывод будет следующим:
Конструктор по умолчанию для Animal
Собственный конструктор по умолчанию
Теперь, если мы будем называть конструктор Dog's
параметром?
Dog dog = new Dog("Rex");
Вы знаете, что члены родительского класса, которые не являются частными, наследуются дочерним классом, а это означает, что у Dog
также будет поле name
.
В этом случае мы передали аргумент нашему конструктору. Он, в свою очередь, передает аргумент конструктору родительского класса с параметром , который инициализирует поле name
.
Выход будет
Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex
Резюме:
Каждое создание объекта начинается с базового класса. В наследовании классы, находящиеся в иерархии, связаны цепями. Поскольку все классы производятся от Object
, первый конструктор, который будет вызываться при создании любого объекта, является конструктором класса Object
; Затем вызывается следующий конструктор в цепочке и только после того, как все они называются, объект создается
ключевое слово base
- Ключевое слово base используется для доступа к членам базового класса из производного класса:
- Вызвать метод в базовом классе, который был переопределен другим методом. Укажите, какой конструктор базового класса следует вызывать при создании экземпляров производного класса.
Методы наследования
Существует несколько способов унаследовать методы
public abstract class Car
{
public void HonkHorn() {
// Implementation of horn being honked
}
// virtual methods CAN be overridden in derived classes
public virtual void ChangeGear() {
// Implementation of gears being changed
}
// abstract methods MUST be overridden in derived classes
public abstract void Accelerate();
}
public class Mustang : Car
{
// Before any code is added to the Mustang class, it already contains
// implementations of HonkHorn and ChangeGear.
// In order to compile, it must be given an implementation of Accelerate,
// this is done using the override keyword
public override void Accelerate() {
// Implementation of Mustang accelerating
}
// If the Mustang changes gears differently to the implementation in Car
// this can be overridden using the same override keyword as above
public override void ChangeGear() {
// Implementation of Mustang changing gears
}
}
Наследование Анти-шаблоны
Неправильное наследование
Допустим, есть 2 класса класса Foo
и Bar
. Foo
имеет две функции: Do1
и Do2
. Bar
должен использовать Do1
из Foo
, но ему не нужна Do2
или функция, эквивалентная Do2
но делает что-то совершенно другое.
Плохой путь : сделайте Do2()
на виртуальном Foo
затем переопределите его в Bar
или просто throw Exception
в Bar
для Do2()
public class Bar : Foo
{
public override void Do2()
{
//Does something completely different that you would expect Foo to do
//or simply throws new Exception
}
}
Хороший способ
Выньте Do1()
из Foo
и поместите его в новый класс Baz
затем наследуйте как Foo
и Bar
от Baz
и реализуйте Do2()
отдельно
public class Baz
{
public void Do1()
{
// magic
}
}
public class Foo : Baz
{
public void Do2()
{
// foo way
}
}
public class Bar : Baz
{
public void Do2()
{
// bar way or not have Do2 at all
}
}
Теперь почему первый пример плох, а второй хорош: когда разработчик nr2 должен внести изменения в Foo
, возможно, он сломает реализацию Bar
потому что Bar
теперь неотделим от Foo
. Когда это делается по последнему примеру, Foo
и Bar
commonalty были перемещены в Baz
и они не влияют друг на друга (как и не должны).
Базовый класс с рекурсивной спецификацией типа
Однократное определение базового базового класса с рекурсивным спецификатором типа. Каждый узел имеет один родительский элемент и несколько дочерних элементов.
/// <summary>
/// Generic base class for a tree structure
/// </summary>
/// <typeparam name="T">The node type of the tree</typeparam>
public abstract class Tree<T> where T : Tree<T>
{
/// <summary>
/// Constructor sets the parent node and adds this node to the parent's child nodes
/// </summary>
/// <param name="parent">The parent node or null if a root</param>
protected Tree(T parent)
{
this.Parent=parent;
this.Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; private set; }
public List<T> Children { get; private set; }
public bool IsRoot { get { return Parent==null; } }
public bool IsLeaf { get { return Children.Count==0; } }
/// <summary>
/// Returns the number of hops to the root object
/// </summary>
public int Level { get { return IsRoot ? 0 : Parent.Level+1; } }
}
Вышеупомянутое может быть повторно использовано каждый раз, когда необходимо определить иерархию дерева объектов. Объект узла в дереве должен наследовать от базового класса с помощью
public class MyNode : Tree<MyNode>
{
// stuff
}
каждый класс узла знает, где он находится в иерархии, каков родительский объект, а также какие объекты-дети. В нескольких встроенных типах используется древовидная структура, например Control
или XmlElement
и вышеупомянутое Tree<T>
может использоваться как базовый класс любого типа в вашем коде.
Например, чтобы создать иерархию частей, где общий вес вычисляется по весу всех детей, выполните следующие действия:
public class Part : Tree<Part>
{
public static readonly Part Empty = new Part(null) { Weight=0 };
public Part(Part parent) : base(parent) { }
public Part Add(float weight)
{
return new Part(this) { Weight=weight };
}
public float Weight { get; set; }
public float TotalWeight { get { return Weight+Children.Sum((part) => part.TotalWeight); } }
}
для использования в качестве
// [Q:2.5] -- [P:4.2] -- [R:0.4]
// \
// - [Z:0.8]
var Q = Part.Empty.Add(2.5f);
var P = Q.Add(4.2f);
var R = P.Add(0.4f);
var Z = Q.Add(0.9f);
// 2.5+(4.2+0.4)+0.9 = 8.0
float weight = Q.TotalWeight;
Другим примером может служить определение относительных координат. В этом случае истинное положение кадра координат зависит от положения всех исходных координат.
public class RelativeCoordinate : Tree<RelativeCoordinate>
{
public static readonly RelativeCoordinate Start = new RelativeCoordinate(null, PointF.Empty) { };
public RelativeCoordinate(RelativeCoordinate parent, PointF local_position)
: base(parent)
{
this.LocalPosition=local_position;
}
public PointF LocalPosition { get; set; }
public PointF GlobalPosition
{
get
{
if(IsRoot) return LocalPosition;
var parent_pos = Parent.GlobalPosition;
return new PointF(parent_pos.X+LocalPosition.X, parent_pos.Y+LocalPosition.Y);
}
}
public float TotalDistance
{
get
{
float dist = (float)Math.Sqrt(LocalPosition.X*LocalPosition.X+LocalPosition.Y*LocalPosition.Y);
return IsRoot ? dist : Parent.TotalDistance+dist;
}
}
public RelativeCoordinate Add(PointF local_position)
{
return new RelativeCoordinate(this, local_position);
}
public RelativeCoordinate Add(float x, float y)
{
return Add(new PointF(x, y));
}
}
для использования в качестве
// Define the following coordinate system hierarchy
//
// o--> [A1] --+--> [B1] -----> [C1]
// |
// +--> [B2] --+--> [C2]
// |
// +--> [C3]
var A1 = RelativeCoordinate.Start;
var B1 = A1.Add(100, 20);
var B2 = A1.Add(160, 10);
var C1 = B1.Add(120, -40);
var C2 = B2.Add(80, -20);
var C3 = B2.Add(60, -30);
double dist1 = C1.TotalDistance;