Поиск…


Синтаксис

  • класс 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

  1. Ключевое слово base используется для доступа к членам базового класса из производного класса:
  2. Вызвать метод в базовом классе, который был переопределен другим методом. Укажите, какой конструктор базового класса следует вызывать при создании экземпляров производного класса.

Методы наследования

Существует несколько способов унаследовать методы

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;


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow