Megosztás a következőn keresztül:


Öröklés C# és .NET környezetben

Ez az oktatóanyag bemutatja a C#-beli öröklést. Az öröklés az objektumorientált programozási nyelvek egyik funkciója, amely lehetővé teszi egy alaposztály definiálását, amely adott funkciókat (adatokat és viselkedést) biztosít, valamint olyan származtatott osztályok definiálását, amelyek öröklik vagy felülbírálják ezt a funkciót.

Előfeltételek

Telepítési utasítások

Windows rendszeren ez a WinGet konfigurációs fájl az összes előfeltétel telepítéséhez. Ha már telepített valamit, a WinGet kihagyja ezt a lépést.

  1. Töltse le a fájlt, és kattintson rá duplán a futtatáshoz.
  2. Olvassa el a licencszerződést, írja be y, és válassza Adja meg, amikor a rendszer kéri az elfogadást.
  3. Ha a tálcán megjelenik egy villogó Felhasználói fiókok felügyelete (UAC) kérés, engedélyezze a telepítés folytatását.

Más platformokon külön kell telepítenie ezeket az összetevőket.

  1. Töltse le az ajánlott telepítőt a .NET SDK letöltési oldaláról, majd kattintson rá duplán a futtatásához. A letöltési oldal észleli a platformot, és a legújabb telepítőt javasolja a platformhoz.
  2. Töltse le a legújabb telepítőt a Visual Studio Code kezdőlapjáról, és kattintson rá duplán a futtatásához. Ez az oldal a platformot is észleli, és a hivatkozásnak helyesnek kell lennie a rendszer számára.
  3. Kattintson a "Telepítés" gombra a C# DevKit bővítménylapon. Ez megnyitja a Visual Studio-kódot, és megkérdezi, hogy szeretné-e telepíteni vagy engedélyezni a bővítményt. Válassza a "telepítés" lehetőséget.

A példák futtatása

Az oktatóanyagban szereplő példák létrehozásához és futtatásához használja a parancssorból a dotnet segédprogramot. Kövesse az alábbi lépéseket az egyes példákhoz:

  1. Hozzon létre egy könyvtárat a példa tárolásához.

  2. Új .NET Core-projekt létrehozásához írja be a dotnet új konzol parancsot egy parancssorba.

  3. Másolja és illessze be a kódot a példából a kódszerkesztőbe.

  4. Adja meg a dotnet restore parancsot a parancssorból a projekt függőségeinek betöltéséhez vagy visszaállításához.

    Nem kell futtatnia dotnet restore, mert az összes olyan parancs implicit módon fut, amely visszaállítást igényel, például dotnet new, dotnet build, dotnet run, dotnet test, dotnet publishés dotnet pack. Az implicit visszaállítás letiltásához használja a --no-restore lehetőséget.

    A dotnet restore parancs továbbra is hasznos bizonyos esetekben, amikor a explicit visszaállításnak van értelme, például folyamatos integrációs buildeket az Azure DevOps Services vagy olyan buildrendszerekben, amelyeknek explicit módon kell szabályozni a visszaállítást.

    További információ a NuGet-hírcsatornák kezeléséről: dotnet restore dokumentáció.

  5. Adja meg a dotnet run parancsot a példa fordításához és végrehajtásához.

Háttér: Mi az öröklés?

Az öröklés az objektumorientált programozás egyik alapvető attribútuma. Lehetővé teszi egy gyermekosztály definiálását, amely újra felhasználja (örökli), kibővíti vagy módosítja a szülőosztály viselkedését. Az az osztály, amelynek tagjait öröklik, neve alaposztály. Az alaposztály tagjait öröklő osztályt származtatott osztálynaknevezzük.

A C# és a .NET csak támogatja egyetlen öröklést. Vagyis egy osztály csak egyetlen osztálytól örökölhet. Az öröklés azonban tranzitív, amely lehetővé teszi egy öröklési hierarchia definiálását egy halmazhoz. Más szóval, a D típus örökölhet a Ctípustól, amely a Btípustól örököl, amely az alaposztályból, a Atípusból származik. Mivel az öröklés tranzitív, a A típus tagjai elérhetők a Dtípus számára.

A származtatott osztályok nem öröklik az alaposztály minden tagját. A következő tagok nem öröklődnek:

  • statikus konstruktorok, amelyek inicializálják az osztály statikus adatait.

  • példánykonstruktorok, amelyeket az osztály egy új példányának létrehozására hív meg. Minden osztálynak saját konstruktorokat kell meghatároznia.

  • A véglegesítők (Finalizers), amelyeket a futtatókörnyezet szemétgyűjtője hív meg, hogy elpusztítsa egy osztály példányait.

Bár az alaposztály minden más tagját öröklik a származtatott osztályok, azok láthatósága attól függ, hogy milyen elérhetőséggel rendelkeznek. A tagok akadálymentessége az alábbiak szerint befolyásolja a származtatott osztályok láthatóságát:

  • Privát tagok csak a bázisosztályukba beágyazott származtatott osztályokban láthatók. Ellenkező esetben nem láthatók származtatott osztályokban. Az alábbi példában a A.B egy beágyazott osztály, amely az A-ből származik, míg a C az A-ból származik. A privát A._value mező látható az A.B-ben. Ha azonban eltávolítja a megjegyzéseket a C.GetValue metódusból, és megpróbálja lefordítani a példát, az cs0122-es fordítói hibát okoz: a "A._value" a védelmi szintje miatt nem érhető el."

    public class A
    {
        private int _value = 10;
    
        public class B : A
        {
            public int GetValue()
            {
                return _value;
            }
        }
    }
    
    public class C : A
    {
        //    public int GetValue()
        //    {
        //        return _value;
        //    }
    }
    
    public class AccessExample
    {
        public static void Main(string[] args)
        {
            var b = new A.B();
            Console.WriteLine(b.GetValue());
        }
    }
    // The example displays the following output:
    //       10
    
  • Védett tagok csak származtatott osztályokban láthatók.

  • belső tagok csak olyan származtatott osztályokban láthatók, amelyek ugyanabban a könyvtárban találhatók, mint az alaposztály. Nem láthatók az alaposztálytól eltérő szerelvényben található származtatott osztályokban.

  • nyilvános tagok származtatott osztályokban láthatók, és a származtatott osztály nyilvános felületének részét képezik. A nyilvánosan öröklődő tagok ugyanúgy hívhatók meg, mintha a származtatott osztályban lennének definiálva. Az alábbi példában a A osztály egy Method1nevű metódust határoz meg, az osztály B pedig az Aosztálytól örökli. A példa ezután úgy hívja meg Method1-t, mintha az egy példánymetódus lenne a B-en.

    public class A
    {
        public void Method1()
        {
            // Method implementation.
        }
    }
    
    public class B : A
    { }
    
    public class Example
    {
        public static void Main()
        {
            B b = new ();
            b.Method1();
        }
    }
    

Az örökölt tagokat a származtatott osztályok alternatív megvalósítással is felülbírálhatják. Ahhoz, hogy felül lehessen bírálni egy tagot, az alaposztály tagját meg kell jelölni a virtuális kulcsszóval. Alapértelmezés szerint az alaposztálytagok nincsenek megjelölve virtual ként, és nem bírálhatók felül. Ha nem virtuális tag felülbírálási kísérlete történik, az alábbi példa szerint fordítói hibát okoz: "<tag> nem bírálhatja felül az örökölt <tagot>, mert nincs megjelölve sem virtuálisnak, sem absztraktnak, sem felülbírálónak."

public class A
{
    public void Method1()
    {
        // Do something.
    }
}

public class B : A
{
    public override void Method1() // Generates CS0506.
    {
        // Do something else.
    }
}

Bizonyos esetekben egy származtatott osztály felül kell bírálnia az alaposztály implementálását. Az absztrakt kulcsszóval megjelölt alaposztálytagok megkövetelik, hogy a származtatott osztályok felülbírálják őket. A következő példa fordítására tett kísérlet cs0534-ös fordítói hibát eredményez, "<osztály> nem implementálja az örökölt absztrakt tagot <tag>", mert a B osztály nem biztosít implementációt A.Method1számára.

public abstract class A
{
    public abstract void Method1();
}

public class B : A // Generates CS0534.
{
    public void Method3()
    {
        // Do something.
    }
}

Az öröklés csak osztályokra és felületekre vonatkozik. Más típuskategóriák (szerkezetek, meghatalmazottak és enumerálások) nem támogatják az öröklést. A szabályok miatt az alábbi példához hasonló kódfordítási kísérlet cs0527-es fordítói hibát eredményez: "A "ValueType" típus a felületlistában nem interfész." A hibaüzenet azt jelzi, hogy bár megadhatja a szerkezet által implementált interfészeket, az öröklés nem támogatott.

public struct ValueStructure : ValueType // Generates CS0527.
{
}

Implicit öröklés

A .NET-típusrendszer minden olyan típusa mellett, amelytől örökölhetnek egyetlen örökléssel, a .NET-típusrendszer minden típusa implicit módon örököl Object vagy az abból származtatott típusból. A Object általános funkciója bármilyen típus számára elérhető.

Az implicit öröklés fogalmának megtekintéséhez definiáljunk egy új osztályt, SimpleClass, amely egyszerűen egy üres osztálydefiníció:

public class SimpleClass
{ }

Ezután a tükrözés használatával (amely lehetővé teszi, hogy ellenőrizze egy típus metaadatait, hogy információt szerezzen róla) lekérheti a SimpleClass típushoz tartozó tagok listáját. Bár nem definiált tagokat a SimpleClass osztályban, a példában szereplő kimenet azt jelzi, hogy valójában kilenc taggal rendelkezik. Az egyik ilyen tag egy paraméter nélküli (vagy alapértelmezett) konstruktor, amelyet a C#-fordító automatikusan biztosít a SimpleClass típushoz. A fennmaradó nyolc a Objecttagja, amelyből a .NET-típusrendszer összes osztálya és felülete implicit módon örököl.

using System.Reflection;

public class SimpleClassExample
{
    public static void Main()
    {
        Type t = typeof(SimpleClass);
        BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
                             BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;
        MemberInfo[] members = t.GetMembers(flags);
        Console.WriteLine($"Type {t.Name} has {members.Length} members: ");
        foreach (MemberInfo member in members)
        {
            string access = "";
            string stat = "";
            var method = member as MethodBase;
            if (method != null)
            {
                if (method.IsPublic)
                    access = " Public";
                else if (method.IsPrivate)
                    access = " Private";
                else if (method.IsFamily)
                    access = " Protected";
                else if (method.IsAssembly)
                    access = " Internal";
                else if (method.IsFamilyOrAssembly)
                    access = " Protected Internal ";
                if (method.IsStatic)
                    stat = " Static";
            }
            string output = $"{member.Name} ({member.MemberType}): {access}{stat}, Declared by {member.DeclaringType}";
            Console.WriteLine(output);
        }
    }
}
// The example displays the following output:
//	Type SimpleClass has 9 members:
//	ToString (Method):  Public, Declared by System.Object
//	Equals (Method):  Public, Declared by System.Object
//	Equals (Method):  Public Static, Declared by System.Object
//	ReferenceEquals (Method):  Public Static, Declared by System.Object
//	GetHashCode (Method):  Public, Declared by System.Object
//	GetType (Method):  Public, Declared by System.Object
//	Finalize (Method):  Internal, Declared by System.Object
//	MemberwiseClone (Method):  Internal, Declared by System.Object
//	.ctor (Constructor):  Public, Declared by SimpleClass

Az Object osztály implicit öröklése az alábbi módszereket teszi elérhetővé a SimpleClass osztály számára:

  • A nyilvános ToString metódus, amely egy SimpleClass objektumot sztringre konvertál, a teljes típusnevet adja vissza. Ebben az esetben a ToString metódus a "SimpleClass" sztringet adja vissza.

  • Három módszer, amely két objektum egyenlőségét teszteli: a nyilvános példány Equals(Object) metódust, a nyilvános statikus Equals(Object, Object) metódust és a nyilvános statikus ReferenceEquals(Object, Object) metódust. Alapértelmezés szerint ezek a módszerek a referenciaegyenlőséget tesztelik; vagyis, hogy egyenlő legyen, két objektumváltozónak ugyanarra az objektumra kell hivatkoznia.

  • A nyilvános GetHashCode metódus, amely egy olyan értéket számít ki, amely lehetővé teszi egy ilyen típusú példány használatát kivonatolt gyűjteményekben.

  • A nyilvános GetType metódus, amely egy Type objektumot ad vissza, amely a SimpleClass típust jelöli.

  • A védett Finalize metódus, amely úgy lett kialakítva, hogy nem felügyelt erőforrásokat szabadít fel, mielőtt a szemétgyűjtő visszanyerné az objektum memóriáját.

  • A védett MemberwiseClone metódus, amely az aktuális objektum sekély klónját hozza létre.

Implicit öröklés miatt bármely örökölt tagot meghívhat egy SimpleClass objektumból, mintha az ténylegesen a SimpleClass osztályban definiált tag lenne. Az alábbi példa a SimpleClass.ToString metódust hívja meg, amit SimpleClass örököl Object-től.

public class EmptyClass
{ }

public class ClassNameExample
{
    public static void Main()
    {
        EmptyClass sc = new();
        Console.WriteLine(sc.ToString());
    }
}
// The example displays the following output:
//        EmptyClass

Az alábbi táblázat felsorolja a C#-ban létrehozható típusok kategóriáit és azokat a típusokat, amelyekből implicit módon örökölnek. Minden alaptípus egy másik tagkészletet tesz elérhetővé az öröklésen keresztül az implicit módon származtatott típusok számára.

Kategóriatípus Implicit módon örökli a
osztály Object
struktúra ValueType, Object
enumerálás \, \, \
képviselő \, \, \

Öröklés és "egy" kapcsolat

Az öröklés általában egy alaposztály és egy vagy több származtatott osztály közötti "egy" kapcsolat kifejezésére szolgál, ahol a származtatott osztályok az alaposztály speciális verziói; a származtatott osztály az alaposztály típusa. A Publication osztály például bármilyen kiadványt, a Book és a Magazine osztály pedig bizonyos kiadványtípusokat jelöl.

Megjegyzés

Egy osztály vagy struktúra egy vagy több interfészt implementálhat. Bár az interfész implementálását gyakran kerülő megoldásként mutatják be az önálló örökléshez, vagy az öröklés szerkezetekkel való használatának módjaként, az interfész és annak implementálási típusa között más kapcsolatot ("can do" kapcsolatot) kíván kifejezni, mint az öröklés. Az interfész a funkciók egy részhalmazát határozza meg (például az egyenlőség tesztelésének, az objektumok összehasonlításának vagy rendezésének, illetve a kultúraérzékeny elemzések és formázás támogatásának képességét), amelyet az interfész elérhetővé tesz a implementálási típusai számára.

Vegye figyelembe, hogy az "is a" kifejezés egy típus és egy adott példány közötti kapcsolatot is kifejezi. A következő példában Automobile egy olyan osztály, amely három egyedi csak olvasható tulajdonsággal rendelkezik: Make: az autó gyártója; Model: az autó típusa; és Year: az év, amikor gyártották. A Automobile osztályhoz egy konstruktor is tartozik, amelynek argumentumai hozzá vannak rendelve a tulajdonságértékekhez, és felülbírálja a Object.ToString metódust egy sztring létrehozásához, amely a Automobile osztály helyett egyedileg azonosítja a Automobile-példányt.

public class Automobile
{
    public Automobile(string make, string model, int year)
    {
        if (make == null)
            throw new ArgumentNullException(nameof(make), "The make cannot be null.");
        else if (string.IsNullOrWhiteSpace(make))
            throw new ArgumentException("make cannot be an empty string or have space characters only.");
        Make = make;

        if (model == null)
            throw new ArgumentNullException(nameof(model), "The model cannot be null.");
        else if (string.IsNullOrWhiteSpace(model))
            throw new ArgumentException("model cannot be an empty string or have space characters only.");
        Model = model;

        if (year < 1857 || year > DateTime.Now.Year + 2)
            throw new ArgumentException("The year is out of range.");
        Year = year;
    }

    public string Make { get; }

    public string Model { get; }

    public int Year { get; }

    public override string ToString() => $"{Year} {Make} {Model}";
}

Ebben az esetben nem szabad öröklésre támaszkodnia, hogy konkrét autómárkákat és modelleket képviseljen. Például nem kell definiálnia egy Packard típust, amely a Packard Motor Car Company által gyártott autókat jelöli. Ehelyett egy Automobile objektum létrehozásával jelölheti őket az osztálykonstruktornak átadott megfelelő értékekkel, ahogyan az alábbi példa is szemlélteti.

using System;

public class Example
{
    public static void Main()
    {
        var packard = new Automobile("Packard", "Custom Eight", 1948);
        Console.WriteLine(packard);
    }
}
// The example displays the following output:
//        1948 Packard Custom Eight

Az öröklésen alapuló kapcsolatok az alaposztályra és az olyan származtatott osztályokra alkalmazhatók a legjobban, amelyek további tagokat adnak hozzá az alaposztályhoz, vagy amelyek az alaposztályban nem található további funkciókat igényelnek.

Az alaposztály és a származtatott osztályok tervezése

Tekintsük át az alaposztály és annak származtatott osztályai tervezésének folyamatát. Ebben a szakaszban meghatározunk egy alaposztályt, Publication, amely bármilyen kiadványt jelöl, például könyvet, magazint, újságot, folyóiratot, cikket stb. Egy Book osztályt is meghatároz, amely a Publicationszármazik. A példát egyszerűen kiterjesztheti más származtatott osztályok definiálására, például Magazine, Journal, Newspaperés Article.

Az alap kiadványosztály

A Publication osztály tervezésekor több tervezési döntést kell meghoznia:

  • Az alapszintű Publication osztályba felvenni kívánt tagok, és hogy a Publication tagok nyújtanak-e metódus-implementációkat, vagy Publication egy absztrakt alaposztály, amely sablonként szolgál a származtatott osztályokhoz.

    Ebben az esetben a Publication osztály metódus-implementációkat biztosít. Az Absztrakt alaposztályok és azok származtatott osztályai szakasz egy olyan példát tartalmaz, amely absztrakt alaposztály használatával határozza meg a származtatott osztályok által felülbírálandó metódusokat. A származtatott osztályok szabadon biztosítják a származtatott típusnak megfelelő implementációkat.

    A kód újrafelhasználásának lehetősége (azaz több származtatott osztály osztozik az alaposztály-metódusok deklarálásában és implementálásában, és nem kell felülbírálni őket) a nem absztrakt alaposztályok előnye. Ezért fel kell vennie a tagokat a Publication-ba, ha annak a kódját valószínűleg megosztja néhány vagy a legtöbb specializált Publication típus. Ha nem sikerül hatékonyan megadnia az alaposztály-implementációkat, a végén a származtatott osztályokban nagyjából azonos tag-implementációkat kell biztosítania, és nem az alaposztály egyetlen implementációját. A hibák lehetséges forrása, hogy több helyen is fenn kell tartani a duplikált kódot.

    A kód újrafelhasználásának maximalizálása és a logikai és intuitív öröklési hierarchia létrehozása érdekében győződjön meg arról, hogy a Publication osztályba csak azokat az adatokat és funkciókat foglalja bele, amelyek az összes vagy a legtöbb kiadványban gyakoriak. A származtatott osztályok ezután olyan tagokat implementálnak, amelyek egyediek az általuk képviselt kiadványtípusokra vonatkozóan.

  • Milyen messze bővítheti az osztályhierarchiát. Három vagy több osztályból álló hierarchiát szeretne létrehozni, nem csupán egy alaposztályt és egy vagy több származtatott osztályt? A Publication lehet például a Periodicalalaposztálya , amely viszont a Magazine, Journal és Newspaperalaposztálya .

    A példában egy Publication osztály és egyetlen származtatott osztály kis hierarchiáját fogja használni, Book. A példát egyszerűen kiterjesztheti, hogy több további osztályt hozzon létre, amelyek Publicationszármaznak , például Magazine és Article.

  • Van-e értelme az alaposztály példányosításának. Ha nem, akkor az absztrakt kulcsszót kell alkalmaznia az osztályra. Ellenkező esetben a Publication osztály példányosítható az osztálykonstruktor meghívásával. Ha az osztálykonstruktor közvetlen hívása a abstract kulcsszóval jelölt osztály példányosítására tesz kísérletet, a C#-fordító a CS0144 hibát generálja: "Az absztrakt osztály vagy interfész példánya nem hozható létre." Ha az osztályt reflexióval próbálják példányosítani, a reflexiós módszer MemberAccessException.

    Alapértelmezés szerint az alaposztály példányosítható az osztálykonstruktor meghívásával. Nem kell explicit módon definiálnia egy osztálykonstruktort. Ha nem szerepel az alaposztály forráskódjában, a C#-fordító automatikusan biztosít egy alapértelmezett (paraméter nélküli) konstruktort.

    A példádban jelöld meg a Publication osztályt absztraktnak, hogy ne lehessen példányosítani. A abstract metódusok nélküli abstract osztály azt jelzi, hogy ez az osztály olyan absztrakt fogalmat jelöl, amely több konkrét osztály (például egy Book, Journal) között oszlik meg.

  • Azt, hogy a származtatott osztályoknak örökölniük kell-e az adott tagok alaposztály-implementációját, lehetőségük van-e felülbírálni az alaposztály implementálását, vagy implementációt kell biztosítaniuk. Az absztrakt kulcsszóval kényszerítheti a származtatott osztályokat a megvalósítás biztosítására. A virtuális kulcsszóval engedélyezheti, hogy a származtatott osztályok felülírják az alaposztály metódusát. Alapértelmezés szerint az alaposztályban definiált metódusok nem felülírhatók.

    A Publication osztály nem rendelkezik abstract metódusok, de maga az osztály abstract.

  • Az, hogy egy származtatott osztály az öröklési hierarchiában az utolsó osztályt jelöli-e, és önmagában nem használható-e alaposztályként további származtatott osztályokhoz. Alapértelmezés szerint bármely osztály szolgálhat alaposztályként. A lezárt kulcsszóval jelezheti, hogy egy osztály nem szolgálhat alaposztályként további osztályokhoz. A lezárt osztályból való származtatási kísérlet fordítóhiba Cs0509-et generált: "nem származhat lezárt típusból <typeName>."

    A példádban te fogod a származtatott osztályt sealed-ként megjelölni.

Az alábbi példa a Publication osztály forráskódját, valamint a PublicationType tulajdonság által visszaadott Publication.PublicationType enumerálást mutatja be. A Object-tól örökölt tagok mellett a Publication osztály a következő egyedi tagokat és tagfelülbírálásokat határozza meg:


public enum PublicationType { Misc, Book, Magazine, Article };

public abstract class Publication
{
    private bool _published = false;
    private DateTime _datePublished;
    private int _totalPages;

    public Publication(string title, string publisher, PublicationType type)
    {
        if (string.IsNullOrWhiteSpace(publisher))
            throw new ArgumentException("The publisher is required.");
        Publisher = publisher;

        if (string.IsNullOrWhiteSpace(title))
            throw new ArgumentException("The title is required.");
        Title = title;

        Type = type;
    }

    public string Publisher { get; }

    public string Title { get; }

    public PublicationType Type { get; }

    public string? CopyrightName { get; private set; }

    public int CopyrightDate { get; private set; }

    public int Pages
    {
        get { return _totalPages; }
        set
        {
            if (value <= 0)
                throw new ArgumentOutOfRangeException(nameof(value), "The number of pages cannot be zero or negative.");
            _totalPages = value;
        }
    }

    public string GetPublicationDate()
    {
        if (!_published)
            return "NYP";
        else
            return _datePublished.ToString("d");
    }

    public void Publish(DateTime datePublished)
    {
        _published = true;
        _datePublished = datePublished;
    }

    public void Copyright(string copyrightName, int copyrightDate)
    {
        if (string.IsNullOrWhiteSpace(copyrightName))
            throw new ArgumentException("The name of the copyright holder is required.");
        CopyrightName = copyrightName;

        int currentYear = DateTime.Now.Year;
        if (copyrightDate < currentYear - 10 || copyrightDate > currentYear + 2)
            throw new ArgumentOutOfRangeException($"The copyright year must be between {currentYear - 10} and {currentYear + 1}");
        CopyrightDate = copyrightDate;
    }

    public override string ToString() => Title;
}
  • Konstruktor

    Mivel a Publication osztály abstract, nem hozható létre közvetlenül a kódból, például az alábbi példában:

    var publication = new Publication("Tiddlywinks for Experts", "Fun and Games",
                                      PublicationType.Book);
    

    A példánykonstruktor azonban közvetlenül származtatott osztálykonstruktorokból hívható meg, ahogyan az Book osztály forráskódja is mutatja.

  • Két kiadványhoz kapcsolódó tulajdonság

    Title egy írásvédett String tulajdonság, amelynek értékét a Publication konstruktor meghívása szolgáltatja.

    Pages egy olvasási-írási Int32 tulajdonság, amely azt jelzi, hogy a kiadvány hány oldallal rendelkezik. Az érték egy totalPagesnevű magánmezőben van tárolva. Pozitív számnak kell lennie, különben egy ArgumentOutOfRangeException kódú hibát dobunk.

  • Kiadói tagok

    Két írásvédett tulajdonság, Publisher és Type. Az értékeket eredetileg a Publication osztálykonstruktor hívása adja meg.

  • Közzétételhez kapcsolódó tagok

    Két metódus , Publish és GetPublicationDate– adja meg és adja vissza a közzététel dátumát. A Publish metódus egy privát published jelzőt állít be true a meghíváskor, és argumentumként hozzárendeli a neki átadott dátumot a privát datePublished mezőhöz. A GetPublicationDate metódus az "NYP" sztringet adja vissza, ha a published jelző false, és ha datePublished, akkor a true mező értékét.

  • Szerzői joggal kapcsolatos tagok

    A Copyright metódus argumentumként veszi fel a szerzői jog jogosultjának nevét és a szerzői jog évét, és hozzárendeli őket a CopyrightName és CopyrightDate tulajdonságokhoz.

  • A ToString metódus felülbírálása

    Ha egy típus nem bírálja felül a Object.ToString metódust, akkor a típus teljes nevét adja vissza, amely kevés hasznos az egyik példány megkülönböztetése során a másiktól. A Publication osztály felülírja a Object.ToString-t, hogy visszaadja a Title tulajdonság értékét.

Az alábbi ábra az alapszintű Publication osztály és az implicit módon örökölt Object osztály közötti kapcsolatot mutatja be.

Objektum- és kiadványosztályok

A Book osztály

A Book osztály egy könyvet jelöl speciális kiadványtípusként. Az alábbi példa a Book osztály forráskódját mutatja be.

using System;

public sealed class Book : Publication
{
    public Book(string title, string author, string publisher) :
           this(title, string.Empty, author, publisher)
    { }

    public Book(string title, string isbn, string author, string publisher) : base(title, publisher, PublicationType.Book)
    {
        // isbn argument must be a 10- or 13-character numeric string without "-" characters.
        // We could also determine whether the ISBN is valid by comparing its checksum digit
        // with a computed checksum.
        //
        if (!string.IsNullOrEmpty(isbn))
        {
            // Determine if ISBN length is correct.
            if (!(isbn.Length == 10 | isbn.Length == 13))
                throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
            if (!ulong.TryParse(isbn, out _))
                throw new ArgumentException("The ISBN can consist of numeric characters only.");
        }
        ISBN = isbn;

        Author = author;
    }

    public string ISBN { get; }

    public string Author { get; }

    public decimal Price { get; private set; }

    // A three-digit ISO currency symbol.
    public string? Currency { get; private set; }

    // Returns the old price, and sets a new price.
    public decimal SetPrice(decimal price, string currency)
    {
        if (price < 0)
            throw new ArgumentOutOfRangeException(nameof(price), "The price cannot be negative.");
        decimal oldValue = Price;
        Price = price;

        if (currency.Length != 3)
            throw new ArgumentException("The ISO currency symbol is a 3-character string.");
        Currency = currency;

        return oldValue;
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Book book)
            return false;
        else
            return ISBN == book.ISBN;
    }

    public override int GetHashCode() => ISBN.GetHashCode();

    public override string ToString() => $"{(string.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}

A Publication-tól örökölt tagok mellett a Book osztály a következő egyedi tagokat és tagfelülbírálásokat határozza meg:

  • Két konstruktor

    A két Book konstruktor három gyakori paramétert használ. Kettő, cím és kiadó, a Publication konstruktor paramétereinek felelnek meg. A harmadik a szerző, amely egy nyilvános, nem módosítható Author tulajdonságként van tárolva. Az egyik konstruktor tartalmaz egy isbn paramétert, amelyet a ISBN automatikus tulajdonság tárol.

    Az első konstruktor a másik konstruktor meghívásához ezt a kulcsszót használja. A konstruktorláncolás a konstruktorok definiálásának gyakori mintája. A kevesebb paraméterrel rendelkező konstruktorok alapértelmezett értékeket biztosítanak, amikor a konstruktort a legnagyobb számú paraméterrel hívja meg.

    A második konstruktor a alap kulcsszóval adja át a címet és a kiadó nevét az alaposztály konstruktorának. Ha nem hív meg explicit módon egy alaposztály-konstruktort a forráskódban, a C#-fordító automatikusan meghívja az alaposztály alapértelmezett vagy paraméter nélküli konstruktorát.

  • Csak olvasható ISBN tulajdonság, amely a Book objektum nemzetközi standard könyvszámát adja vissza, egy egyedi 10- vagy 13-jegyű számot. Az ISBN argumentumként van megadva az egyik Book konstruktornak. Az ISBN egy privát háttérmezőben van tárolva, amelyet a fordító automatikusan generál.

  • Írásvédett Author tulajdonság. A szerző nevét argumentumként adja meg mindkét Book konstruktor, és a tulajdonság tárolja.

  • Két csak olvasható, árhoz kapcsolódó tulajdonság, Price és Currency. Az értékek argumentumként vannak megadva egy SetPrice metódushívásban. A Currency tulajdonság a háromjegyű ISO pénznemszimbólum (például usd az amerikai dollár esetében). Az ISO-pénznemszimbólumok lekérhetők a ISOCurrencySymbol tulajdonságból. Mindkét tulajdonság kívülről írásvédett, de mindkettő beállítható kóddal a Book osztályban.

  • Egy SetPrice metódus, amely a Price és Currency tulajdonságok értékeit állítja be. Ezeket az értékeket ugyanazok a tulajdonságok adják vissza.

  • Felülbírálások a ToString metódusra (öröklött a Publication-ból/-ből) és a Object.Equals(Object) és a GetHashCode metódusokra (öröklött a Object-ból/-ből).

    Kivéve, ha felül van bírálva, a Object.Equals(Object) módszer a referenciaegyenlőség vizsgálatára szolgál. Vagyis két objektumváltozó egyenlőnek minősül, ha ugyanarra az objektumra hivatkoznak. A Book osztályban viszont két Book objektumnak egyenlőnek kell lennie, ha ugyanazzal az ISBN-vel rendelkeznek.

    Ha felülbírálja a Object.Equals(Object) metódust, felül kell bírálnia a GetHashCode metódust is, amely egy olyan értéket ad vissza, amelyet a futtatókörnyezet a kivonatolt gyűjtemények elemeinek tárolására használ a hatékony lekérés érdekében. A kivonatkódnak olyan értéket kell visszaadnia, amely összhangban van az egyenlőségi teszttel. Mivel felülbíráltad Object.Equals(Object)-t, hogy true-t adjon vissza, ha két Book objektum ISBN tulajdonságai egyenlők, visszaadod a GetHashCode metódus meghívásával kiszámított kivonatkódot, amelyet a ISBN tulajdonság által visszaadott sztring ad.

Az alábbi ábra a Book osztály és az alaposztály Publicationközötti kapcsolatot mutatja be.

kiadvány- és könyvosztályok

Most már létrehozhat egy Book objektumot, meghívhatja annak egyedi és örökölt tagjait is, és argumentumként továbbíthatja azt egy olyan metódusnak, amely Publication vagy Booktípusú paramétert vár, ahogyan az alábbi példa is mutatja.

public class ClassExample
{
    public static void Main()
    {
        var book = new Book("The Tempest", "0971655819", "Shakespeare, William",
                            "Public Domain Press");
        ShowPublicationInfo(book);
        book.Publish(new DateTime(2016, 8, 18));
        ShowPublicationInfo(book);

        var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
        Console.Write($"{book.Title} and {book2.Title} are the same publication: " +
              $"{((Publication)book).Equals(book2)}");
    }

    public static void ShowPublicationInfo(Publication pub)
    {
        string pubDate = pub.GetPublicationDate();
        Console.WriteLine($"{pub.Title}, " +
                  $"{(pubDate == "NYP" ? "Not Yet Published" : "published on " + pubDate):d} by {pub.Publisher}");
    }
}
// The example displays the following output:
//        The Tempest, Not Yet Published by Public Domain Press
//        The Tempest, published on 8/18/2016 by Public Domain Press
//        The Tempest and The Tempest are the same publication: False

Absztrakt alaposztályok és származtatott osztályaik tervezése

Az előző példában meghatározott egy alaposztályt, amely számos metódus implementációját adta ahhoz, hogy a származtatott osztályok megoszthassanak egy kódot. Sok esetben azonban az alaposztály várhatóan nem nyújt implementációt. Az alaposztály ehelyett egy absztrakt osztály, amely absztrakt metódusokat deklarál; sablonként szolgál, amely meghatározza azokat a tagokat, amelyeket az egyes származtatott osztályoknak implementálniuk kell. Az absztrakt alaposztályokban általában az egyes származtatott típusok implementációja egyedi az adott típushoz. Az osztályt absztrakciós kulcsszóval jelölte meg, mert nem volt értelme példányosítani egy Publication objektumot, bár az osztály a kiadványok által gyakran használt funkciók implementációit biztosította.

Például minden zárt kétdimenziós geometriai alakzat két tulajdonságot tartalmaz: terület, az alakzat belső kiterjedése; kerületet vagy az alakzat szélei mentén mért távolságot. A tulajdonságok kiszámításának módja azonban teljesen az adott alakzattól függ. A kör kerületének (vagy kerületének) kiszámítására szolgáló képlet például eltér a négyzetétől. A Shape osztály egy abstract osztály abstract metódusokkal. Ez azt jelzi, hogy a származtatott osztályok ugyanazt a funkciót használják, de ezek a származtatott osztályok ezt a funkciót másképp valósítják meg.

Az alábbi példa egy Shape nevű absztrakt alaposztályt definiál, amely két tulajdonságot határoz meg: Area és Perimeter. Amellett, hogy az osztályt absztrakt kulcsszóval jelöli, minden egyes példánytagot az absztrakt kulcsszó is jelöl. Ebben az esetben a Shape úgy írja felül a Object.ToString metódust, hogy a típus nevét adja vissza a teljes név helyett. Két statikus tagot határoz meg, GetArea és GetPerimeter, amelyek lehetővé teszik a hívók számára, hogy könnyen lekérjék a származtatott osztály egy példányának területét és kerületét. Amikor egy származtatott osztály egy példányát átadja valamelyik metódusnak, a futtatókörnyezet meghívja a származtatott osztály metódus felülbírálását.

public abstract class Shape
{
    public abstract double Area { get; }

    public abstract double Perimeter { get; }

    public override string ToString() => GetType().Name;

    public static double GetArea(Shape shape) => shape.Area;

    public static double GetPerimeter(Shape shape) => shape.Perimeter;
}

Ezután bizonyos osztályok származtathatók Shape, amelyek meghatározott alakzatokat jelölnek. Az alábbi példa három osztályt határoz meg: Square, Rectangleés Circle. Mindegyik az adott alakzathoz egyedi képletet használ a terület és a szegély kiszámításához. A származtatott osztályok némelyike olyan tulajdonságokat is definiál, például Rectangle.Diagonal és Circle.Diameter, amelyek egyediek az általuk képviselt alakzatra.

using System;

public class Square : Shape
{
    public Square(double length)
    {
        Side = length;
    }

    public double Side { get; }

    public override double Area => Math.Pow(Side, 2);

    public override double Perimeter => Side * 4;

    public double Diagonal => Math.Round(Math.Sqrt(2) * Side, 2);
}

public class Rectangle : Shape
{
    public Rectangle(double length, double width)
    {
        Length = length;
        Width = width;
    }

    public double Length { get; }

    public double Width { get; }

    public override double Area => Length * Width;

    public override double Perimeter => 2 * Length + 2 * Width;

    public bool IsSquare() => Length == Width;

    public double Diagonal => Math.Round(Math.Sqrt(Math.Pow(Length, 2) + Math.Pow(Width, 2)), 2);
}

public class Circle : Shape
{
    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double Area => Math.Round(Math.PI * Math.Pow(Radius, 2), 2);

    public override double Perimeter => Math.Round(Math.PI * 2 * Radius, 2);

    // Define a circumference, since it's the more familiar term.
    public double Circumference => Perimeter;

    public double Radius { get; }

    public double Diameter => Radius * 2;
}

Az alábbi példa Shapeszármazó objektumokat használ. Létrehoz egy Shape osztályból származó objektumtömböt, és meghívja a Shape osztály statikus metódusait, amelyek becsomagolják a Shape tulajdonságértékeket. A futtatókörnyezet lekéri az értékeket a származtatott típusok felülírt tulajdonságaiból. A példa a tömb minden Shape objektumát kasztolja annak származtatott típusára, és ha a kasztolás sikeres, lekéri a Shapeadott alosztályának tulajdonságait.

using System;

public class Example
{
    public static void Main()
    {
        Shape[] shapes = { new Rectangle(10, 12), new Square(5),
                    new Circle(3) };
        foreach (Shape shape in shapes)
        {
            Console.WriteLine($"{shape}: area, {Shape.GetArea(shape)}; " +
                              $"perimeter, {Shape.GetPerimeter(shape)}");
            if (shape is Rectangle rect)
            {
                Console.WriteLine($"   Is Square: {rect.IsSquare()}, Diagonal: {rect.Diagonal}");
                continue;
            }
            if (shape is Square sq)
            {
                Console.WriteLine($"   Diagonal: {sq.Diagonal}");
                continue;
            }
        }
    }
}
// The example displays the following output:
//         Rectangle: area, 120; perimeter, 44
//            Is Square: False, Diagonal: 15.62
//         Square: area, 25; perimeter, 20
//            Diagonal: 7.07
//         Circle: area, 28.27; perimeter, 18.85