Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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
- A legújabb .NET SDK
- Visual Studio Code szerkesztő
- A C# DevKit
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.
- Töltse le a fájlt, és kattintson rá duplán a futtatáshoz.
- Olvassa el a licencszerződést, írja be y, és válassza Adja meg, amikor a rendszer kéri az elfogadást.
- 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.
- 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.
- 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.
- 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:
Hozzon létre egy könyvtárat a példa tárolásához.
Új .NET Core-projekt létrehozásához írja be a dotnet új konzol parancsot egy parancssorba.
Másolja és illessze be a kódot a példából a kódszerkesztőbe.
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áuldotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
ésdotnet 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ó.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 C
típustól, amely a B
típustól örököl, amely az alaposztályból, a A
típusból származik. Mivel az öröklés tranzitív, a A
típus tagjai elérhetők a D
tí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 azA
-ből származik, míg aC
azA
-ból származik. A privátA._value
mező látható az A.B-ben. Ha azonban eltávolítja a megjegyzéseket aC.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 egyMethod1
nevű metódust határoz meg, az osztályB
pedig azA
osztálytól örökli. A példa ezután úgy hívja megMethod1
-t, mintha az egy példánymetódus lenne aB
-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.Method1
szá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 egySimpleClass
objektumot sztringre konvertál, a teljes típusnevet adja vissza. Ebben az esetben aToString
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 statikusEquals(Object, Object)
metódust és a nyilvános statikusReferenceEquals(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 aSimpleClass
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 Publication
szá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 aPublication
tagok nyújtanak-e metódus-implementációkat, vagyPublication
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áltPublication
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 aPeriodical
alaposztálya , amely viszont aMagazine
,Journal
ésNewspaper
alaposztá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, amelyekPublication
származnak , példáulMagazine
ésArticle
.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 aabstract
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. Aabstract
metódusok nélküliabstract
osztály azt jelzi, hogy ez az osztály olyan absztrakt fogalmat jelöl, amely több konkrét osztály (például egyBook
,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 rendelkezikabstract
metódusok, de maga az osztályabstract
.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ályabstract
, 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 aPublication
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 egytotalPages
nevű 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
ésType
. Az értékeket eredetileg aPublication
osztálykonstruktor hívása adja meg.Közzétételhez kapcsolódó tagok
Két metódus ,
Publish
ésGetPublicationDate
– adja meg és adja vissza a közzététel dátumát. APublish
metódus egy privátpublished
jelzőt állít betrue
a meghíváskor, és argumentumként hozzárendeli a neki átadott dátumot a privátdatePublished
mezőhöz. AGetPublicationDate
metódus az "NYP" sztringet adja vissza, ha apublished
jelzőfalse
, és hadatePublished
, akkor atrue
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 aCopyrightName
ésCopyrightDate
tulajdonságokhoz.A
ToString
metódus felülbírálásaHa 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 aTitle
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.
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ó, aPublication
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 aISBN
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 aBook
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 egyikBook
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étBook
konstruktor, és a tulajdonság tárolja.Két csak olvasható, árhoz kapcsolódó tulajdonság,
Price
ésCurrency
. Az értékek argumentumként vannak megadva egySetPrice
metódushívásban. ACurrency
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 aBook
osztályban.Egy
SetPrice
metódus, amely aPrice
ésCurrency
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 aPublication
-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étBook
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étBook
objektum ISBN tulajdonságai egyenlők, visszaadod a GetHashCode metódus meghívásával kiszámított kivonatkódot, amelyet aISBN
tulajdonság által visszaadott sztring ad.
Az alábbi ábra a Book
osztály és az alaposztály Publication
közötti kapcsolatot mutatja be.
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 Book
tí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 Shape
szá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 Shape
adott 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