Ricerca…


Sintassi

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

Osservazioni

Le classi possono ereditare direttamente da una sola classe, ma (al suo posto o allo stesso tempo) possono implementare una o più interfacce.

Le strutture possono implementare interfacce ma non possono ereditare esplicitamente da alcun tipo. Essi ereditano implicitamente da System.ValueType , che a sua volta eredita direttamente da System.Object .

Le classi statiche non possono implementare interfacce.

Ereditare da una classe base

Per evitare la duplicazione del codice, definire i metodi e gli attributi comuni in una classe generale come 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()
    {
        // ...
    }
}

Ora che hai una classe che rappresenta Animal in generale, puoi definire una classe che descrive le peculiarità di specifici animali:

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

La classe Cat accede non solo ai metodi descritti nella sua definizione in modo esplicito, ma anche a tutti i metodi definiti nella classe generale Animal base. Qualsiasi animale (che fosse o meno un gatto) poteva mangiare, stare in piedi o rotolare. Un animale non sarebbe in grado di grattare, tuttavia, a meno che non fosse anche un gatto. Potresti quindi definire altre classi che descrivono altri animali. (Come Gopher con un metodo per distruggere i giardini fioriti e la bradipo senza alcun metodo aggiuntivo.)

Ereditare da una classe e implementare un'interfaccia

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

Ereditare da una classe e implementare più interfacce

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

Test e navigazione dell'eredità

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

Estendere una classe base astratta

A differenza delle interfacce, che possono essere descritte come contratti per l'implementazione, le classi astratte fungono da contratti per l'estensione.

Una classe astratta non può essere istanziata, deve essere estesa e la classe risultante (o classe derivata) può quindi essere istanziata.

Le classi astratte vengono utilizzate per fornire implementazioni generiche

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'esempio sopra mostra come qualsiasi classe che estende l'auto riceverà automaticamente il metodo HonkHorn con l'implementazione. Ciò significa che qualsiasi sviluppatore che crea una nuova auto non dovrà preoccuparsi di come suonerà il clacson.

Costruttori in una sottoclasse

Quando crei una sottoclasse di una classe base, puoi costruire la classe base usando : base dopo i parametri del costruttore della sottoclasse.

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

Eredità. Sequenza di chiamate dei costruttori

Considera di avere un Animal classe che ha un Dog classe

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

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

Per impostazione predefinita, ogni classe eredita implicitamente la classe Object .

Questo è uguale al codice precedente.

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

Quando si crea un'istanza della classe Dog , il costruttore predefinito delle classi base (senza parametri) verrà chiamato se non vi è alcuna chiamata esplicita a un altro costruttore nella classe genitore . Nel nostro caso, prima si chiamerà Object's constructor, quindi Animal's e alla fine Dog's costruttore Dog's .

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

L'output sarà

Nel costruttore di Animal
Nel costruttore di Dog

Visualizza la demo

Chiama il costruttore genitore in modo esplicito.

Negli esempi precedenti, il nostro costruttore di classi Dog chiama il costruttore predefinito della classe Animal . Se vuoi, puoi specificare quale costruttore dovrebbe essere chiamato: è possibile chiamare qualsiasi costruttore che è definito nella classe genitore.

Considera che abbiamo queste due classi.

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

Cosa sta succedendo qui?

Abbiamo 2 costruttori in ogni classe.

Cosa significa base ?

base è un riferimento alla classe genitore. Nel nostro caso, quando creiamo un'istanza di classe Dog come questa

Dog dog = new Dog();

Il runtime chiama prima Dog() , che è il costruttore senza parametri. Ma il suo corpo non funziona immediatamente. Dopo le parentesi del costruttore abbiamo una tale chiamata: base() , il che significa che quando chiamiamo il costruttore predefinito di Dog , a sua volta chiamerà il costruttore predefinito del genitore. Dopo che il costruttore del genitore è stato eseguito, verrà restituito e, infine, verrà eseguito il corpo del costruttore Dog() .

Quindi l'output sarà così:

Costruttore predefinito di Animal
Costruttore predefinito del cane

Visualizza la demo

E se chiamassimo il costruttore Dog's con un parametro?

Dog dog = new Dog("Rex");

Sapete che i membri della classe genitore che non sono privati ​​sono ereditati dalla classe figlia, nel senso che Dog avrà anche il campo del name .
In questo caso abbiamo passato un argomento al nostro costruttore. A sua volta passa l'argomento al costruttore della classe genitore con un parametro , che inizializza il campo del name .

L'output sarà

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

Sommario:

Ogni creazione di oggetti inizia dalla classe base. Nell'ereditarietà, le classi che si trovano nella gerarchia sono concatenate. Poiché tutte le classi derivano da Object , il primo costruttore da chiamare quando viene creato un oggetto è il costruttore della classe Object ; Quindi viene chiamato il prossimo costruttore della catena e solo dopo che tutti sono chiamati viene creato l'oggetto

parola chiave di base

  1. La parola chiave di base viene utilizzata per accedere ai membri della classe base da una classe derivata:
  2. Chiama un metodo sulla classe base che è stato sovrascritto da un altro metodo. Specificare quale costruttore della classe base deve essere chiamato quando si creano le istanze della classe derivata.

Metodi ereditari

Esistono diversi modi in cui i metodi possono essere ereditati

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

Ereditarietà Anti-pattern

Eredità impropria

Diciamo che ci sono 2 classi di classe Foo e Bar . Foo ha due funzioni Do1 e Do2 . Bar bisogno di usare Do1 da Foo , ma non ha bisogno di Do2 o di funzionalità che equivalgono a Do2 ma fa qualcosa di completamente diverso.

Cattivo : crea Do2() su Foo virtual quindi sostituiscilo in Bar o throw Exception in Bar per 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 
    }
}

Buon modo

Prendi Do1() da Foo e mettilo nella nuova classe Baz poi eredita sia Foo che Bar da Baz e implementa Do2() separatamente

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

Ora, perché il primo esempio è cattivo e il secondo è buono: quando lo sviluppatore nr2 deve fare una modifica in Foo , è probabile che interromperà l'implementazione di Bar perché la Bar è ora inseparabile da Foo . Quando lo si fa con l'ultimo esempio, Foo and Bar commonalty è stato spostato su Baz e non si influenzano a vicenda (come il non dovrebbe).

Classe base con specifica del tipo ricorsivo

Definizione una tantum di una classe base generica con identificatore di tipo ricorsivo. Ogni nodo ha un genitore e più figli.

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

Quanto sopra può essere riutilizzato ogni volta che è necessario definire una gerarchia di alberi. L'oggetto nodo nella struttura deve ereditare dalla classe base con

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

ogni classe nodo sa dove si trova nella gerarchia, quale sia l'oggetto genitore e gli oggetti figli. Diversi tipi incorporati utilizzano una struttura ad albero, come Control o XmlElement e l' Tree<T> sopra può essere usato come una classe base di qualsiasi tipo nel codice.


Ad esempio, per creare una gerarchia di parti in cui viene calcolato il peso totale dal peso di tutti i bambini, effettuare le seguenti operazioni:

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

essere usato come

// [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 altro esempio sarebbe nella definizione dei frame di coordinate relative. In questo caso, la posizione reale del frame di coordinate dipende dalle posizioni di tutti i frame di coordinate padre.

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

essere usato come

// 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow