Arv i C# och .NET
I den här självstudien får du lära dig arv i C#. Arv är en funktion i objektorienterade programmeringsspråk som gör att du kan definiera en basklass som ger specifika funktioner (data och beteende) och definiera härledda klasser som antingen ärver eller åsidosätter den funktionen.
Förutsättningar
- Vi rekommenderar Visual Studio för Windows. Du kan ladda ned en kostnadsfri version från nedladdningssidan för Visual Studio. Visual Studio innehåller .NET SDK.
- Du kan också använda Visual Studio Code-redigeraren med C# DevKit. Du måste installera den senaste .NET SDK separat.
- Om du föredrar en annan redigerare måste du installera den senaste .NET SDK:en.
Köra exemplen
Om du vill skapa och köra exemplen i den här självstudien använder du dotnet-verktyget från kommandoraden. Följ dessa steg för varje exempel:
Skapa en katalog för att lagra exemplet.
Ange det nya konsolkommandot dotnet i en kommandotolk för att skapa ett nytt .NET Core-projekt.
Kopiera och klistra in koden från exemplet i kodredigeraren.
Ange kommandot dotnet restore från kommandoraden för att läsa in eller återställa projektets beroenden.
Du behöver inte köra
dotnet restore
eftersom den körs implicit av alla kommandon som kräver en återställning, till exempeldotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
ochdotnet pack
. Om du vill inaktivera implicit återställning använder du alternativet--no-restore
.Kommandot
dotnet restore
är fortfarande användbart i vissa scenarier där det är meningsfullt att uttryckligen återställa, till exempel kontinuerliga integreringsversioner i Azure DevOps Services eller i byggsystem som uttryckligen behöver styra när återställningen sker.Information om hur du hanterar NuGet-feeds finns i dokumentationen
dotnet restore
.Ange kommandot dotnet run för att kompilera och köra exemplet.
Bakgrund: Vad är arv?
Arv är ett av de grundläggande attributen för objektorienterad programmering. Du kan definiera en underordnad klass som återanvänder (ärver), utökar eller ändrar beteendet för en överordnad klass. Klassen vars medlemmar ärvs kallas för basklassen. Klassen som ärver medlemmarna i basklassen kallas för den härledda klassen.
C# och .NET stöder endast enskilt arv . En klass kan alltså bara ärva från en enda klass. Arv är dock transitivt, vilket gör att du kan definiera en arvshierarki för en uppsättning typer. Med andra ord kan typen D
ärva från typen C
, som ärver från typen B
, som ärver från basklasstypen A
. Eftersom arv är transitivt är medlemmarna av typen A
tillgängliga för att skriva D
.
Alla medlemmar i en basklass ärvs inte av härledda klasser. Följande medlemmar ärvs inte:
Statiska konstruktorer som initierar statiska data för en klass.
Instanskonstruktorer som du anropar för att skapa en ny instans av klassen. Varje klass måste definiera sina egna konstruktorer.
Finalizers, som anropas av körningens skräpinsamlare för att förstöra instanser av en klass.
Alla andra medlemmar i en basklass ärvs av härledda klasser, men om de är synliga eller inte beror på deras tillgänglighet. En medlems tillgänglighet påverkar dess synlighet för härledda klasser på följande sätt:
Privata medlemmar visas endast i härledda klasser som är kapslade i sin basklass. Annars visas de inte i härledda klasser. I följande exempel
A.B
är en kapslad klass som härleds frånA
ochC
härleds frånA
. Det privataA._value
fältet visas i A.B. Men om du tar bort kommentarerna frånC.GetValue
metoden och försöker kompilera exemplet genererar den kompilatorfelet CS0122: "'A._value' är otillgänglig på grund av dess skyddsnivå."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
Skyddade medlemmar visas endast i härledda klasser.
Interna medlemmar visas endast i härledda klasser som finns i samma sammansättning som basklassen. De visas inte i härledda klasser som finns i en annan sammansättning än basklassen.
Offentliga medlemmar visas i härledda klasser och ingår i den härledda klassens offentliga gränssnitt. Offentliga ärvda medlemmar kan anropas precis som om de definieras i den härledda klassen. I följande exempel definierar klassen
A
en metod med namnetMethod1
, och klassenB
ärver från klassenA
. Exemplet anroparMethod1
sedan som om det vore en instansmetod påB
.public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new (); b.Method1(); } }
Härledda klasser kan också åsidosätta ärvda medlemmar genom att tillhandahålla en alternativ implementering. För att kunna åsidosätta en medlem måste medlemmen i basklassen markeras med det virtuella nyckelordet. Som standard markeras inte basklassmedlemmar som virtual
och kan inte åsidosättas. Om du försöker åsidosätta en icke-virtuell medlem, som i följande exempel, genereras kompilatorfelet CS0506: "<medlemmen> kan inte åsidosätta ärvd medlemsmedlem <> eftersom den inte har markerats som virtuell, abstrakt eller åsidosättning."
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
I vissa fall måste en härledd klass åsidosätta basklassimplementeringen. Basklassmedlemmar som markerats med det abstrakta nyckelordet kräver att härledda klasser åsidosätter dem. När du försöker kompilera följande exempel genereras kompilatorfelet CS0534, "<klassen implementerar inte ärvd abstrakt medlemsmedlem><", eftersom klassen B
inte tillhandahåller någon implementering för A.Method1
> .
public abstract class A
{
public abstract void Method1();
}
public class B : A // Generates CS0534.
{
public void Method3()
{
// Do something.
}
}
Arv gäller endast klasser och gränssnitt. Andra typkategorier (structs, ombud och uppräkningar) stöder inte arv. På grund av dessa regler genererar försök att kompilera kod som i följande exempel kompilatorfel CS0527: "Skriv "ValueType" i gränssnittslistan är inte ett gränssnitt." Felmeddelandet anger att arv inte stöds, även om du kan definiera de gränssnitt som en struct implementerar.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Implicit arv
Förutom alla typer som de kan ärva från via ett enda arv ärver alla typer i .NET-typsystemet implicit från Object eller en typ som härleds från det. De vanliga funktionerna Object i är tillgängliga för alla typer.
Om du vill se vad implicit arv innebär ska vi definiera en ny klass, SimpleClass
, som helt enkelt är en tom klassdefinition:
public class SimpleClass
{ }
Du kan sedan använda reflektion (som gör att du kan granska en typs metadata för att få information om den typen) för att hämta en lista över de medlemmar som tillhör SimpleClass
typen. Även om du inte har definierat några medlemmar i klassen SimpleClass
indikerar utdata från exemplet att den faktiskt har nio medlemmar. En av dessa medlemmar är en parameterlös (eller standard) konstruktor som automatiskt tillhandahålls för SimpleClass
typen av C#-kompilatorn. De återstående åtta är medlemmar i Object, den typ som alla klasser och gränssnitt i .NET-typsystemet slutligen implicit ärver.
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
Implicit arv från Object klassen gör dessa metoder tillgängliga för SimpleClass
klassen:
Den offentliga
ToString
metoden, som konverterar ettSimpleClass
objekt till dess strängrepresentation, returnerar det fullständigt kvalificerade typnamnet. I det här falletToString
returnerar metoden strängen "SimpleClass".Tre metoder som testar för likhet mellan två objekt: den offentliga instansmetoden
Equals(Object)
, den offentliga statiskaEquals(Object, Object)
metoden och den offentliga statiskaReferenceEquals(Object, Object)
metoden. Som standard testar dessa metoder för referensjämlikhet. för att vara lika med måste två objektvariabler referera till samma objekt.Den offentliga
GetHashCode
metoden, som beräknar ett värde som gör att en instans av typen kan användas i hashade samlingar.Den offentliga
GetType
metoden, som returnerar ett Type objekt som representerarSimpleClass
typen.Den skyddade Finalize metoden, som är utformad för att frigöra ohanterade resurser innan ett objekts minne frigörs av skräpinsamlaren.
Den skyddade MemberwiseClone metoden, som skapar en ytlig klon av det aktuella objektet.
På grund av implicit arv kan du anropa alla ärvda medlemmar från ett SimpleClass
objekt precis som om det faktiskt var en medlem som definierats i SimpleClass
klassen. I följande exempel anropas SimpleClass.ToString
till exempel metoden som SimpleClass
ärver från Object.
public class EmptyClass
{ }
public class ClassNameExample
{
public static void Main()
{
EmptyClass sc = new();
Console.WriteLine(sc.ToString());
}
}
// The example displays the following output:
// EmptyClass
I följande tabell visas de kategorier av typer som du kan skapa i C# och de typer som de implicit ärver från. Varje bastyp gör en annan uppsättning medlemmar tillgängliga via arv till implicit härledda typer.
Typkategori | Ärver implicit från |
---|---|
klass | Object |
Struct | ValueType, Object |
uppräkning | Enum, , ValueTypeObject |
Delegera | MulticastDelegate, , DelegateObject |
Arv och en "är en" relation
Vanligtvis används arv för att uttrycka en "is a"-relation mellan en basklass och en eller flera härledda klasser, där de härledda klasserna är specialiserade versioner av basklassen. den härledda klassen är en typ av basklass. Klassen representerar till exempel Publication
en publikation av något slag, och Book
klasserna och Magazine
representerar specifika typer av publikationer.
Kommentar
En klass eller struct kan implementera ett eller flera gränssnitt. Även om gränssnittsimplementering ofta presenteras som en lösning för enskilt arv eller som ett sätt att använda arv med structs, är det avsett att uttrycka en annan relation (en "kan göra"-relation) mellan ett gränssnitt och dess implementeringstyp än arv. Ett gränssnitt definierar en delmängd av funktioner (till exempel möjligheten att testa för likhet, jämföra eller sortera objekt eller för att stödja kulturkänslig parsning och formatering) som gränssnittet gör tillgängligt för sina implementeringstyper.
Observera att "är en" också uttrycker relationen mellan en typ och en specifik instansiering av den typen. I följande exempel Automobile
är en klass som har tre unika skrivskyddade egenskaper: Make
, tillverkaren av bilen, Model
, typen av bil och Year
, dess tillverkningsår. Klassen Automobile
har också en konstruktor vars argument tilldelas till egenskapsvärdena och åsidosätter Object.ToString metoden för att skapa en sträng som unikt identifierar instansen Automobile
i stället Automobile
för klassen.
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}";
}
I det här fallet bör du inte förlita dig på arv för att representera specifika bilmodeller och bilmodeller. Du behöver till exempel inte definiera en Packard
typ som representerar bilar som tillverkas av Packard Motor Car Company. I stället kan du representera dem genom att skapa ett Automobile
objekt med lämpliga värden som skickas till dess klasskonstruktor, som i följande exempel.
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
En is-a-relation som baseras på arv tillämpas bäst på en basklass och på härledda klasser som lägger till ytterligare medlemmar i basklassen eller som kräver ytterligare funktioner som inte finns i basklassen.
Utforma basklassen och härledda klasser
Nu ska vi titta på processen med att utforma en basklass och dess härledda klasser. I det här avsnittet definierar du en basklass, Publication
, som representerar en publikation av något slag, till exempel en bok, en tidning, en tidning, en tidskrift, en artikel osv. Du definierar också en Book
klass som härleds från Publication
. Du kan enkelt utöka exemplet för att definiera andra härledda klasser, till exempel Magazine
, Journal
, Newspaper
och Article
.
Baspubliceringsklassen
När du utformar din Publication
klass måste du fatta flera designbeslut:
Vilka medlemmar som ska ingå i basklassen
Publication
Publication
och om medlemmarna tillhandahåller metodimplementeringar eller omPublication
det är en abstrakt basklass som fungerar som mall för sina härledda klasser.I det här fallet
Publication
tillhandahåller klassen metodimplementeringar. Avsnittet Designa abstrakta basklasser och deras härledda klasser innehåller ett exempel som använder en abstrakt basklass för att definiera de metoder som härledda klasser måste åsidosätta. Härledda klasser kan tillhandahålla alla implementeringar som är lämpliga för den härledda typen.Möjligheten att återanvända kod (dvs. flera härledda klasser delar deklarationen och implementeringen av basklassmetoder och behöver inte åsidosätta dem) är en fördel med icke-abstrakta basklasser. Därför bör du lägga till
Publication
medlemmar i om deras kod sannolikt kommer att delas av vissa eller de flesta specialiseradePublication
typer. Om du inte tillhandahåller grundläggande klassimplementeringar effektivt måste du tillhandahålla i stort sett identiska medlemsimplementeringar i härledda klasser i stället för en enda implementering i basklassen. Behovet av att underhålla duplicerad kod på flera platser är en potentiell källa till buggar.Både för att maximera återanvändning av kod och för att skapa en logisk och intuitiv arvshierarki vill du vara säker på att du i
Publication
klassen endast inkluderar de data och funktioner som är gemensamma för alla eller för de flesta publikationer. Härledda klasser implementerar sedan medlemmar som är unika för de specifika typer av publikationer som de representerar.Hur långt du kan utöka din klasshierarki. Vill du utveckla en hierarki med tre eller flera klasser i stället för bara en basklass och en eller flera härledda klasser? Kan till exempel
Publication
vara en basklass förPeriodical
, som i sin tur är en basklass förMagazine
,Journal
ochNewspaper
.I ditt exempel använder du den lilla hierarkin för en
Publication
klass och en enda härledd klass,Book
. Du kan enkelt utöka exemplet för att skapa ett antal ytterligare klasser som härleds frånPublication
, till exempelMagazine
ochArticle
.Om det är meningsfullt att instansiera basklassen. Om den inte gör det bör du använda det abstrakta nyckelordet för klassen. Annars kan klassen
Publication
instansieras genom att anropa dess klasskonstruktor. Om ett försök görs att instansiera en klass som markerats med nyckelordetabstract
av ett direktanrop till dess klasskonstruktor genererar C#-kompilatorn felet CS0144, "Det går inte att skapa en instans av den abstrakta klassen eller gränssnittet". Om ett försök görs att instansiera klassen med hjälp av reflektion genererar reflektionsmetoden en MemberAccessException.Som standard kan en basklass instansieras genom att anropa dess klasskonstruktor. Du behöver inte uttryckligen definiera en klasskonstruktor. Om det inte finns någon i basklassens källkod tillhandahåller C#-kompilatorn automatiskt en standardkonstruktor (parameterlös).
I ditt exempel markerar
Publication
du klassen som abstrakt så att den inte kan instansieras. Enabstract
klass utan metoderabstract
anger att den här klassen representerar ett abstrakt begrepp som delas mellan flera konkreta klasser (till exempel enBook
,Journal
).Om härledda klasser måste ärva basklassimplementeringen av vissa medlemmar, om de har möjlighet att åsidosätta basklassimplementeringen eller om de måste tillhandahålla en implementering. Du använder det abstrakta nyckelordet för att tvinga härledda klasser att tillhandahålla en implementering. Du använder det virtuella nyckelordet för att tillåta att härledda klasser åsidosätter en basklassmetod. Som standard kan metoder som definierats i basklassen inte åsidosättas.
Klassen
Publication
har ingaabstract
metoder, men själva klassen ärabstract
.Om en härledd klass representerar den sista klassen i arvshierarkin och inte själv kan användas som basklass för ytterligare härledda klasser. Som standard kan alla klasser fungera som en basklass. Du kan använda det förseglade nyckelordet för att ange att en klass inte kan fungera som basklass för ytterligare klasser. Försök att härleda från ett förseglat klassgenererat kompilatorfel CS0509, "kan inte härledas från typnamn> av förseglad typ<".
I ditt exempel markerar du din härledda klass som
sealed
.
I följande exempel visas källkoden Publication
för klassen samt en PublicationType
uppräkning som returneras av Publication.PublicationType
egenskapen. Förutom de medlemmar som den ärver från ObjectPublication
definierar klassen följande unika medlemmar och medlems åsidosättningar:
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;
}
En konstruktor
Publication
Eftersom klassen ärabstract
kan den inte instansieras direkt från kod som i följande exempel:var publication = new Publication("Tiddlywinks for Experts", "Fun and Games", PublicationType.Book);
Dess instanskonstruktor kan dock anropas direkt från härledda klasskonstruktorer, som källkoden
Book
för klassen visar.Två publikationsrelaterade egenskaper
Title
är en skrivskyddad String egenskap vars värde anges genom att anropaPublication
konstruktorn.Pages
är en skrivskyddad Int32 egenskap som anger hur många totalt antal sidor publikationen har. Värdet lagras i ett privat fält med namnettotalPages
. Det måste vara ett positivt tal eller ett ArgumentOutOfRangeException utlöses.Utgivarrelaterade medlemmar
Två skrivskyddade egenskaper och
Publisher
Type
. Värdena tillhandahålls ursprungligen av anropet tillPublication
klasskonstruktorn.Publiceringsrelaterade medlemmar
Två metoder,
Publish
ochGetPublicationDate
, anger och returnerar publiceringsdatumet. MetodenPublish
anger en privatpublished
flagga tilltrue
när den anropas och tilldelar det datum som skickades till den som ett argument till det privatadatePublished
fältet. MetodenGetPublicationDate
returnerar strängen "NYP" ompublished
flaggan ärfalse
och värdet fördatePublished
fältet om det ärtrue
.Upphovsrättsrelaterade medlemmar
Metoden
Copyright
tar namnet på upphovsrättsinnehavaren och upphovsrättsinnehavarens år som argument och tilldelar dem tillCopyrightName
egenskaperna ochCopyrightDate
.En åsidosättning av
ToString
metodenOm en typ inte åsidosätter Object.ToString metoden returnerar den det fullständigt kvalificerade namnet på typen, vilket är till liten nytta vid differentiering av en instans från en annan. Klassen
Publication
åsidosätter Object.ToString för att returnera värdet förTitle
egenskapen.
Följande bild illustrerar relationen mellan basklassen Publication
och dess implicit ärvda Object klass.
Klassen Book
Klassen Book
representerar en bok som en specialiserad typ av publikation. I följande exempel visas källkoden Book
för klassen.
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}";
}
Förutom de medlemmar som den ärver från Publication
Book
definierar klassen följande unika medlemmar och medlems åsidosättningar:
Två konstruktorer
De två
Book
konstruktorerna delar tre vanliga parametrar. Två, rubrik och utgivare, motsvarar konstruktornsPublication
parametrar. Den tredje är författare, som lagras i en offentlig oföränderligAuthor
egenskap. En konstruktor innehåller en isbn-parameter som lagras i denISBN
automatiska egenskapen.Den första konstruktorn använder nyckelordet för att anropa den andra konstruktorn. Konstruktorlänkning är ett vanligt mönster för att definiera konstruktorer. Konstruktorer med färre parametrar ger standardvärden när konstruktorn anropas med det största antalet parametrar.
Den andra konstruktorn använder basnyckelordet för att skicka rubrik- och utgivarnamnet till basklasskonstruktorn. Om du inte gör ett explicit anrop till en basklasskonstruktor i källkoden, levererar C#-kompilatorn automatiskt ett anrop till basklassens standardkonstruktor eller parameterlösa konstruktor.
En skrivskyddad
ISBN
egenskap som returnerarBook
objektets internationella standardboksnummer, ett unikt 10- eller 13-siffrigt tal. ISBN levereras som ett argument till en av konstruktörernaBook
. ISBN lagras i ett privat säkerhetskopieringsfält som genereras automatiskt av kompilatorn.En skrivskyddad
Author
egenskap. Författarens namn anges som ett argument till bådaBook
konstruktorerna och lagras i egenskapen .Två skrivskyddade prisrelaterade egenskaper
Price
ochCurrency
. Deras värden anges som argument i ettSetPrice
metodanrop. EgenskapenCurrency
är den tresiffriga ISO-valutasymbolen (till exempel USD för den amerikanska dollarn). ISO-valutasymboler kan hämtas från egenskapen ISOCurrencySymbol . Båda dessa egenskaper är externt skrivskyddade, men båda kan anges med kod iBook
klassen.En
SetPrice
metod som anger värdena förPrice
egenskaperna ochCurrency
. Dessa värden returneras av samma egenskaper.Åsidosättningar till
ToString
metoden (ärvd frånPublication
) och Object.Equals(Object) metoderna och GetHashCode (ärvda från Object).Om det inte åsidosätts Object.Equals(Object) , testar metoden för referensjämlikhet. Det innebär att två objektvariabler anses vara lika med om de refererar till samma objekt.
Book
I klassen ska å andra sidan tvåBook
objekt vara lika med om de har samma ISBN.När du åsidosätter Object.Equals(Object) metoden måste du också åsidosätta GetHashCode metoden, som returnerar ett värde som körningen använder för att lagra objekt i hashade samlingar för effektiv hämtning. Hash-koden ska returnera ett värde som är konsekvent med testet för likhet. Eftersom du har åsidosatt Object.Equals(Object) för att returnera
true
om ISBN-egenskaperna för tvåBook
objekt är lika returnerar du hash-koden som beräknas genom att anropa GetHashCode metoden för strängen som returneras avISBN
egenskapen.
Följande bild illustrerar relationen mellan Book
klassen och Publication
, dess basklass.
Nu kan du instansiera ett Book
objekt, anropa både dess unika och ärvda medlemmar och skicka det som ett argument till en metod som förväntar sig en parameter av typen Publication
eller av typen Book
, som i följande exempel visas.
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
Utforma abstrakta basklasser och deras härledda klasser
I föregående exempel definierade du en basklass som tillhandahöll en implementering för ett antal metoder för att tillåta härledda klasser att dela kod. I många fall förväntas dock inte basklassen tillhandahålla någon implementering. I stället är basklassen en abstrakt klass som deklarerar abstrakta metoder. Den fungerar som en mall som definierar de medlemmar som varje härledd klass måste implementera. I en abstrakt basklass är implementeringen av varje härledd typ vanligtvis unik för den typen. Du markerade klassen med det abstrakta nyckelordet eftersom det inte var meningsfullt att instansiera ett Publication
objekt, även om klassen gav implementeringar av funktioner som är gemensamma för publikationer.
Till exempel innehåller varje sluten tvådimensionell geometrisk form två egenskaper: området, formens inre omfattning; och perimeter, eller avståndet längs formens kanter. Hur dessa egenskaper beräknas beror dock helt på den specifika formen. Formeln för att beräkna en cirkels perimeter (eller omkrets) skiljer sig till exempel från en kvadrat. Klassen Shape
är en abstract
klass med abstract
metoder. Det indikerar att härledda klasser har samma funktioner, men de härledda klasserna implementerar den funktionen på olika sätt.
I följande exempel definieras en abstrakt basklass med namnet Shape
som definierar två egenskaper: Area
och Perimeter
. Förutom att markera klassen med det abstrakta nyckelordet markeras varje instansmedlem också med det abstrakta nyckelordet. I det här fallet Shape
åsidosätter Object.ToString även metoden för att returnera namnet på typen i stället för dess fullständigt kvalificerade namn. Och det definierar två statiska medlemmar, GetArea
och GetPerimeter
, som gör det möjligt för anropare att enkelt hämta området och perimetern för en instans av en härledd klass. När du skickar en instans av en härledd klass till någon av dessa metoder anropar körningen metoden åsidosättning av den härledda klassen.
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;
}
Du kan sedan härleda vissa klasser från Shape
som representerar specifika former. I följande exempel definieras tre klasser, Square
, Rectangle
och Circle
. Var och en använder en formel som är unik för just den formen för att beräkna området och perimetern. Vissa av de härledda klasserna definierar också egenskaper, till exempel och Circle.Diameter
, som Rectangle.Diagonal
är unika för den form som de representerar.
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;
}
I följande exempel används objekt som härletts från Shape
. Den instansierar en matris med objekt som härletts från Shape
och anropar klassens statiska metoder Shape
, som omsluter returegenskapsvärden Shape
. Körningen hämtar värden från de åsidosatta egenskaperna för de härledda typerna. Exemplet omvandlar också varje Shape
objekt i matrisen till dess härledda typ och hämtar, om gjutningen lyckas, egenskaperna för den specifika underklassen av Shape
.
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