Recherche…


Syntaxe

  • class DerivedClass: BaseClass
  • class DerivedClass: BaseClass, IExampleInterface
  • class DerivedClass: BaseClass, IExampleInterface, IAnotherInterface

Remarques

Les classes peuvent hériter directement d'une seule classe, mais (ou à la fois) peuvent implémenter une ou plusieurs interfaces.

Les structures peuvent implémenter des interfaces mais ne peuvent pas explicitement hériter d'aucun type. Ils héritent implicitement de System.ValueType , qui à son tour hérite directement de System.Object .

Les classes statiques ne peuvent pas implémenter des interfaces.

Hériter d'une classe de base

Pour éviter la duplication de code, définissez des méthodes et des attributs communs dans une classe générale en tant que base:

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()
    {
        // ...
    }
}

Maintenant que vous avez une classe qui représente Animal en général, vous pouvez définir une classe décrivant les particularités de certains animaux:

public class Cat : Animal
{
    public Cat() 
    {
        Name = "Cat";
    }
    // Methods for scratching furniture and ignoring owner
    public void Scratch(Object furniture)
    {
        // ...
    }
}

La classe Cat accède non seulement aux méthodes décrites explicitement dans sa définition, mais aussi à toutes les méthodes définies dans la classe de base Animal . Tout animal (chat ou non) pouvait manger, regarder ou rouler. Un animal ne serait pas en mesure de gratter, à moins que ce ne soit également un chat. Vous pouvez ensuite définir d'autres classes décrivant d'autres animaux. (Comme Gopher avec une méthode pour détruire les jardins de fleurs et la paresse sans aucune méthode supplémentaire.)

Héritage d'une classe et implémentation d'une interface

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";
    }
}

Hériter d'une classe et implémenter plusieurs interfaces

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";
    }
}

Tester et naviguer dans l'héritage

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

Extension d'une classe de base abstraite

Contrairement aux interfaces, qui peuvent être décrites comme des contrats d’implémentation, les classes abstraites servent de contrats d’extension.

Une classe abstraite ne peut pas être instanciée, elle doit être étendue et la classe résultante (ou la classe dérivée) peut être instanciée.

Les classes abstraites sont utilisées pour fournir des implémentations génériques

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.
}

L'exemple ci-dessus montre comment toute classe qui étend sa voiture recevra automatiquement la méthode HonkHorn avec l'implémentation. Cela signifie que tout développeur qui crée une nouvelle voiture n'aura pas à se soucier de la façon dont il va klaxonner.

Constructeurs dans une sous-classe

Lorsque vous créez une sous-classe d'une classe de base, vous pouvez construire la classe de base en utilisant : base après les paramètres du constructeur de la sous-classe.

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;
    }
}

Héritage. Séquence d'appels des constructeurs

Considérons que nous avons un Animal classe qui a un Dog classe enfant

class Animal
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

class Dog : Animal
{
    public Dog()
    {
        Console.WriteLine("In Dog's constructor");
    }
}

Par défaut, chaque classe hérite implicitement de la classe Object .

Ceci est identique au code ci-dessus.

class Animal : Object
{
    public Animal()
    {
        Console.WriteLine("In Animal's constructor");
    }
}

Lors de la création d'une instance de classe Dog , le constructeur par défaut des classes de base (sans paramètres) sera appelé s'il n'y a pas d'appel explicite à un autre constructeur de la classe parent . Dans notre cas, nous appellerons d'abord Object's constructeur Object's , puis Object's constructeur de Animal's et à la fin de Dog's .

public class Program
{
    public static void Main()
    {
        Dog dog = new Dog();
    }
}

La sortie sera

Dans le constructeur d'Animal
Dans le constructeur de Dog

Voir la démo

Appelez explicitement le constructeur du parent.

Dans les exemples ci-dessus, notre constructeur de classe Dog appelle le constructeur par défaut de la classe Animal . Si vous le souhaitez, vous pouvez spécifier quel constructeur doit être appelé: il est possible d'appeler n'importe quel constructeur défini dans la classe parente.

Considérez que nous avons ces deux classes.

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);
    }
}

Qu'est-ce qui se passe ici?

Nous avons 2 constructeurs dans chaque classe.

Que signifie la base ?

base est une référence à la classe parente. Dans notre cas, lorsque nous créons une instance de classe Dog comme celle-ci

Dog dog = new Dog();

Le runtime appelle d'abord le Dog() , qui est le constructeur sans paramètre. Mais son corps ne fonctionne pas immédiatement. Après les parenthèses du constructeur, nous avons un tel appel: base() , ce qui signifie que lorsque nous appelons le constructeur par défaut de Dog , il appellera à son tour le constructeur par défaut du parent. Une fois le constructeur du parent exécuté, il retournera puis exécutera le corps du constructeur Dog() .

Donc, la sortie sera comme ceci:

Constructeur par défaut de l'animal
Constructeur par défaut du chien

Voir la démo

Maintenant, si on appelle le constructeur Dog's avec un paramètre?

Dog dog = new Dog("Rex");

Vous savez que les membres de la classe parent qui ne sont pas privés sont hérités par la classe enfant, ce qui signifie que Dog aura également le champ name .
Dans ce cas, nous avons passé un argument à notre constructeur. Il transmet à son tour l'argument au constructeur de la classe parente avec un paramètre qui initialise le champ de name .

La sortie sera

Animal's constructor with 1 parameter
Rex
Dog's constructor with 1 parameter
Rex

Résumé:

Chaque création d'objet commence à partir de la classe de base. Dans l'héritage, les classes de la hiérarchie sont chaînées. Comme toutes les classes dérivent d' Object , le constructeur de la classe Object est le premier constructeur à être appelé lorsqu'un objet est créé. Ensuite, le constructeur suivant dans la chaîne est appelé et seulement après que tous soient appelés, l'objet est créé

mot clé de base

  1. Le mot-clé base est utilisé pour accéder aux membres de la classe de base à partir d'une classe dérivée:
  2. Appelez une méthode sur la classe de base qui a été remplacée par une autre méthode. Indiquez quel constructeur de classe de base doit être appelé lors de la création d'instances de la classe dérivée.

Méthodes d'héritage

Il existe plusieurs manières d’hériter des méthodes

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
    }
}

Héritage Anti-patrons

Héritage incorrect

Disons qu'il y a 2 classes classe Foo et Bar . Foo a deux fonctionnalités Do1 et Do2 . Bar doit utiliser Do1 de Foo , mais elle ne nécessite pas Do2 ou nécessite une fonctionnalité équivalente à Do2 mais qui fait quelque chose de complètement différent.

Mauvaise manière : rendre Do2() sur Foo virtual, puis le remplacer dans Bar ou simplement throw Exception dans Bar pour 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 
    }
}

Bonne façon

Sortez Do1() de Foo et mettez-le dans la nouvelle classe Baz puis héritez de Foo et Bar de Baz et implémentez Do2() séparément

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
    }
}

Maintenant, pourquoi le premier exemple est mauvais et le second bon: quand le développeur nr2 doit faire un changement dans Foo , il est probable qu’il va casser l’implémentation de Bar parce que Bar est désormais indissociable de Foo . Par exemple, Foo and Bar commonalty a été déplacé dans Baz et ils ne s’affectent pas (comme dans le cas contraire).

Classe de base avec spécification de type récursif

Définition unique d'une classe de base générique avec spécificateur de type récursif. Chaque nœud a un parent et plusieurs enfants.

/// <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; } }
}

Ce qui précède peut être réutilisé chaque fois qu'une hiérarchie d'arbres d'objets doit être définie. L’objet nœud dans l’arbre doit hériter de la classe de base avec

public class MyNode : Tree<MyNode>
{
    // stuff
}

chaque classe de noeud sait où elle se trouve dans la hiérarchie, quel est l'objet parent et quels sont les objets enfants. Plusieurs types intégrés utilisent une structure arborescente, comme Control ou XmlElement et l' Tree<T> ci-dessus Tree<T> peut être utilisé comme classe de base de n'importe quel type dans votre code.


Par exemple, pour créer une hiérarchie d'éléments dont le poids total est calculé à partir du poids de tous les enfants, procédez comme suit:

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); } }
}

à utiliser comme

// [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;

Un autre exemple serait la définition des trames de coordonnées relatives. Dans ce cas, la position réelle du cadre de coordonnées dépend des positions de tous les cadres de coordonnées parents.

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));
    }
}

à utiliser comme

// 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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow