Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa esercitazione presenta l'ereditarietà in C#. L'ereditarietà è una funzionalità dei linguaggi di programmazione orientati agli oggetti che consente di definire una classe base che fornisce funzionalità specifiche (dati e comportamento) e definire classi derivate che ereditano o eseguono l'override di tale funzionalità.
Su Windows, usa questo file di configurazione WinGet per installare tutti i prerequisiti. Se è già installato un elemento, WinGet ignorerà questo passaggio.
- Scaricare il file e fare doppio clic per eseguirlo.
- Leggere il contratto di licenza, digitare ye selezionare Immettere quando viene richiesto di accettare.
- Se viene visualizzato un prompt di controllo dell'account utente lampeggiante nella barra delle applicazioni, consentire all'installazione di continuare.
In altre piattaforme è necessario installare ognuno di questi componenti separatamente.
- Scaricare il programma di installazione consigliato dalla pagina di download di .NET SDK e fare doppio clic per eseguirlo. La pagina di download rileva la piattaforma e consiglia il programma di installazione più recente per la piattaforma.
- Scarica il programma di installazione più recente dalla home page Visual Studio Code e fai doppio clic per eseguirlo. Questa pagina rileva anche la tua piattaforma e il collegamento dovrebbe essere corretto per il tuo sistema.
- Fare clic sul pulsante "Installa" nella pagina dell'estensione C# DevKit. Verrà aperto Visual Studio Code e viene chiesto se si vuole installare o abilitare l'estensione. Seleziona "install".
Per creare ed eseguire gli esempi in questa esercitazione, si utilizza l'utilità dotnet dalla riga di comando. Seguire questa procedura per ogni esempio:
Creare una directory per archiviare l'esempio.
Immettere il comando dotnet new console al prompt dei comandi per creare un nuovo progetto .NET Core.
Copiare e incollare il codice dall'esempio nell'editor di codice.
Immettere il comando dotnet restore dalla riga di comando per caricare o ripristinare le dipendenze del progetto.
Non è necessario eseguire
dotnet restore
perché viene eseguito in modo implicito da tutti i comandi che richiedono un ripristino, ad esempiodotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
edotnet pack
. Per disabilitare il ripristino implicito, usare l'opzione--no-restore
.Il comando
dotnet restore
è ancora utile in alcuni scenari in cui ha senso eseguire un ripristino esplicito, ad esempio le compilazioni di integrazione continua in Azure DevOps Services o in sistemi di compilazione che richiedono il controllo esplicito quando viene eseguito il ripristino.Per informazioni su come gestire i feed NuGet, vedere la
dotnet restore
documentazione.Immettere il comando dotnet run per compilare ed eseguire l'esempio.
l'ereditarietà è uno degli attributi fondamentali della programmazione orientata agli oggetti. Consente di definire una classe figlio che riutilizza (eredita), estende o modifica il comportamento di una classe padre. La classe i cui membri vengono ereditati viene chiamata classe base . La classe che eredita i membri della classe base viene chiamata classe derivata .
C# e .NET supportano solo di ereditarietà singola. Ovvero, una classe può ereditare solo da una singola classe. Tuttavia, l'ereditarietà è transitiva, che consente di definire una gerarchia di ereditarietà per un set di tipi. In altre parole, il tipo D
può ereditare dal tipo C
, che eredita dal tipo B
, che eredita dal tipo di classe base A
. Poiché l'ereditarietà è transitiva, i membri di tipo A
sono accessibili dal tipo D
.
Non tutti i membri di una classe base vengono ereditati dalle classi derivate. I membri seguenti non vengono ereditati:
costruttori statici, che inizializzano i dati statici di una classe.
costruttori di istanza, che vengono chiamati per creare una nuova istanza della classe. Ogni classe deve definire i propri costruttori.
finalizzatori, chiamati dal Garbage Collector del runtime per eliminare definitivamente le istanze di una classe.
Anche se tutti gli altri membri di una classe di base vengono ereditati dalle classi derivate, che siano visibili o meno dipende dall'accessibilità. L'accessibilità di un membro influisce sulla visibilità per le classi derivate come indicato di seguito:
membri privati sono visibili solo nelle classi derivate annidate nella relativa classe di base. In caso contrario, non sono visibili nelle classi derivate. Nell'esempio seguente,
A.B
è una classe annidata che deriva daA
eC
deriva daA
. Il campoA._value
privato è visibile in A.B. Tuttavia, se si rimuovono i commenti dal metodoC.GetValue
e si tenta di compilare l'esempio, genera l'errore del compilatore CS0122: "'A._value' è inaccessibile a causa del relativo livello di protezione".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
membri protected sono visibili solo nelle classi derivate.
I membri interni sono visibili solo nelle classi derivate che si trovano nella stessa assembly della classe di base. Non sono visibili nelle classi derivate che si trovano in un assembly diverso dalla classe di base.
I membri pubblici sono visibili nelle classi derivate e fanno parte dell'interfaccia pubblica delle classi derivate. I membri ereditati pubblici possono essere chiamati esattamente come se siano definiti nella classe derivata. Nell'esempio seguente la classe
A
definisce un metodo denominatoMethod1
e la classeB
eredita dalla classeA
. L'esempio chiama quindiMethod1
come se fosse un metodo di istanza inB
.public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new (); b.Method1(); } }
Le classi derivate possono anche eseguire l'override membri ereditati fornendo un'implementazione alternativa. Per poter eseguire l'override di un membro, il membro nella classe base deve essere contrassegnato con la parola chiave virtuale. Per impostazione predefinita, i membri della classe base non vengono contrassegnati come virtual
e non possono essere sottoposti a override. Il tentativo di eseguire l'override di un membro non virtuale, come nell'esempio seguente, genera l'errore del compilatore CS0506: "<membro> non può eseguire l'override del membro ereditato <membro> perché non è contrassegnato come virtuale, astratto o sottoposto a override".
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
In alcuni casi, una classe derivata deve eseguire l'override dell'implementazione della classe di base. I membri della classe base contrassegnati con la parola chiave astratta richiedono che le classi derivate ne eseggono l'override. Il tentativo di compilare l'esempio seguente genera l'errore del compilatore CS0534, "<classe> non implementa membri astratti ereditati <membro>", perché la classe B
non fornisce alcuna implementazione per A.Method1
.
public abstract class A
{
public abstract void Method1();
}
public class B : A // Generates CS0534.
{
public void Method3()
{
// Do something.
}
}
L'ereditarietà si applica solo a classi e interfacce. Altre categorie di tipi (struct, delegati ed enumerazioni) non supportano l'ereditarietà. A causa di queste regole, il tentativo di compilare codice come l'esempio seguente genera l'errore del compilatore CS0527: "Tipo 'ValueType' nell'elenco di interfacce non è un'interfaccia". Il messaggio di errore indica che, sebbene sia possibile definire le interfacce implementate da uno struct, l'ereditarietà non è supportata.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Oltre a tutti i tipi che possono ereditare da tramite ereditarietà singola, tutti i tipi nel sistema di tipi .NET ereditano in modo implicito da Object o da un tipo derivato da esso. La funzionalità comune di Object è disponibile per ogni tipo.
Per vedere cosa significa l'ereditarietà implicita, definiamo una nuova classe, SimpleClass
, che è semplicemente una definizione di classe vuota:
public class SimpleClass
{ }
È quindi possibile usare la reflection (che consente di esaminare i metadati di un tipo per ottenere informazioni su tale tipo) per ottenere un elenco dei membri che appartengono al tipo di SimpleClass
. Anche se non sono stati definiti membri nella classe SimpleClass
, l'output dell'esempio indica che ha effettivamente nove membri. Uno di questi membri è un costruttore senza parametri (o predefinito) fornito automaticamente per il tipo SimpleClass
dal compilatore C#. Gli otto rimanenti sono membri di Object, il tipo da cui ereditano in modo implicito tutte le classi e le interfacce nel sistema dei tipi .NET.
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
L'ereditarietà implicita dalla classe Object rende disponibili questi metodi alla classe SimpleClass
:
Il metodo pubblico
ToString
, che converte un oggettoSimpleClass
nella relativa rappresentazione di stringa, restituisce il nome completo del tipo. In questo caso, il metodoToString
restituisce la stringa "SimpleClass".Tre metodi che testano l'uguaglianza di due oggetti: il metodo
Equals(Object)
dell'istanza pubblica, il metodoEquals(Object, Object)
statico pubblico e il metodoReferenceEquals(Object, Object)
statico pubblico. Per impostazione predefinita, questi metodi testano l'uguaglianza dei riferimenti; ovvero, per essere uguale, due variabili oggetto devono fare riferimento allo stesso oggetto.Metodo pubblico
GetHashCode
, che calcola un valore che consente l'uso di un'istanza del tipo nelle raccolte con hash.Metodo
GetType
pubblico, che restituisce un oggetto Type che rappresenta il tipo diSimpleClass
.Il metodo Finalize protetto, progettato per rilasciare risorse non gestite prima che la memoria di un oggetto venga recuperata dal Garbage Collector.
Metodo MemberwiseClone protetto, che crea un clone superficiale dell'oggetto corrente.
A causa dell'ereditarietà implicita, è possibile chiamare qualsiasi membro ereditato da un oggetto SimpleClass
come se fosse effettivamente un membro definito nella classe SimpleClass
. L'esempio seguente, ad esempio, chiama il metodo SimpleClass.ToString
, che SimpleClass
eredita da 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
La tabella seguente elenca le categorie di tipi che è possibile creare in C# e i tipi da cui ereditano in modo implicito. Ogni tipo di base rende disponibile un set diverso di membri attraverso l'ereditarietà ai tipi derivati implicitamente.
Categoria di tipi | Eredita in modo implicito da |
---|---|
classe | Object |
struttura | ValueType, Object |
enum | Enum, ValueType, Object |
delegato | MulticastDelegate, Delegate, Object |
In genere, l'ereditarietà viene usata per esprimere una relazione "è " tra una classe di base e una o più classi derivate, in cui le classi derivate sono versioni specializzate della classe base; la classe derivata è un tipo della classe base. Ad esempio, la classe Publication
rappresenta una pubblicazione di qualsiasi tipo e le classi Book
e Magazine
rappresentano tipi specifici di pubblicazioni.
Nota
Una classe o uno struct può implementare una o più interfacce. Sebbene l'implementazione dell'interfaccia venga spesso presentata come soluzione alternativa per l'ereditarietà singola o come metodo di utilizzo dell'ereditarietà con struct, è progettata per esprimere una relazione diversa (una relazione "può fare") tra un'interfaccia e il relativo tipo di implementazione rispetto all'ereditarietà. Un'interfaccia definisce un sottoinsieme di funzionalità, ad esempio la possibilità di verificare l'uguaglianza, confrontare o ordinare oggetti, o supportare l'analisi e la formattazione sensibili alle impostazioni culturali, che l'interfaccia mette a disposizione dei tipi che la implementano.
Si noti che esprime anche la relazione tra un tipo e un'istanza specifica di tale tipo. Nell'esempio seguente Automobile
è una classe con tre proprietà di sola lettura univoche: Make
, il produttore dell'automobile; Model
, il tipo di automobile; e Year
, il suo anno di produzione. La classe Automobile
ha anche un costruttore i cui argomenti sono assegnati ai valori della proprietà ed esegue l'override del metodo Object.ToString per produrre una stringa che identifichi in modo univoco l'istanza di Automobile
anziché la classe 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}";
}
In questo caso, non dovresti basarti sull'ereditarietà per rappresentare marche e modelli specifici di auto. Ad esempio, non è necessario definire un tipo Packard
per rappresentare le automobili prodotte dalla Packard Motor Car Company. È invece possibile rappresentarli creando un oggetto Automobile
con i valori appropriati passati al relativo costruttore di classe, come illustrato nell'esempio seguente.
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
Una relazione basata sull'ereditarietà viene applicata in modo ottimale a una classe di base e alle classi derivate che aggiungono membri aggiuntivi alla classe base o che richiedono funzionalità aggiuntive non presenti nella classe base.
Si esaminerà ora il processo di progettazione di una classe di base e delle relative classi derivate. In questa sezione si definirà una classe base, Publication
, che rappresenta una pubblicazione di qualsiasi tipo, ad esempio un libro, una rivista, un giornale, un giornale, un giornale, un articolo e così via. Si definirà anche una classe Book
che deriva da Publication
. È possibile estendere facilmente l'esempio per definire altre classi derivate, ad esempio Magazine
, Journal
, Newspaper
e Article
.
Nella progettazione della classe Publication
è necessario prendere diverse decisioni di progettazione:
Quali membri includere nella classe di base
Publication
e se i membriPublication
forniscono implementazioni del metodo o sePublication
è una classe base astratta che funge da modello per le relative classi derivate.In questo caso, la classe
Publication
fornirà implementazioni del metodo. La sezione Progettazione di classi base astratte e delle relative classi derivate contiene un esempio che usa una classe base astratta per definire i metodi di cui devono eseguire l'override le classi derivate. Le classi derivate sono libere di fornire qualsiasi implementazione adatta al tipo derivato.La possibilità di riutilizzare il codice ( ovvero più classi derivate condividono la dichiarazione e l'implementazione dei metodi della classe di base e non devono eseguirne l'override) è un vantaggio delle classi di base non astratte. Pertanto, è consigliabile aggiungere membri a
Publication
se è probabile che il codice venga condiviso da alcuni o più tipi diPublication
specializzati. Se non si forniscono implementazioni di classi di base in modo efficiente, si finisce per fornire implementazioni membro in gran parte identiche nelle classi derivate piuttosto una singola implementazione nella classe base. La necessità di mantenere il codice duplicato in più posizioni è una potenziale origine di bug.Sia per ottimizzare il riutilizzo del codice che per creare una gerarchia di ereditarietà logica e intuitiva, è necessario assicurarsi di includere nella classe
Publication
solo i dati e le funzionalità comuni a tutte o alla maggior parte delle pubblicazioni. Le classi derivate implementano quindi membri univoci per i tipi specifici di pubblicazione rappresentati.Quanto estendere la gerarchia delle classi. Si vuole sviluppare una gerarchia di tre o più classi, anziché semplicemente una classe di base e una o più classi derivate? Ad esempio,
Publication
potrebbe essere una classe base diPeriodical
, che a sua volta è una classe base diMagazine
,Journal
eNewspaper
.Ad esempio, si userà la piccola gerarchia di una classe
Publication
e una singola classe derivata,Book
. È possibile estendere facilmente l'esempio per creare una serie di classi aggiuntive che derivano daPublication
, ad esempioMagazine
eArticle
.Indica se è opportuno creare un'istanza della classe di base. In caso contrario, è necessario applicare la parola chiave astratta alla classe. In caso contrario, è possibile creare un'istanza della classe
Publication
chiamando il relativo costruttore di classe. Se si tenta di creare un'istanza di una classe contrassegnata con la parola chiaveabstract
da una chiamata diretta al relativo costruttore di classe, il compilatore C# genera l'errore CS0144, "Cannot create an instance of the abstract class or interface". Se si tenta di creare un'istanza della classe usando la reflection, il metodo di reflection genera un MemberAccessException.Per impostazione predefinita, è possibile creare un'istanza di una classe base chiamando il relativo costruttore di classe. Non è necessario definire in modo esplicito un costruttore di classe. Se non è presente nel codice sorgente della classe di base, il compilatore C# fornisce automaticamente un costruttore predefinito (senza parametri).
Per esempio, si contrassegnerà la classe
Publication
come astratta in modo tale che non possa essere creata un'istanza. Una classeabstract
senza metodiabstract
indica che questa classe rappresenta un concetto astratto condiviso tra diverse classi concrete (ad esempio unBook
,Journal
).Se le classi derivate devono ereditare l'implementazione della classe base di determinati membri, indipendentemente dal fatto che abbiano la possibilità di eseguire l'override dell'implementazione della classe di base o se devono fornire un'implementazione. Si utilizza la parola chiave astratta per forzare le classi derivate a fornire un'implementazione. Usare la parola chiave virtuale per consentire alle classi derivate di eseguire l'override di un metodo della classe base. Per impostazione predefinita, i metodi definiti nella classe base non sono sostituibili.
La classe
Publication
non dispone di metodiabstract
, ma la classe stessa èabstract
.Indica se una classe derivata rappresenta la classe finale nella gerarchia di ereditarietà e non può essere utilizzata come classe di base per classi derivate aggiuntive. Per impostazione predefinita, qualsiasi classe può fungere da classe base. È possibile applicare la parola chiave sealed per indicare che una classe non può fungere da classe di base per altre classi. Tentativo di derivare da una classe sealed ha generato l'errore del compilatore CS0509, "impossibile derivare dal tipo sealed <typeName>".
Ad esempio, si contrassegnerà la classe derivata come
sealed
.
Nell'esempio seguente viene illustrato il codice sorgente per la classe Publication
, nonché un'enumerazione PublicationType
restituita dalla proprietà Publication.PublicationType
. Oltre ai membri che eredita da Object, la classe Publication
definisce i seguenti membri univoci e gli override dei membri:
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;
}
Costruttore
Poiché la classe
Publication
èabstract
, non è possibile creare un'istanza direttamente dal codice come nell'esempio seguente:var publication = new Publication("Tiddlywinks for Experts", "Fun and Games", PublicationType.Book);
Tuttavia, il costruttore dell'istanza può essere chiamato direttamente dai costruttori di classi derivate, come illustrato dal codice sorgente per la classe
Book
.Due proprietà correlate alla pubblicazione
Title
è una proprietà di sola lettura String il cui valore viene fornito chiamando il costruttorePublication
.Pages
è una proprietà di Int32 di lettura/scrittura che indica il numero di pagine totali della pubblicazione. Il valore viene archiviato in un campo privato denominatototalPages
. Deve essere un numero positivo o viene generata una ArgumentOutOfRangeException.Membri correlati all'editore
Due proprietà di sola lettura,
Publisher
eType
. I valori vengono originariamente forniti dalla chiamata al costruttore della classePublication
.Membri legati alla pubblicazione
Due metodi,
Publish
eGetPublicationDate
, impostare e restituire la data di pubblicazione. Il metodoPublish
imposta un flagpublished
privato sutrue
quando viene chiamato e assegna la data passata come argomento al campodatePublished
privato. Il metodoGetPublicationDate
restituisce la stringa "NYP" se il flagpublished
èfalse
e il valore del campodatePublished
se ètrue
.Membri correlati al copyright
Il metodo
Copyright
accetta il nome del titolare del copyright e l'anno del copyright come argomenti e li assegna alle proprietàCopyrightName
eCopyrightDate
.Sovrascrittura del metodo
ToString
Se un tipo non esegue l'override del metodo Object.ToString, restituisce il nome completo del tipo, che è di poco utile per differenziare un'istanza da un'altra. La classe
Publication
esegue l'override di Object.ToString per restituire il valore della proprietàTitle
.
La figura seguente illustra la relazione tra la classe Publication
di base e la classe Object ereditata in modo implicito.
La classe Book
rappresenta un libro come tipo specializzato di pubblicazione. Nell'esempio seguente viene illustrato il codice sorgente per la classe 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}";
}
Oltre ai membri che eredita da Publication
, la classe Book
definisce i seguenti membri unici e override dei membri:
Due costruttori
I due costruttori
Book
condividono tre parametri comuni. Due parametri, titolo e editore, corrispondono al costruttorePublication
. Il terzo è autore, archiviato in una proprietàAuthor
pubblica non modificabile. Un costruttore include un parametro isbn, memorizzato nella proprietà automaticaISBN
.Il primo costruttore usa la parola chiave this per richiamare l'altro costruttore. Il concatenamento dei costruttori è un modello comune nella definizione dei costruttori. I costruttori con un minor numero di parametri forniscono valori predefiniti quando si chiama il costruttore con il maggior numero di parametri.
Il secondo costruttore usa la parola chiave base per passare il titolo e il nome dell'editore al costruttore della classe base. Se non si effettua una chiamata esplicita a un costruttore della classe di base nel codice sorgente, il compilatore C# fornisce automaticamente una chiamata al costruttore predefinito o senza parametri della classe di base.
Proprietà di
ISBN
di sola lettura, che restituisce il numero di libro standard internazionale dell'oggettoBook
, un numero univoco di 10 o 13 cifre. Il codice ISBN viene fornito come argomento a uno dei costruttori diBook
. Il codice ISBN viene archiviato in un campo sottostante privato, generato automaticamente dal compilatore.Proprietà di
Author
di sola lettura. Il nome dell'autore viene fornito come argomento ai costruttoriBook
e viene memorizzato nella proprietà.Due proprietà correlate al prezzo di sola lettura,
Price
eCurrency
. I valori vengono forniti come argomenti in una chiamata al metodoSetPrice
. La proprietàCurrency
è il simbolo di valuta ISO a tre cifre, ad esempio USD per il dollaro statunitense. I simboli di valuta ISO possono essere recuperati dalla proprietà ISOCurrencySymbol. Entrambe queste proprietà sono di sola lettura esterna, ma entrambe possono essere impostate dal codice nella classeBook
.Metodo
SetPrice
, che imposta i valori delle proprietàPrice
eCurrency
. Quei valori vengono restituiti da quelle stesse proprietà.Esegue l'override del metodo
ToString
(ereditato daPublication
) e dei metodi Object.Equals(Object) e GetHashCode (ereditati da Object).A meno che non sia sottoposto a override, il metodo Object.Equals(Object) verifica l'uguaglianza dei riferimenti. Ovvero, due variabili oggetto vengono considerate uguali se fanno riferimento allo stesso oggetto. Nella classe
Book
, invece, due oggettiBook
devono essere uguali se hanno lo stesso ISBN.Quando si esegue l'override del metodo Object.Equals(Object), è inoltre necessario eseguire l'override del metodo GetHashCode, che restituisce un valore usato dal runtime per archiviare gli elementi nelle raccolte con hash per un recupero efficiente. Il codice hash deve restituire un valore coerente con il test per verificarne l'uguaglianza. Poiché hai sottoposto a override Object.Equals(Object) per restituire
true
se le proprietà ISBN di due oggettiBook
sono uguali, restituisci il codice hash calcolato chiamando il metodo GetHashCode sulla stringa restituita dalla proprietàISBN
.
Nella figura seguente viene illustrata la relazione tra la classe Book
e Publication
, la relativa classe di base.
È ora possibile creare un'istanza di un oggetto Book
, richiamarne i membri univoci e ereditati e passarlo come argomento a un metodo che prevede un parametro di tipo Publication
o di tipo Book
, come illustrato nell'esempio seguente.
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
Nell'esempio precedente è stata definita una classe base che ha fornito un'implementazione per diversi metodi per consentire alle classi derivate di condividere il codice. In molti casi, tuttavia, la classe base non deve fornire un'implementazione. La classe base è invece una classe astratta che dichiara metodi astratti; funge da modello che definisce i membri che ogni classe derivata deve implementare. In genere in una classe base astratta, l'implementazione di ogni tipo derivato è univoca per tale tipo. Hai contrassegnato la classe con la parola chiave abstract perché non avrebbe avuto senso creare un'istanza di un oggetto Publication
, anche se la classe ha fornito implementazioni di funzionalità comuni alle pubblicazioni.
Ad esempio, ogni forma geometrica bidimensionale chiusa include due proprietà: area, l'estensione interna della forma; e perimetrale, o la distanza lungo i bordi della forma. Il modo in cui queste proprietà vengono calcolate, tuttavia, dipende completamente dalla forma specifica. La formula per calcolare il perimetro (o circonferenza) di un cerchio, ad esempio, è diversa da quella di un quadrato. La classe Shape
è una classe abstract
con metodi abstract
. Ciò indica che le classi derivate condividono la stessa funzionalità, ma le classi derivate implementano tale funzionalità in modo diverso.
Nell'esempio seguente viene definita una classe base astratta denominata Shape
che definisce due proprietà: Area
e Perimeter
. Oltre a contrassegnare la classe con la parola chiave astratta, ogni membro dell'istanza viene contrassegnato anche con la parola chiave astratta. In questo caso, Shape
esegue anche l'override del metodo Object.ToString per restituire il nome del tipo, anziché il nome completo. Definisce inoltre due membri statici, GetArea
e GetPerimeter
, che consentono ai chiamanti di recuperare facilmente l'area e il perimetro di un'istanza di qualsiasi classe derivata. Quando si passa un'istanza di una classe derivata a uno di questi metodi, l'override del metodo della classe derivata viene chiamato dal runtime.
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;
}
È quindi possibile derivare alcune classi da Shape
che rappresentano forme specifiche. Nell'esempio seguente vengono definite tre classi, Square
, Rectangle
e Circle
. Ognuna usa una formula univoca per la forma specifica per calcolare l'area e il perimetro. Alcune classi derivate definiscono anche proprietà, ad esempio Rectangle.Diagonal
e Circle.Diameter
, univoche per la forma che rappresentano.
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;
}
Nell'esempio seguente vengono utilizzati oggetti derivati da Shape
. Crea un'istanza di una matrice di oggetti derivati da Shape
e chiama i metodi statici della classe Shape
, che incapsula il ritorno dei valori delle proprietà Shape
. Il runtime recupera i valori dalle proprietà sottoposte a override dei tipi derivati. Nell'esempio viene inoltre eseguito il cast di ogni oggetto Shape
nella matrice al tipo derivato e, se il cast ha esito positivo, recupera le proprietà di tale particolare sottoclasse di 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
Feedback su .NET
.NET è un progetto di open source. Selezionare un collegamento per fornire feedback: