Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Ce didacticiel vous présente l’héritage dans C#. L’héritage est une fonctionnalité des langages de programmation orientés objet qui vous permet de définir une classe de base qui fournit des fonctionnalités spécifiques (données et comportement) et de définir des classes dérivées qui héritent ou remplacent cette fonctionnalité.
Conditions préalables
- La dernière version du SDK .NET
- Éditeur de code Visual Studio
- Le DevKit C#
Instructions d’installation
Sous Windows, ce fichier de configuration WinGet permet d'installer tous les prérequis. Si vous avez déjà installé quelque chose, WinGet ignore cette étape.
- Téléchargez le fichier et double-cliquez pour l’exécuter.
- Lisez le contrat de licence, tapez et, puis sélectionnez Entrer lorsque vous êtes invité à accepter.
- Si vous recevez une invite de contrôle de compte d’utilisateur (UAC) qui clignote dans votre barre des tâches, autorisez la poursuite de l'installation.
Sur d’autres plateformes, vous devez installer chacun de ces composants séparément.
- Téléchargez le programme d’installation recommandé à partir de la page de téléchargement du Kit de développement logiciel (SDK) .NET et double-cliquez pour l’exécuter. La page de téléchargement détecte votre plateforme et recommande le dernier programme d’installation de votre plateforme.
- Téléchargez le dernier programme d’installation à partir de la page d’accueil Visual Studio Code, puis double-cliquez pour l’exécuter. Cette page détecte également votre plateforme et le lien doit être correct pour votre système.
- Cliquez sur le bouton « Installer » dans la page d’extension devKit C#. Cela ouvre Visual Studio Code et vous demande si vous souhaitez installer ou activer l’extension. Sélectionnez « installer ».
Exécution des exemples
Pour créer et exécuter les exemples de ce didacticiel, vous utilisez l’utilitaire dotnet à partir de la ligne de commande. Procédez comme suit pour chaque exemple :
Créez un répertoire pour stocker l’exemple.
Entrez la commande dotnet new console dans l'invite de commande pour créer un projet .NET Core.
Copiez et collez le code de l’exemple dans votre éditeur de code.
Entrez la commande dotnet restore à partir de la ligne de commande pour charger ou restaurer les dépendances du projet.
Vous n’avez pas besoin d’exécuter
dotnet restore
, car il est exécuté implicitement par toutes les commandes qui nécessitent une restauration pour se produire, commedotnet new
,dotnet build
,dotnet run
,dotnet test
,dotnet publish
etdotnet pack
. Pour désactiver la restauration implicite, utilisez l’option--no-restore
.La commande
dotnet restore
est toujours utile dans certains scénarios où la restauration explicite est logique, comme les builds d’intégration continue dans Azure DevOps Services ou dans les systèmes de génération qui doivent contrôler explicitement le moment où la restauration se produit.Pour plus d’informations sur la gestion des flux NuGet, consultez la documentation
dotnet restore
.Entrez la commande dotnet run pour compiler et exécuter l’exemple.
Arrière-plan : Qu’est-ce que l’héritage ?
L’héritage est un des attributs fondamentaux de la programmation orientée objet. Il vous permet de définir une classe enfant qui réutilise (hérite), étend ou modifie le comportement d’une classe parente. La classe dont les membres sont hérités s’appelle la classe de base. La classe qui hérite des membres de la classe de base est appelée classe dérivée.
C# et .NET prennent uniquement en charge l’héritage simple. Autrement dit, une classe ne peut hériter qu’d’une seule classe. Toutefois, l’héritage est transitif, ce qui vous permet de définir une hiérarchie d’héritage pour un ensemble de types. En d’autres termes, le type D
peut hériter du type C
, qui hérite du type B
, qui hérite du type de classe de base A
. Étant donné que l’héritage est transitif, les membres de type A
sont disponibles pour le type D
.
Tous les membres d’une classe de base ne sont pas hérités par des classes dérivées. Les membres suivants ne sont pas hérités :
constructeurs statiques, qui initialisent les données statiques d’une classe.
Les constructeurs d’instance, que vous appelez pour créer une nouvelle instance de la classe. Chaque classe doit définir ses propres constructeurs.
Les finaliseurs, qui sont appelés par le récupérateur de mémoire du runtime pour détruire les instances d’une classe.
Bien que tous les autres membres d’une classe de base soient hérités par des classes dérivées, qu’ils soient visibles ou non dépendent de leur accessibilité. L’accessibilité d’un membre affecte sa visibilité pour les classes dérivées comme suit :
Les membres privés sont visibles uniquement dans les classes dérivées qui sont imbriquées dans leur classe de base. Sinon, elles ne sont pas visibles dans les classes dérivées. Dans l’exemple suivant,
A.B
est une classe imbriquée qui dérive deA
, etC
dérive deA
. Le champ deA._value
privé est visible dans A.B. Toutefois, si vous supprimez les commentaires de la méthodeC.GetValue
et tentez de compiler l’exemple, il génère l’erreur du compilateur CS0122 : « ''A._value' est inaccessible en raison de son niveau de protection. »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
Les membres protégés sont visibles uniquement dans les classes dérivées.
Les membres internes sont visibles uniquement dans les classes dérivées qui sont trouvent dans le même assembly que la classe de base. Ils ne sont pas visibles dans les classes dérivées situées dans un assembly différent de la classe de base.
Les membres publics sont visibles dans les classes dérivées et font partie de l’interface publique de la classe dérivée. Les membres publics hérités peuvent être appelés comme s’ils étaient définis dans la classe dérivée. Dans l’exemple suivant, la classe
A
définit une méthode nomméeMethod1
, et la classeB
hérite de la classeA
. L’exemple appelle ensuiteMethod1
comme s’il s’agissait d’une méthode d’instance surB
.public class A { public void Method1() { // Method implementation. } } public class B : A { } public class Example { public static void Main() { B b = new (); b.Method1(); } }
Les classes dérivées peuvent également substituer les membres hérités en fournissant une implémentation alternative. Pour pouvoir remplacer un membre, le membre de la classe de base doit être marqué avec le mot clé virtuel. Par défaut, les membres de classe de base ne sont pas marqués comme virtual
et ne peuvent pas être substitués. La tentative de remplacement d’un membre non virtuel, comme l’exemple suivant, génère l’erreur du compilateur CS0506 : «<membre> ne peut pas remplacer le membre hérité <membre>, car il n’est pas marqué comme virtuel, abstrait ou substitué ».
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
Dans certains cas, une classe dérivée doit remplacer l’implémentation de classe de base. Les membres de classe de base marqués avec le mot clé abstrait nécessitent que les classes dérivées les remplacent. La tentative de compilation de l’exemple suivant génère l’erreur du compilateur CS0534 , «<classe> n’implémente pas le membre abstrait hérité <membre>», car la classe B
ne fournit aucune implémentation pour A.Method1
.
public abstract class A
{
public abstract void Method1();
}
public class B : A // Generates CS0534.
{
public void Method3()
{
// Do something.
}
}
L’héritage s’applique uniquement aux classes et interfaces. Les autres catégories de type (structures, délégués et énumérations) ne permettent pas l’héritage. En raison de ces règles, la tentative de compilation du code comme l’exemple suivant génère l’erreur du compilateur CS0527 : « Type « ValueType » dans la liste d’interface n’est pas une interface. Le message d’erreur indique que, bien que vous puissiez définir les interfaces qu’un struct implémente, l’héritage n’est pas pris en charge.
public struct ValueStructure : ValueType // Generates CS0527.
{
}
Héritage implicite
Outre tous les types qu’ils peuvent hériter par le biais d’un héritage unique, tous les types du système de type .NET héritent implicitement de Object ou d’un type dérivé de celui-ci. Les fonctionnalités courantes de Object sont disponibles pour n’importe quel type.
Pour voir ce que signifie l’héritage implicite, nous allons définir une nouvelle classe, SimpleClass
, qui est simplement une définition de classe vide :
public class SimpleClass
{ }
Vous pouvez ensuite utiliser la réflexion (qui vous permet d’inspecter les métadonnées d’un type pour obtenir des informations sur ce type) pour obtenir une liste des membres qui appartiennent au type SimpleClass
. Bien que vous n’ayez défini aucun membre dans votre classe SimpleClass
, la sortie de l’exemple indique qu’elle comporte en fait neuf membres. L’un de ces membres est un constructeur sans paramètre (ou par défaut) qui est automatiquement fourni pour le type SimpleClass
par le compilateur C#. Les huit restants sont membres de Object, le type à partir duquel toutes les classes et interfaces du système de type .NET héritent implicitement.
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’héritage implicite de la classe Object met ces méthodes à la disposition de la classe SimpleClass
:
La méthode
ToString
publique, qui convertit un objetSimpleClass
en sa représentation sous forme de chaîne, retourne le nom de type complet. Dans ce cas, la méthodeToString
retourne la chaîne « SimpleClass ».Trois méthodes qui testent l’égalité de deux objets : l’instance publique
Equals(Object)
méthode, la méthodeEquals(Object, Object)
statique publique et la méthodeReferenceEquals(Object, Object)
statique publique. Par défaut, ces méthodes testent l’égalité des références ; autrement dit, pour être égale, deux variables d’objet doivent faire référence au même objet.Méthode de
GetHashCode
publique, qui calcule une valeur qui permet à une instance du type d’être utilisée dans les collections hachées.Méthode
GetType
publique, qui retourne un objet Type qui représente le typeSimpleClass
.La méthode protégée Finalize, conçue pour libérer les ressources non gérées avant que la mémoire d'un objet ne soit récupérée par le ramasse-miettes.
Méthode MemberwiseClone protégée, qui crée un clone peu profond de l’objet actuel.
En raison de l’héritage implicite, vous pouvez appeler n’importe quel membre hérité d’un objet SimpleClass
comme s’il s’agissait réellement d’un membre défini dans la classe SimpleClass
. Par exemple, l’exemple suivant appelle la méthode SimpleClass.ToString
, qui SimpleClass
hérite de 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
Le tableau suivant répertorie les catégories de types que vous pouvez créer en C# et les types à partir desquels ils héritent implicitement. Chaque type de base apporte un autre ensemble de membres disponibles via l’héritage aux types dérivés implicitement.
Catégorie de type | Hérite implicitement de |
---|---|
classe | Object |
Struct | ValueType, Object |
énumération | Enum, ValueType, Object |
déléguer | MulticastDelegate, Delegate, Object |
L’héritage et une relation « est un »
En règle générale, l’héritage est utilisé pour exprimer une relation « is a » entre une classe de base et une ou plusieurs classes dérivées, où les classes dérivées sont des versions spécialisées de la classe de base ; la classe dérivée est un type de la classe de base. Par exemple, la classe Publication
représente une publication de n’importe quel type, et les classes Book
et Magazine
représentent des types spécifiques de publications.
Remarque
Une classe ou un struct peut implémenter une ou plusieurs interfaces. Bien que l’implémentation d’interface est souvent présentée comme une solution de contournement pour l’héritage unique ou comme une façon d’utiliser l’héritage avec les structures, elle est conçue pour exprimer une autre relation (« peut faire ») entre une interface et son type d’implémentation que l’héritage. Une interface définit un sous-ensemble de fonctionnalités (telles que la possibilité de tester l’égalité, de comparer ou de trier des objets, ou de prendre en charge l’analyse et la mise en forme sensibles à la culture) que l’interface met à la disposition de ses types d’implémentation.
Notez que « est un » exprime également la relation entre un type et une instance spécifique de ce type. Dans l’exemple suivant, Automobile
est une classe qui a trois propriétés en lecture seule uniques : Make
, le fabricant de l’automobile ; Model
, le genre d’automobile ; et Year
, son année de fabrication. Votre classe Automobile
a également un constructeur dont les arguments sont affectés aux valeurs de propriété et remplace la méthode Object.ToString pour produire une chaîne qui identifie de manière unique l’instance Automobile
plutôt que 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}";
}
Dans ce cas, vous ne devez pas compter sur l’héritage pour représenter des modèles et des voitures spécifiques. Par exemple, vous n’avez pas besoin de définir un type de Packard
pour représenter les automobiles fabriquées par packard Motor Car Company. Au lieu de cela, vous pouvez les représenter en créant un objet Automobile
avec les valeurs appropriées passées à son constructeur de classe, comme dans l’exemple suivant.
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
Une relation is-a basée sur l’héritage est mieux appliquée à une classe de base et aux classes dérivées qui ajoutent des membres supplémentaires à la classe de base ou qui nécessitent des fonctionnalités supplémentaires qui ne sont pas présentes dans la classe de base.
Conception de la classe de base et des classes dérivées
Examinons le processus de conception d’une classe de base et de ses classes dérivées. Dans cette section, vous allez définir une classe de base, Publication
, qui représente une publication de tout type, comme un livre, un magazine, un journal, un journal, un article, etc. Vous allez également définir une classe Book
qui dérive de Publication
. Vous pouvez facilement étendre l’exemple pour définir d’autres classes dérivées, telles que Magazine
, Journal
, Newspaper
et Article
.
Classe Publication de base
Dans la conception de votre classe Publication
, vous devez prendre plusieurs décisions de conception :
Quels membres inclure dans votre classe de base
Publication
et si les membresPublication
fournissent des implémentations de méthode ou siPublication
est une classe de base abstraite qui sert de modèle pour ses classes dérivées.Dans ce cas, la classe
Publication
fournit des implémentations de méthode. La Conception de classes de base abstraites et de leurs classes dérivées section contient un exemple qui utilise une classe de base abstraite pour définir les méthodes que les classes dérivées doivent remplacer. Les classes dérivées sont libres de fournir toute implémentation adaptée au type dérivé.La possibilité de réutiliser du code (autrement dit, plusieurs classes dérivées partagent la déclaration et l’implémentation des méthodes de classe de base et n’ont pas besoin de les remplacer) est un avantage des classes de base non abstraites. Par conséquent, vous devez ajouter des membres à
Publication
si leur code est susceptible d’être partagé par certains ou la plupart des typesPublication
spécialisés. Si vous ne parvenez pas à fournir efficacement des implémentations de classe de base, vous devrez fournir des implémentations membres largement identiques dans des classes dérivées plutôt qu’une seule implémentation dans la classe de base. La nécessité de maintenir du code en double dans plusieurs emplacements est une source potentielle de bogues.Pour optimiser la réutilisation du code et pour créer une hiérarchie d’héritage logique et intuitive, vous souhaitez être sûr que vous incluez dans la classe
Publication
uniquement les données et les fonctionnalités communes à toutes ou à la plupart des publications. Les classes dérivées implémentent ensuite des membres uniques aux types particuliers de publication qu’ils représentent.Jusqu’à quel point étendre votre hiérarchie de classes. Voulez-vous développer une hiérarchie de trois classes ou plus, plutôt que simplement une classe de base et une ou plusieurs classes dérivées ? Par exemple,
Publication
peut être une classe de base dePeriodical
, qui à son tour est une classe de base deMagazine
,Journal
etNewspaper
.Pour votre exemple, vous allez utiliser la petite hiérarchie d’une classe
Publication
et d’une classe dérivée unique,Book
. Vous pouvez facilement étendre l’exemple pour créer un certain nombre de classes supplémentaires qui dérivent dePublication
, telles queMagazine
etArticle
.Indique s’il est judicieux d’instancier la classe de base. Si ce n’est pas le cas, vous devez appliquer le mot-clé abstract à la classe. Sinon, votre classe
Publication
peut être instanciée en appelant son constructeur de classe. Si vous tentez d'instancier une classe marquée avec le mot cléabstract
par un appel direct à son constructeur, le compilateur C# génère l’erreur CS0144, « Impossible de créer une instance de la classe ou de l’interface abstraite ». Si vous tentez d'instancier la classe à l’aide de la réflexion, la méthode de réflexion lève une MemberAccessException.Par défaut, une classe de base peut être instanciée en appelant son constructeur de classe. Vous n’avez pas besoin de définir explicitement un constructeur de classe. Si l’un n’est pas présent dans le code source de la classe de base, le compilateur C# fournit automatiquement un constructeur par défaut (sans paramètre).
Pour votre exemple, vous marquez la classe
Publication
comme abstraite afin qu’elle ne puisse pas être instanciée. Une classeabstract
sans méthodesabstract
indique que cette classe représente un concept abstrait partagé entre plusieurs classes concrètes (comme unBook
,Journal
).Si les classes dérivées doivent hériter de l’implémentation de classe de base de membres particuliers, qu’elles aient la possibilité de remplacer l’implémentation de classe de base ou si elles doivent fournir une implémentation. Vous utilisez le mot clé abstrait pour forcer les classes dérivées à fournir une implémentation. Vous utilisez le mot clé virtuel pour permettre aux classes dérivées de remplacer une méthode de classe de base. Par défaut, les méthodes définies dans la classe de base ne sont pas substituables.
La classe
Publication
n’a pas de méthodesabstract
, mais la classe elle-même estabstract
.Indique si une classe dérivée représente la classe finale dans la hiérarchie d’héritage et ne peut pas être utilisée en tant que classe de base pour des classes dérivées supplémentaires. Par défaut, n’importe quelle classe peut servir de classe de base. Vous pouvez appliquer le mot clé scellé pour indiquer qu’une classe ne peut pas servir de classe de base pour toutes les classes supplémentaires. Tentative de dériver d'une classe scellée a entraîné l'erreur du compilateur CS0509 : "ne peut pas dériver du type scellé <typeName>".
Pour votre exemple, vous marquez votre classe dérivée comme
sealed
.
L’exemple suivant montre le code source de la classe Publication
, ainsi qu’une énumération PublicationType
retournée par la propriété Publication.PublicationType
. Outre les membres qu’elle hérite de Object, la classe Publication
définit les membres uniques et substitutions de membres suivants :
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;
}
Constructeur
Étant donné que la classe
Publication
estabstract
, elle ne peut pas être instanciée directement à partir du code comme dans l’exemple suivant :var publication = new Publication("Tiddlywinks for Experts", "Fun and Games", PublicationType.Book);
Toutefois, son constructeur d’instance peut être appelé directement à partir de constructeurs de classes dérivées, comme le code source de la classe
Book
montre.Deux propriétés liées à la publication
Title
est une propriété String en lecture seule dont la valeur est fournie en appelant le constructeurPublication
.Pages
est une propriété Int32 en lecture-écriture qui indique le nombre total de pages de la composition. La valeur est stockée dans un champ privé nommétotalPages
. Elle doit être positive, sans quoi une exception ArgumentOutOfRangeException est levée.Membres liés à l’éditeur
Deux propriétés en lecture seule,
Publisher
etType
. Les valeurs sont initialement fournies par l’appel au constructeur de classePublication
.Membres liés à la publication
Deux méthodes,
Publish
etGetPublicationDate
, définissent et retournent la date de publication. La méthodePublish
définit l’indicateur privépublished
surtrue
lorsqu’elle est appelée et affecte la date passée comme argument au champ privédatePublished
. La méthodeGetPublicationDate
retourne la chaîne « NYP » si l’indicateur depublished
estfalse
et la valeur du champdatePublished
s’il esttrue
.Membres liés au droit d’auteur
La méthode
Copyright
prend le nom du titulaire du droit d’auteur et l’année du droit d’auteur comme arguments et les assigne aux propriétésCopyrightName
etCopyrightDate
.Une substitution de la méthode
ToString
Si un type ne remplace pas la méthode Object.ToString, il retourne le nom complet du type, qui est peu utilisé pour différencier une instance d’une autre. La classe
Publication
remplace Object.ToString pour retourner la valeur de la propriétéTitle
.
La figure suivante illustre la relation entre votre classe de base Publication
et sa classe Object héritée implicitement.
La classe Book
La classe Book
représente un livre en tant que type de publication spécialisé. L’exemple suivant montre le code source de 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}";
}
Outre les membres qu’elle hérite de Publication
, la classe Book
définit les membres uniques et substitutions de membres suivants :
Deux constructeurs
Les deux constructeurs
Book
partagent trois paramètres communs. Deux d’entre eux, title et publisher, correspondent aux paramètres du constructeurPublication
. Le troisième est author, qui est stocké dans une propriétéAuthor
non modifiable publique. Un constructeur inclut un paramètre isbn, qui est stocké dans l’auto-propriétéISBN
.Le premier constructeur utilise le mot-clé this pour appeler l’autre constructeur. Le chaînage de constructeurs est un modèle courant pour la définition de constructeurs. Les constructeurs avec moins de paramètres fournissent des valeurs par défaut lors de l’appel du constructeur avec le plus grand nombre de paramètres.
Le deuxième constructeur utilise le mot-clé base pour transmettre le titre et le nom de l’éditeur au constructeur de classe de base. Si vous n’effectuez pas d’appel explicite à un constructeur de classe de base dans votre code source, le compilateur C# fournit automatiquement un appel au constructeur par défaut ou sans paramètre de la classe de base.
Propriété
ISBN
en lecture seule, qui renvoie le numéro de livre standard international de l’objetBook
, un nombre unique à 10 ou 13 chiffres. Le numéro ISBN est fourni en tant qu’argument à un des constructeursBook
. L’ISBN est stocké dans un champ de stockage privé, qui est généré automatiquement par le compilateur.Une propriété
Author
en lecture seule. Le nom de l’auteur est fourni en tant qu’argument aux deux constructeursBook
et est stocké dans la propriété.Deux propriétés en lecture seule relatives au prix,
Price
etCurrency
. Leurs valeurs sont fournies en tant qu’arguments dans un appel de méthodeSetPrice
. La propriétéCurrency
est le symbole monétaire ISO à trois chiffres (par exemple, USD pour le dollar américain). Les symboles de devise ISO peuvent être récupérés à partir de la propriété ISOCurrencySymbol. Ces deux propriétés sont en lecture seule externe, mais les deux peuvent être définies par code dans la classeBook
.Méthode
SetPrice
, qui définit les valeurs des propriétésPrice
etCurrency
. Ces valeurs sont retournées par ces mêmes propriétés.Remplace la méthode
ToString
(héritée dePublication
) et les méthodes Object.Equals(Object) et GetHashCode (héritées de Object).À moins qu’elle ne soit remplacée, la méthode Object.Equals(Object) teste l’égalité de référence. Autrement dit, deux variables d’objet sont considérées comme égales si elles font référence au même objet. Dans la classe
Book
, d’autre part, deux objetsBook
doivent être égaux s’ils ont le même ISBN.Lorsque vous remplacez la méthode Object.Equals(Object), vous devez également remplacer la méthode GetHashCode, qui retourne une valeur que le runtime utilise pour stocker des éléments dans des collections hachées pour une récupération efficace. Le code de hachage doit retourner une valeur cohérente avec le test d’égalité. Étant donné que vous avez remplacé Object.Equals(Object) pour retourner
true
si les propriétés ISBN de deux objetsBook
sont égales, vous retournez le code de hachage calculé en appelant la méthode GetHashCode de la chaîne retournée par la propriétéISBN
.
La figure suivante illustre la relation entre la classe Book
et Publication
, sa classe de base.
cours de publication et de livre
Vous pouvez maintenant instancier un objet Book
, appeler à la fois ses membres uniques et hérités, et le transmettre en tant qu’argument à une méthode qui attend un paramètre de type Publication
ou de type Book
, comme l’illustre l’exemple suivant.
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
Conception de classes de base abstraites et de leurs classes dérivées
Dans l’exemple précédent, vous avez défini une classe de base qui a fourni une implémentation pour un certain nombre de méthodes afin d’autoriser les classes dérivées à partager du code. Toutefois, dans de nombreux cas, la classe de base n’est pas censée fournir une implémentation. Au lieu de cela, la classe de base est une classe abstraite qui déclare méthodes abstraites; il sert de modèle qui définit les membres que chaque classe dérivée doit implémenter. En règle générale, dans une classe de base abstraite, l’implémentation de chaque type dérivé est unique à ce type. Vous avez marqué la classe avec le mot clé abstrait, car il n’a pas été judicieux d’instancier un objet Publication
, même si la classe a fourni des implémentations de fonctionnalités communes aux publications.
Par exemple, chaque forme géométrique à deux dimensions fermée comprend deux propriétés : la zone, l’étendue interne de la forme ; et périmètre, ou la distance le long des bords de la forme. Toutefois, la façon dont ces propriétés sont calculées dépend complètement de la forme spécifique. La formule pour calculer le périmètre (ou la circonférence) d’un cercle, par exemple, est différente de celle d’un carré. La classe Shape
est une classe abstract
avec des méthodes abstract
. Cela indique que les classes dérivées partagent la même fonctionnalité, mais ces classes dérivées implémentent cette fonctionnalité différemment.
L’exemple suivant définit une classe de base abstraite nommée Shape
qui définit deux propriétés : Area
et Perimeter
. En plus de marquer la classe avec le mot clé abstrait, chaque membre d’instance est également marqué avec le mot clé abstrait. Dans ce cas, Shape
remplace également la méthode Object.ToString pour retourner le nom du type, plutôt que son nom complet. Il définit deux membres statiques, GetArea
et GetPerimeter
, qui permettent aux appelants de récupérer facilement la zone et le périmètre d’une instance de toute classe dérivée. Lorsque vous passez une instance d’une classe dérivée à l’une de ces méthodes, le runtime appelle la substitution de la méthode de la classe dérivée.
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;
}
Vous pouvez ensuite dériver certaines classes de Shape
qui représentent des formes spécifiques. L’exemple suivant définit trois classes, Square
, Rectangle
et Circle
. Chacune utilise une formule unique pour cette forme particulière pour calculer la zone et le périmètre. Certaines des classes dérivées définissent également des propriétés, telles que Rectangle.Diagonal
et Circle.Diameter
, qui sont uniques à la forme qu’elles représentent.
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;
}
L’exemple suivant utilise des objets dérivés de Shape
. Il instancie un tableau d’objets dérivés de Shape
et appelle les méthodes statiques de la classe Shape
, qui enveloppent les valeurs des propriétés de retour Shape
. Le runtime récupère des valeurs à partir des propriétés substituées des types dérivés. L’exemple convertit également chaque objet Shape
dans le tableau en son type dérivé et, si le cast réussit, récupère les propriétés de cette sous-classe particulière de 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