Configurare classi di base e derivate
I termini classe base e classe derivata vengono usati per descrivere la relazione tra classi in una gerarchia di ereditarietà. Una classe base viene usata per definire un set comune di attributi e comportamenti ereditati da altre classi. Una classe derivata viene usata per definire un set specializzato di attributi e comportamenti che estendono o modificano la classe di base.
Esaminare la natura transitiva dell'ereditarietà
In C#, una classe può ereditare da una sola classe di base. Questa restrizione è nota come ereditarietà singola. L'ereditarietà singola consente a una classe derivata di accedere alle proprietà e ai metodi di una singola classe di base, promuovendo una gerarchia chiara e semplice. Il concetto di ereditarietà multipla non è supportato in C#. Questa restrizione evita le complessità e le ambiguità che possono verificarsi quando ereditano da più classi di base. C# usa invece interfacce per ottenere funzionalità simili.
Sebbene una classe possa ereditare da una sola classe di base, l'ereditarietà è transitiva. Ciò significa che se una classe C eredita dalla classe B e la classe B eredita dalla classe A, la classe C eredita i membri dichiarati sia nella classe B che nella classe A. Questa proprietà consente gerarchie più approfondite e un ulteriore riutilizzo del codice.
Si consideri, ad esempio, un sistema di gestione dei veicoli in cui è presente una classe base Vehicle, una classe derivata Car che eredita da Vehiclee un'altra classe derivata ElectricCar che eredita da Car. In questa gerarchia, ElectricCar eredita i membri sia da Car che da Vehicle, dimostrando la natura transitiva dell'ereditarietà.
public class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started.");
}
public void StopEngine()
{
Console.WriteLine("Engine stopped.");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public void OpenTrunk()
{
Console.WriteLine("Trunk opened.");
}
public void HonkHorn()
{
Console.WriteLine("Horn honked.");
}
public void LockDoors()
{
Console.WriteLine("Doors locked.");
}
}
public class ElectricCar : Car
{
public int BatteryCapacity { get; set; }
public void ChargeBattery()
{
Console.WriteLine("Battery charging.");
}
public void DisplayBatteryStatus()
{
Console.WriteLine("Battery status displayed.");
}
}
public class CombustionEngineCar : Car
{
public int FuelCapacity { get; set; }
public void Refuel()
{
Console.WriteLine("Car refueled.");
}
public void CheckOilLevel()
{
Console.WriteLine("Oil level checked.");
}
}
In questo esempio viene illustrato un semplice sistema di gestione dei veicoli con le classi seguenti:
Vehicleclasse: la classe baseVehiclecontiene proprietà e metodi comuni per tutti i veicoli, ad esempio Make, Model, StartEngine e StopEngine.classe
Car: la classeCareredita daVehiclee aggiunge proprietà e metodi specifici per le automobili, ad esempioNumberOfDoors,OpenTrunk,HonkHorneLockDoors.classe
ElectricCar: la classeElectricCareredita daCare aggiunge proprietà e metodi specifici per le automobili elettriche, ad esempioBatteryCapacity,ChargeBatteryeDisplayBatteryStatus.classe
CombustionEngineCar: la classeCombustionEngineCareredita anche daCare aggiunge proprietà e metodi specifici per le automobili a motore a combustione, ad esempioFuelCapacity,RefueleCheckOilLevel.
In questo esempio, la classe Car eredita i membri dalla classe Vehicle e il ElectricCar e CombustionEngineCar ereditano membri dalla classe Car. Questa gerarchia illustra la natura transitiva dell'ereditarietà, in cui ElectricCar e CombustionEngineCar ereditano membri sia da Car che da Vehicle.
Esaminare l'ereditarietà e la visibilità dei membri
Quando una classe eredita da una classe base, si applicano le regole seguenti:
- I costruttori statici e di istanza non vengono ereditati.
- Tutti gli altri membri della classe di base vengono ereditati, ma i modificatori di accesso influiscono sulla loro visibilità nella classe derivata. I modificatori di accesso includono:
public,protected,internaleprivate.
Membri pubblici
I membri pubblici sono accessibili da qualsiasi codice che abbia accesso alla classe . Le classi derivate ereditano membri pubblici e sono accessibili dall'esterno della gerarchia di classi.
public class BaseClass
{
public int publicField;
public void PublicMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessPublicMember()
{
publicField = 10;
PublicMethod();
}
}
In questo esempio, il DerivedClass eredita i membri publicField e PublicMethod dal BaseClass. Il metodo AccessPublicMember nel DerivedClass può accedere a questi membri. I membri pubblici sono accessibili anche dal codice esterno alla gerarchia di classi.
Membri protetti
I membri protetti sono accessibili all'interno della classe in cui sono dichiarati e all'interno di classi derivate. Non sono accessibili dall'esterno della gerarchia di classi.
public class BaseClass
{
protected int protectedField;
protected void ProtectedMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessProtectedMember()
{
protectedField = 10;
ProtectedMethod();
}
}
In questo esempio, il DerivedClass eredita i membri protectedField e ProtectedMethod dal BaseClass. Il metodo AccessProtectedMember nel DerivedClass può accedere a questi membri. Tuttavia, se si tenta di accedere ai membri protetti dall'esterno della gerarchia di classi, viene generato un errore in fase di compilazione.
Membri interni
I membri interni sono accessibili all'interno dello stesso assembly. Non sono accessibili dall'esterno dell'assembly, anche se la classe viene ereditata.
public class BaseClass
{
internal int internalField;
internal void InternalMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessInternalMember()
{
internalField = 10;
InternalMethod();
}
}
In questo esempio, il DerivedClass eredita i membri internalField e InternalMethod dal BaseClass. Il metodo AccessInternalMember nella DerivedClass può accedere a questi membri perché si trovano nello stesso assembly. Tuttavia, se si tenta di accedere ai membri interni dall'esterno dell'assembly, viene generato un errore in fase di compilazione.
Membri privati
I membri privati sono accessibili solo all'interno della classe in cui sono dichiarati. Le classi derivate non ereditano membri privati, quindi non sono direttamente accessibili nella classe derivata.
public class BaseClass
{
private int privateField;
private void PrivateMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessPrivateMember()
{
// Can't access privateField or PrivateMethod
}
}
In questo esempio, l'DerivedClass eredita dal BaseClass, ma non può accedere ai membri privateField o PrivateMethod perché sono privati.
Esaminare l'uso di parole chiave abstract, virtual e sealed nella classe base
Le classi di base e derivate usano le parole chiave abstract, virtuale sealed per controllare il comportamento dei membri ereditati. Queste parole chiave consentono di definire il livello di controllo che le classi derivate hanno sui membri della classe di base.
Esaminare l'uso della parola chiave abstract
La parola chiave abstract in C# viene usata per definire classi e membri di classe incompleti e che devono essere implementati nelle classi derivate. Non è possibile creare un'istanza diretta di una classe astratta e deve essere una classe di base per altre classi. I metodi astratti e le proprietà vengono dichiarati senza alcuna implementazione e devono essere sottoposti a override nelle classi derivate nonabstract.
Per esempio:
public abstract class Shape
{
public abstract int GetArea();
}
public class Square : Shape
{
private int _side;
public Square(int side)
{
_side = side;
}
public override int GetArea()
{
return _side * _side;
}
}
class Program
{
static void Main()
{
Square square = new Square(5);
Console.WriteLine($"Area of the square = {square.GetArea()}");
}
}
In questo esempio la classe Shape è astratta e contiene un metodo astratto GetArea. La classe Square eredita da Shape e fornisce un'implementazione per il metodo GetArea. È possibile creare un'istanza della classe Square e il metodo GetArea restituisce l'area del quadrato.
Le regole seguenti descrivono come la parola chiave abstract influisce sull'ereditarietà:
- Classi astratte: non è possibile creare direttamente un'istanza di una classe astratta. La classe astratta è una classe base per le classi derivate e le classi derivate devono fornire implementazioni per tutti i membri astratti della classe astratta.
- Metodi astratti: i metodi astratti vengono dichiarati senza alcuna implementazione nella classe astratta. Le classi derivate devono eseguire l'override di questi metodi e fornire l'implementazione.
- Proprietà astratte: analogamente ai metodi astratti, le proprietà astratte vengono dichiarate senza implementazione e devono essere sottoposte a override nelle classi derivate.
La parola chiave abstract in C# è uno strumento potente per definire classi e membri incompleti che devono essere implementati nelle classi derivate. Applica un contratto che le classi derivate devono seguire, assicurandosi che vengano implementati determinati metodi e proprietà. L'uso appropriato della parola chiave abstract promuove una chiara delineazione delle responsabilità tra classi di base e derivate.
Esaminare l'uso della parola chiave virtual
La parola chiave virtual in C# viene usata per definire metodi e proprietà di cui è possibile eseguire l'override nelle classi derivate. Un metodo virtuale o una proprietà ha un'implementazione nella classe base, ma può essere estesa o modificata nelle classi derivate. Le classi derivate possono eseguire l'override dei membri virtuali per fornire le proprie implementazioni.
Per esempio:
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Dog barks");
}
}
class Program
{
static void Main()
{
Animal animal = new Dog();
animal.MakeSound();
}
}
In questo esempio, la classe Animal definisce un metodo virtuale MakeSound che stampa un messaggio generico. La classe Dog eredita da Animal ed esegue l'override del metodo MakeSound per stampare un messaggio specifico. Quando si crea un oggetto Dog e si chiama il metodo MakeSound, viene eseguita l'implementazione sottoposta a override nella classe Dog.
Le regole seguenti descrivono come la parola chiave virtual influisce sull'ereditarietà:
- Metodi virtuali: un metodo virtuale ha un'implementazione nella classe base, ma può essere sottoposto a override nelle classi derivate.
- Proprietà virtuali: analogamente ai metodi virtuali, le proprietà virtuali hanno un'implementazione nella classe base e possono essere sottoposte a override nelle classi derivate.
Esaminare l'uso della parola chiave sealed
La parola chiave sealed in C# viene usata per impedire che un membro di classe o classe venga ereditato o sottoposto a override. Quando una classe è contrassegnata come sealed, non può essere usata come classe di base per altre classi. Quando un metodo viene contrassegnato come sealed, non può essere sottoposto a override nelle classi derivate.
Per esempio:
public class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Method1 in BaseClass");
}
public virtual void Method2()
{
Console.WriteLine("Method2 in BaseClass");
}
}
public class DerivedClass : BaseClass
{
public sealed override void Method1()
{
Console.WriteLine("Method1 in DerivedClass");
}
public override void Method2()
{
Console.WriteLine("Method2 in DerivedClass");
}
}
public class FinalClass : DerivedClass
{
// This class can't override Method1 because it's sealed in DerivedClass
public override void Method2()
{
Console.WriteLine("Method2 in FinalClass");
}
}
In questo esempio, il DerivedClass eredita dal BaseClass ed esegue l'override del metodo Method1, contrassegnandolo come sealed. Anche il metodo Method2 viene sottoposto a override nel DerivedClass ma non è sealed. Il FinalClass eredita da DerivedClass e tenta di eseguire l'override del metodo Method2. Tuttavia, non può eseguire l'override del metodo Method1 perché è bloccato nella DerivedClass.
Le regole seguenti descrivono come la parola chiave sealed influisce sull'ereditarietà:
- Classi sealed: una classe sealed non può essere usata come classe base per altre classi. Impedisce l'ereditarietà dalla classe sealed.
- Metodi sealed: non è possibile eseguire l'override di un metodo sealed nelle classi derivate. Impedisce un'ulteriore modifica del metodo nelle classi derivate.
- Proprietà sealed: analogamente ai metodi sealed, le proprietà sealed non possono essere sottoposte a override nelle classi derivate.
Le classi e i metodi sealed sono utili quando si desidera impedire ulteriori estensioni o modifiche di una classe o di un metodo. Forniscono un modo per limitare l'ereditarietà e garantire che determinati membri rimangano invariati.
Esaminare l'ereditarietà implicita da Object
In C# tutte le classi ereditano in modo implicito dalla classe Object. La classe Object definisce diversi metodi disponibili per tutte le classi, ad esempio ToString, Equalse GetHashCode. Se una classe non eredita in modo esplicito da un'altra classe, eredita comunque da Object per impostazione predefinita.
-
ToString: il metodoToStringrestituisce una stringa che rappresenta l'oggetto corrente. Per impostazione predefinita, restituisce il nome completo della classe . -
Equals: il metodoEqualsconfronta due oggetti per verificarne l'uguaglianza. Per impostazione predefinita, confronta i riferimenti degli oggetti . -
GetHashCode: il metodoGetHashCoderestituisce un codice hash per l'oggetto corrente. Per impostazione predefinita, restituisce il codice hash del riferimento dell'oggetto.
Si consideri l'esempio di codice seguente che crea tre oggetti Person e illustra i metodi ereditati ToString, Equalse GetHashCode:
Person person1 = new Person { Name = "Alice", Age = 30 };
Person person2 = new Person { Name = "Alice", Age = 30 };
Person person3 = person1;
Console.WriteLine(person1.ToString());
Console.WriteLine(person1.Equals(person2));
Console.WriteLine(person1.GetHashCode());
Console.WriteLine(person1.Equals(person3));
public class Person
{
public string? Name { get; set; }
public int Age { get; set; }
}
public class Employee : Person
{
public int EmployeeNumber { get; set; }
public decimal Salary { get; set; }
}
// Output: Person
// False
// 32854180
// True
In questo esempio di codice, il metodo ToString restituisce il nome completo della classe Person, che include lo spazio dei nomi definito. Il primo metodo Equals confronta i riferimenti degli oggetti person1 e person2 e restituisce False. Il metodo GetHashCode restituisce il codice hash del riferimento dell'oggetto person1. Il metodo Equals confronta quindi i riferimenti degli oggetti person1 e person3 e restituisce True.
Sommario
L'ereditarietà consente alle classi derivate di ereditare membri della classe base che definiscono un set comune di attributi e comportamenti. Le classi derivate possono estendere o modificare il comportamento della classe di base aggiungendo nuovi membri o eseguendo l'override dei membri esistenti. Le parole chiave abstract, virtuale sealed vengono usate per controllare il modo in cui i membri della classe base vengono ereditati o sottoposti a override. La visibilità dei membri ereditati è influenzata dai modificatori di accesso, ad esempio public, protected, internale private. Tutte le classi in C# ereditano in modo implicito dalla classe Object, che fornisce diversi metodi, ad esempio ToString, Equalse GetHashCode, disponibili per tutte le classi.