Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här handledningen introducerar dig till 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
- Den senaste versionen av .NET SDK
- Visual Studio Code-redigerare
- C# DevKit
Installationsanvisningar
På Windows används den här WinGet-konfigurationsfilen för att installera alla krav. Om du redan har något installerat hoppar WinGet över det steget.
- Ladda ned filen och dubbelklicka för att köra den.
- Läs licensavtalet, skriv yoch välj Ange när du uppmanas att acceptera.
- Om du får en flashande UAC-fråga (User Account Control) i aktivitetsfältet tillåter du att installationen fortsätter.
På andra plattformar måste du installera var och en av dessa komponenter separat.
- Ladda ned det rekommenderade installationsprogrammet från nedladdningssidan .NET SDK och dubbelklicka för att köra det. Nedladdningssidan identifierar din plattform och rekommenderar den senaste installationsprogrammet för din plattform.
- Ladda ned det senaste installationsprogrammet från Visual Studio Code startsida och dubbelklicka för att köra det. Den sidan identifierar även din plattform och länken bör vara korrekt för ditt system.
- Klicka på knappen "Installera" på sidan C# DevKit tillägg. Då öppnas Visual Studio-kod och du tillfrågas om du vill installera eller aktivera tillägget. Välj "installera".
Att köra exemplen
Om du vill skapa och köra exemplen i den här självstudien använder du verktyget dotnet från kommandoraden. Följ dessa steg för varje exempel:
- Skapa en katalog för att lagra exemplet. 
- Ange kommandot dotnet new console 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 restoreeftersom den körs implicit av alla kommandon som kräver en återställning, till exempel- dotnet new,- dotnet build,- dotnet run,- dotnet test,- dotnet publishoch- dotnet 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 - dotnet restoredokumentationen.
- 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 basklass. Klassen som ärver medlemmarna i basklassen kallas deriverad klass.
C# och .NET stöder endast enkel 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 typ D ärva från typ C, som ärver från typ B, som ärver från basklasstypen A. Eftersom arv är transitivt är medlemmar 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örmiljöns skräpinsamlare för att slutfö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 basklassen. Annars visas de inte i härledda klasser. I följande exempel är - A.Ben kapslad klass som härleds från- Aoch- Chärleds från- A. Fältet privat- A._valuevisas i A.B. Men om du tar bort kommentarerna från metoden- C.GetValueoch försöker kompilera exemplet skapar det 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 deriverade 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. 
- public medlemmar är synliga i härledda klasser och ingår i den offentliga gränssnittet för den härledda klassen. Offentliga ärvda medlemmar kan anropas precis som om de vore definierade i den avledda klassen. I följande exempel definierar klass - Aen metod med namnet- Method1och klass- Bärver från klass- A. Exemplet anropar sedan- Method1som 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 erbjuda en alternativ implementering. För att kunna åsidosätta en medlem måste medlemmen i basklassen markeras med nyckelordet virtuell. 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: "<medlem> kan inte åsidosätta en ärvd medlem <medlem> eftersom den inte har markerats som virtuell, abstrakt eller åsidosatt."
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 nyckelordet abstrakt kräver att härledda klasser åsidosätter dem. När du försöker kompilera följande exempel genereras kompilatorfelet CS0534, "<klass> implementerar inte ärvd abstrakt medlem <medlem>", eftersom klass 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 (strukturer, delegater och uppräknade typer) stöder inte arv. På grund av dessa regler genererar ett försök att kompilera kod som i följande exempel kompilatorfel CS0527: "Typen '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 genom 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 i Object ä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 den SimpleClass typen. Även om du inte har definierat några medlemmar i din SimpleClass-klass, 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 typ 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 från.
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 ärvning från klassen Object gör att dessa metoder blir tillgängliga för klassen SimpleClass:
- Den offentliga - ToString-metoden, som konverterar ett- SimpleClass-objekt till dess strängrepresentation, returnerar det fullständigt kvalificerade typnamnet. I det här fallet returnerar metoden- ToStringsträngen "SimpleClass".
- Tre metoder som testar för likhet mellan två objekt: den offentliga instansen - Equals(Object)-metoden, metoden public static- Equals(Object, Object)och metoden public static- ReferenceEquals(Object, Object). 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 representerar- SimpleClasstypen.
- 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 grund 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 klassen SimpleClass. I följande exempel anropas till exempel metoden SimpleClass.ToString, 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 | 
| Struktur | ValueType, Object | 
| enumtyp | Enum, , ValueTypeObject | 
| delegera | MulticastDelegate, , DelegateObject | 
Arv och ett "är en" förhållande
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 Publication representerar till exempel en publikation av något slag, och klasserna Book och Magazine representerar specifika typer av publikationer.
Anmärkning
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 förhållandet mellan en typ och en specifik instansiering av den typen. I följande exempel är Automobile en klass som har tre unika skrivskyddade egenskaper: Make, bilens tillverkare; Model, bilens typ; och Year, tillverkningsåret. Klassen Automobile har också en konstruktor vars argument tilldelas till egenskapsvärdena och åsidosätter metoden Object.ToString för att skapa en sträng som unikt identifierar Automobile-instansen i stället för klassen Automobile.
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 bilmärken och bilmodeller. Du behöver till exempel inte definiera en Packard typ för att representera 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, Newspaperoch Article.
Baspublikationsklassen
När du utformar din Publication-klass måste du fatta flera designbeslut:
- Vilka medlemmar som ska ingå i din basklass - Publicationoch om- Publicationmedlemmar tillhandahåller metodimplementeringar eller om- Publicationär en abstrakt basklass som fungerar som en mall för dess härledda klasser.- I det här fallet tillhandahåller klassen - Publicationmetodimplementeringar. 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 medlemmar i - Publicationom deras kod sannolikt kommer att delas av vissa eller de flesta specialiserade- Publicationtyper. 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 - Publicationklassen 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? Till exempel kan - Publicationvara en basklass för- Periodical, som i sin tur är en basklass för- Magazine,- Journaloch- Newspaper.- 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ån- Publication, till exempel- Magazineoch- Article.
- Om det är meningsfullt att instansiera basklassen. Om det inte fungerar bör du använda nyckelordet abstrakt för klassen. Annars kan din - Publication-klass instansieras genom att anropa dess klasskonstruktor. Om ett försök görs att instansiera en klass markerad med nyckelordet- abstractav 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 du klassen - Publicationsom abstrakt så att den inte kan instansieras. En- abstract-klass utan några- abstractmetoder anger att den här klassen representerar ett abstrakt begrepp som delas mellan flera konkreta klasser (till exempel en- Book,- 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 nyckelordet abstrakt för att tvinga härledda klasser att tillhandahålla en implementering. Du använder nyckelordet virtuell för att tillåta härledda klasser att åsidosätta en basklassmetod. Som standard kan metoder som definierats i basklassen inte åsidosättas. - Klassen - Publicationhar inga- abstractmetoder, men själva klassen är- abstract.
- 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 nyckelordet förseglad för att ange att en klass inte kan fungera som basklass för ytterligare klasser. Försök att härleda från en förseglad klass genererar kompilatorfel CS0509, "kan inte härledas från förseglad typ <typeName>." - I ditt exempel markerar du din härledda klass som - sealed.
I följande exempel visas källkoden för klassen Publication samt en PublicationType uppräkning som returneras av egenskapen Publication.PublicationType. Förutom de medlemmar som den ärver från Objectdefinierar klassen Publication följande unika medlemmar och medlemsöverskridningar:
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 - Eftersom klassen - Publicationär- abstractkan 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 för - Book-klassen visar.
- Två publikationsrelaterade egenskaper - Titleär en skrivskyddad String egenskap vars värde anges genom att anropa- Publicationkonstruktorn.- Egenskapen - Pagesär en läsa/skriva Int32 som anger hur många sidor publikationen har. Värdet lagras i ett privat fält med namnet- totalPages. Det måste vara ett positivt tal, annars kastas ett ArgumentOutOfRangeException.
- Utgivarrelaterade medlemmar - Två skrivskyddade egenskaper, - Publisheroch- Type. Värdena tillhandahålls ursprungligen av anropet till- Publication-klasskonstruktorn.
- Publiceringsrelaterade medlemmar - Två metoder, - Publishoch- GetPublicationDate, anger och returnerar publiceringsdatumet. Metoden- Publishanger en privat- publishedflagga till- truenär den anropas och tilldelar det datum som skickades till den som ett argument till fältet privat- datePublished. Metoden- GetPublicationDatereturnerar strängen "NYP" om flaggan- publishedär- falseoch värdet för fältet- datePublishedom det är- true.
- Upphovsrättsrelaterade medlemmar - Metoden - Copyrighttar namnet på upphovsrättsinnehavaren och upphovsrättsinnehavarens år som argument och tilldelar dem till- CopyrightNameoch- CopyrightDateegenskaper.
- En åsidosättning av metoden - ToString- Om en typ inte åsidosätter metoden Object.ToString returneras 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ör egenskapen- Title.
Följande bild illustrerar relationen mellan din basklass Publication och dess implicit ärvda Object-klass.
               
              
            
Klassen Book
Klassen Book representerar en bok som en särskild typ av publikation. I följande exempel visas källkoden för klassen Book.
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 Publicationdefinierar klassen Book följande unika medlemmar och medlemsöverskridningar:
- Två konstruktorer - De två - Bookkonstruktorerna delar tre vanliga parametrar. Två, rubrik och utgivare, motsvarar konstruktorns parametrar- Publication. Den tredje är författare, som lagras i en oföränderlig offentlig- Authoregenskap. En konstruktor inkluderar en isbn parameter, som lagras i- ISBNautoprop.- Den första konstruktorn använder this-nyckelordet för att anropa den andra konstruktorn. Konstruktorlänkning är ett vanligt mönster inom programmering 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 nyckelordet bas för att skicka titeln och förlagsnamnet 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 returnerar det internationella standardboknumret för- Book-objektet, vilket är ett unikt tal med 10 eller 13 siffror. ISBN levereras som ett argument till en av- Bookkonstruktorerna. ISBN lagras i ett privat säkerhetskopieringsfält som genereras automatiskt av kompilatorn.
- En skrivskyddad - Authoregenskap. Författarens namn anges som ett argument till båda- Book-konstruktörerna och lagras i egenskapen.
- Två skrivskyddade prisrelaterade egenskaper, - Priceoch- Currency. Deras värden anges som argument i ett- SetPricemetodanrop. Egenskapen- Currencyä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 i klassen- Book.
- En - SetPricemetod som anger värdena för egenskaperna- Priceoch- Currency. Dessa värden returneras av samma egenskaper.
- Åsidosätter metoden - ToString(ärvd från- Publication) och metoderna Object.Equals(Object) och GetHashCode (ärvda från Object).- Om det inte åsidosätts, testar Object.Equals(Object)-metoden för referensjämlikhet. Det innebär att två objektvariabler anses vara lika med om de refererar till samma objekt. I klassen - Bookska å andra sidan två- Bookobjekt vara lika om de har samma ISBN.- När du åsidosätter metoden Object.Equals(Object) måste du också åsidosätta metoden GetHashCode, som returnerar ett värde som körmiljön använder för att lagra element 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 - trueom ISBN-egenskaperna för två- Bookobjekt är lika returnerar du hash-koden som beräknas genom att anropa metoden GetHashCode för strängen som returneras av egenskapen- ISBN.
Följande bild illustrerar relationen mellan klassen Book 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 följande exempel visar.
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 nyckelordet abstrakt markeras varje instansmedlem också med nyckelordet abstrakt. I det här fallet åsidosätter Shape även metoden Object.ToString 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 runtime den härledda klassens metodens åsidosättning.
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, Rectangleoch 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 Rectangle.Diagonal och Circle.Diameter, som ä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 skapar en array av objekt härledda från Shape och anropar de statiska metoderna i klassen Shape, som inkapslar de returnerade egenskapsvärdena från Shape. Körtiden hämtar värden från de åsidosatta egenskaperna för de härledda typerna. Exemplet omvandlar också varje Shape objekt i matrisen till sin härledda typ och hämtar, om avgjutningen lyckas, egenskaperna för den specifika underklassen för 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