Sök…


Syntax

  • klass DerivedClass: BaseClass
  • klass DerivedClass: BaseClass, IExampleInterface
  • klass DerivedClass: BaseClass, IExampleInterface, IAnotherInterface

Anmärkningar

Klasser kan ärva direkt från endast en klass, men kan (istället eller på samma gång) implementera ett eller flera gränssnitt.

Strukturer kan implementera gränssnitt men kan inte uttryckligen ärva från någon typ. De arv implicit från System.ValueType , som i sin tur ärver direkt från System.Object .

Statiska klasser kan inte implementera gränssnitt.

Arv från en basklass

För att undvika kopiering av kod, definiera vanliga metoder och attribut i en allmän klass som bas:

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

Nu när du har en klass som representerar Animal i allmänhet kan du definiera en klass som beskriver särdragen hos specifika djur:

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

Cat-klassen får tillgång till inte bara de metoder som beskrivs i sin definition uttryckligen, utan också till alla metoder som definieras i den allmänna Animal . Alla djur (oavsett om det var en katt eller inte) kunde äta, stirra eller rulla. Ett djur skulle dock inte kunna repa, såvida det inte också var en katt. Du kan sedan definiera andra klasser som beskriver andra djur. (Som Gopher med en metod för att förstöra blommaträdgårdar och Sloth utan några extra metoder alls.)

Arv från en klass och implementera ett gränssnitt

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

Arv från en klass och implementera flera gränssnitt

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

Testa och navigera arv

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

Utöka en abstrakt basklass

Till skillnad från gränssnitt, som kan beskrivas som kontrakt för implementering, fungerar abstrakta klasser som kontrakt för förlängning.

En abstrakt klass kan inte instansieras, den måste utökas och den resulterande klassen (eller härledd klass) kan sedan instansieras.

Abstrakta klasser används för att ge generiska implementationer

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

Exemplet ovan visar hur någon klass som förlänger bil automatiskt tar emot HonkHorn-metoden med implementeringen. Detta innebär att alla utvecklare som skapar en ny bil inte behöver oroa sig för hur det kommer att hinka sitt horn.

Konstruktörer i en underklass

När du skapar en underklass av en basklass kan du konstruera basklassen genom att använda : base efter underklasskonstruktörens parametrar.

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

Arv. Konstruktörens samtalssekvens

Tänk på att vi har en klass Animal som har en barnklass Dog

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

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

Som standard varje klass ärver implicit Object klassen.

Detta är samma sak som koden ovan.

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

När du skapar en instans av Dog kommer basklassens standardkonstruktör (utan parametrar) att ringas om det inte finns något uttryckligt samtal till en annan konstruktör i överordnadsklassen . I vårt fall kommer först att kallas Object's konstruktör, sedan Animal's och till slut Dog's konstruktör.

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

Output kommer att vara

I Djurens konstruktör
I Hunds konstruktör

Visa demo

Ring förälderns konstruktör uttryckligen.

I ovanstående exempel kallar vår Dog standardkonstruktören för Animal . Om du vill kan du ange vilken konstruktör som ska kallas: det är möjligt att ringa alla konstruktörer som är definierade i förälderklassen.

Tänk på att vi har dessa två klasser.

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

Vad händer här?

Vi har 2 konstruktörer i varje klass.

Vad betyder base ?

base är en referens till förälderklassen. I vårt fall när vi skapar en instans av Dog som denna

Dog dog = new Dog();

Drifttiden ringer först Dog() , som är den parameterlösa konstruktören. Men kroppen fungerar inte direkt. Efter parenteserna konstruktören har vi en sådan samtal: base() , vilket innebär att när vi kallar standard Dog konstruktören, kommer det i sin tur samtalet förälderns standardkonstruktor. När förälderns konstruktör har kört kommer den att återvända och sedan slutligen köra konstruktörskroppen Dog() .

Så output kommer att vara så här:

Djurens standardkonstruktör
Hundens standardkonstruktör

Visa demo

Vad händer nu om vi kallar Dog's konstruktör med en parameter?

Dog dog = new Dog("Rex");

Du vet att medlemmar i föräldra klass som inte är privat ärvs av barnet klassen, vilket innebär att Dog också kommer att ha name fältet.
I det här fallet överförde vi ett argument till vår konstruktör. Det i sin tur passerar argument till moderklassens konstruktor med en parameter, som initierar name fältet.

Output kommer att vara

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

Sammanfattning:

Varje objekt skapas från basklassen. I arvet är kedjorna i hierarkin kedjade. Eftersom alla klasser härrör från Object är den första konstruktören som kallas när något objekt skapas Object konstruktör; Sedan kallas nästa konstruktör i kedjan och först när alla kallas föremål skapas

bas nyckelord

  1. Basnyckelordet används för att komma åt medlemmar i basklassen inom en härledd klass:
  2. Ring en metod i basklassen som har åsidosatts av en annan metod. Ange vilken basklasskonstruktör som ska anropas när man skapar instanser av den härledda klassen.

Arvande metoder

Det finns flera sätt som metoder kan ärva

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

Arv Antimönster

Felaktig arv

Låt oss säga att det finns två klasser Foo och Bar . Foo har två funktioner Do1 och Do2 . Bar måste använda Do1 från Foo , men den behöver inte Do2 eller Do2 som motsvarar Do2 men gör något helt annat.

Dåligt sätt : gör Do2()Foo virtual och åsidosätt den sedan i Bar eller bara throw Exception i Bar för 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 
    }
}

Bra sätt

Ta ut Do1() från Foo och lägg den i ny klass Baz sedan ärva både Foo och Bar från Baz och implementera Do2() separat

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

Nu varför det första exemplet är dåligt och det andra är bra: När utvecklare nr2 måste göra en förändring i Foo , är chansen stor att han kommer att bryta implementeringen av Bar eftersom Bar nu är oskiljbar från Foo . När det görs av det senare exemplet har Foo och Bar commonality flyttats till Baz och de påverkar inte varandra (som inte borde).

Basklass med rekursiv typspecifikation

En engångsdefinition av en generisk basklass med rekursiv typspecifikation. Varje nod har en förälder och flera barn.

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

Ovanstående kan återanvändas varje gång en trädhierarki av objekt behöver definieras. Nodobjektet i trädet måste ärva från basklassen med

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

varje nodklass vet var det är i hierarkin, vad överordnade objektet är och vad barnens objekt är. Flera inbyggda typer använder en trädstruktur, som Control eller XmlElement och ovanstående Tree<T> kan användas som en basklass av alla typer i din kod.


Till exempel, för att skapa en hierarki med delar där den totala vikten beräknas utifrån alla barnens vikt, gör följande:

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

som ska användas som

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

Ett annat exempel skulle i definitionen av relativa koordinatramar. I detta fall beror den verkliga positionen för koordinatramen på positionerna för alla överordnade koordinatramar.

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

som ska användas som

// 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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow