Metodele virtuale sunt metode care pot fi suprascrise în clasele copil (derivate). În mod implicit, în .NET, metodele nu sunt virtuale. Pentru a declara o metodă ca virtuală, trebuie să o declaram folosind cuvântul cheie virtual, ca atare:
1 2 3 4 |
public virtual void MetodaMea() { Console.WriteLine("Felinele vaneaza " + prada); } |
În acest caz, orice clasă care moștenește de la clasa în care este declarat MetodaMea(), poate avea și ea o metodă numită MetodaMea(), cu aceeași semnătură, care va înlocui metoda din clasa părinte. Abilitatea de a suprascrie metodele virtuale este o cerință fundamentală a polimorfismului, despre care voi vorbi într-o lecție viitoare.
Metoda care suprascrie o metodă virtuală din clasa părinte trebuie să utilizeze cuvântului cheie override.
Așa se declară metoda virtuală:
1 2 3 4 5 6 7 |
public class Felidae { public virtual void Vaneaza(object prada) { Console.WriteLine("Felinele vaneaza " + prada); } } |
Această clasă de bază conține metoda virtuală Vaneaza(), a cărei semnătură acceptă un singur parametru de tip object. Aceasta înseamnă că orice clasă moștenitoare care dorește să suprascrie această metodă, trebuie să declare o metodă cu același nume și aceeași semnătură, astfel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Pisica : Felidae { public override void Vaneaza(object prada) { Console.WriteLine("Pisica vaneaza " + prada); } } public class Leu : Felidae { public override void Vaneaza(object prada) { Console.WriteLine("Leul vaneaza " + prada); } } |
Explicația e simplă: avem două clase, Pisica și Leu, ambele moștenind din clasa de bază Felidae. Ambele suprascriu metoda Vaneaza(), oferind propria lor implementare personalizată și imprimând mesaje personalizate la consolă. Luați în considerare următorul exemplu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
using System; namespace BunaLume { class Program { static void Main(string[] args) { Pisica pisica = new Pisica(); pisica.Vaneaza("soareci"); Leu leu = new Leu(); leu.Vaneaza("zebre"); Felidae felidaeLeu = new Leu(); felidaeLeu.Vaneaza("gazele"); Console.Read(); } } public class Felidae { public virtual void Vaneaza(object prada) { Console.WriteLine("Felidae vaneaza " + prada); } } public class Pisica : Felidae { public override void Vaneaza(object prada) { Console.WriteLine("Pisica vaneaza " + prada); } } public class Leu : Felidae { public override void Vaneaza(object prada) { Console.WriteLine("Leul vaneaza " + prada); } } } |
care produce următoarea ieșire:
În primele două instanțe, ne-am așteptat să vedem că implementările Pisica și Leu sunt tipărite pe consolă, deoarece am suprascris metoda virtuală din clasa părinte în clasele copil. Cu toate acestea, puteți observa că deși a treia instanță a fost declarată de tip Felidae, am instanțiat-o de tip Leu și, prin urmare, a fost apelată metoda suprascrisă, și nu cea de bază, virtuală; consola nu a tipărit „Felidae vaneaza gazele”, ci a afișat „Leul vaneaza gazele”. Din aceasta, putem concluziona că metodele virtuale, precum și metodele abstracte, despre care voi vorbi într-o lecție viitoare, pot fi rescrise, suprascrise. De fapt, metodele abstracte sunt doar metode virtuale, dar fără o implementare concretă. De asemenea, vom afla ulterior că toate metodele definite în interfețe sunt abstracte și, prin urmare, virtuale, chiar dacă acest aspect nu este definit în mod explicit.
Acum, întrebarea logică vine în minte: care este utilitatea unei metode în clasa de bază, dacă este suprascrisă în clasa copil? De ce trebuie să o declaram în primul rând? Ei bine, există două motive pentru asta. În primul rând, s-ar putea să dorim să apelăm metoda virtuală din clasa copil, fără a o suprascrie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; namespace BunaLume { class Program { static void Main(string[] args) { Pisica pisica = new Pisica(); pisica.Vaneaza("soareci"); Console.Read(); } } public class Felidae { public virtual void Vaneaza(object prada) { Console.WriteLine("Felidae vaneaza " + prada); } } public class Pisica : Felidae { } } |
Acesta va fi rezultatul:
Deoarece nu am suprascris metoda Vaneaza() din clasa Pisica, când am apelat-o din instanța de tip Pisica, am apelat de fapt metoda virtuală a clasei de bază.
Un al doilea motiv ar fi faptul că, atunci când suprapunem metode virtuale în clasele copil, putem încă să apelăm în mod explicit implementarea virtuală din clasa de bază a metodei:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using System; namespace BunaLume { class Program { static void Main(string[] args) { Pisica pisica = new Pisica(); pisica.Vaneaza("soareci"); Console.Read(); } } public class Felidae { public virtual void Vaneaza(object prada) { Console.WriteLine("Felidae vaneaza " + prada); } } public class Pisica : Felidae { public override void Vaneaza(object prada) { Console.WriteLine("Pisica vaneaza " + prada); base.Vaneaza(prada); } } } |
Iată rezultatul:
Deoarece în implementarea suprascrisă a metodei Vaneaza() am apelat și base.Vaneaza(prada), i-am spus compilatorului să apeleze în mod explicit și implementarea virtuală a metodei, din clasa de bază, executând în același timp instrucțiunile din implementarea suprascrisă. Aceasta înseamnă că dacă dorim, putem adăuga suplimentar funcționalitatea implementării de bază la funcționalitatea implementării suprascrise, executând efectiv ambele implementări.
Aceasta este diferența principală între o metodă suprascrisă și o metodă normală. Prima ne permite să schimbăm/completăm/modificăm o parte din funcționalitățile moștenite. Un exemplu simplu este metoda virtuală ToString(), pe care orice obiect .NET o are. Deoarece este declarată virtuală, o putem suprascrie în orice implementare, oferind o funcționalitate personalizată. Pot demonstra acest lucru cu următorul exemplu simplu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; namespace BunaLume { class Program { static void Main(string[] args) { Pisica pisica = new Pisica(); Console.WriteLine(pisica.ToString()); Console.Read(); } } public class Pisica { } } |
Ieșirea va fi aceasta:
ceea ce nu este foarte descriptiv. În schimb, pot să înlocuiesc această implementare virtuală implicită cu una personalizată, oferind o ieșire mult mai ușor de citit și înțeles:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System; namespace BunaLume { class Program { static void Main(string[] args) { Pisica pisica = new Pisica(); Console.WriteLine(pisica.ToString()); Console.Read(); } } public class Pisica { public override string ToString() { return "Sunt o pisica!"; } } } |
Trebuie să recunoașteți, acesta este un mod mult mai frumos de a afișa un obiect.
Un aspect final al metodelor virtuale: dacă nu dorim ca metodele noastre suprascrise să fie modificate în continuare de alte clase care ar continua să moștenească clasa noastră, sau dacă dorim să oprim clasa noastră copil să mai fie moștenite de alte clase, putem folosi cuvântul cheie sealed. În principiu, acest cuvânt cheie spune că „această clasă/proprietate/metodă NU poate fi moștenită”. În această lumină, nu are sens să marcăm o metodă virtuală ca sealed. Cuvântul cheie override spune compilatorului că metoda poate fi suprascrisă de obiecte copil, dar cuvântul cheie sealed spune că metoda nu poate fi moștenită de o clasă copil în primul rând. De fapt, atunci când este utilizat pentru metode sau proprietăți, cuvântul cheie sealed este permis numai pentru metode sau proprietăți decorate cu cuvântul cheie override. În acest caz, cuvântul cheie sealed înseamnă că metoda sau proprietatea suprascrisă nu poate fi moștenită/modificată în continuare de clase care moștenesc clasa în care este declarată metoda ce suprascrie:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class ClasaBaza { public virtual void OMetoda() { } } public class ClasaDerivata : ClasaBaza { public void OAltaMetoda() { // nu are sens sa marcam acest membru ca sealed - nu e virtual } public override sealed void OMetoda() { // Daca nu marcam acest membru ca sealed, o alta clasa derivata din aceasta clasa il poate suprascrie din nou } } public class ClasaNepot : ClasaDerivata { public override void OAltaMetoda() { // EROARE - OAltaMetoda nu e virtuala } public override void OMetoda() { // EROARE - am marcat SomeMethod ca sealed in ClasaDerivata // Daca n-as fi facut-o, aceasta metoda ar fi fost corecta } } |
În lecția următoare, voi discuta despre conceptul abstract al programării.
Tags: alți operatori, declarare metode, metode, OOP, operatori, override, programarea orientată pe obiecte, virtual