Interfețe C#

Tradus din această pagină oficială de documentație Microsoft.

O interfață definește un contract care poate fi implementat de clase și struct-uri. O interfață poate conține metode, proprietăți, evenimente, și indexatori. O interfață nu furnizează implementări ai membrilor pe care îi definește — ea pur și simplu specifică membrii care trebuie să fie furnizați de clasele sau struct-urile care implementează interfața.

Interfețele pot folosi moștenire multiplă. In următorul exemplu, interfața IComboBox moștenește și de la ITextBox și IListBox.

interface IControl
{
    void Paint();
}
interface ITextBox: IControl
{
    void SetText(string text);
}
interface IListBox: IControl
{
    void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}

Clasele și struct-urile pot implementa mai multe interfețe. In exemplul următor, clasa EditBox implementează și IControl și IDataBound.

interface IDataBound
{
    void Bind(Binder b);
}
public class EditBox: IControl, IDataBound
{
    public void Paint() { }
    public void Bind(Binder b) { }
}

Când o clasă sau struct implementează o anumită interfață, instanțele acelei clase sau struct-uri pot fi implicit convertite la acel tip de interfață. De exemplu:

EditBox editBox = new EditBox();
IControl control = editBox;
IDataBound dataBound = editBox;

In cazurile în care o instanță nu este cunoscută static că implementează o anumită interfață, convertiri dinamice de tip pot fi folosite. De exemplu, următoarele instrucțiuni folosesc convertiri dinamice de tip pentru a obține implementările de interfețe IControl și IDataBound ale unui obiect. Deoarece tipul concret la timpul rulării al obiectului este EditBox, convertirile reușesc.



object obj = new EditBox();
IControl control = (IControl)obj;
IDataBound dataBound = (IDataBound)obj;

In clasa anterioară EditBox, metoda Paint din interfața IControl și metoda Bind din interfața IDataBound sunt implementate folosind membri publici. C# de asemenea suportă implementări de membri de interfață în mod explicit, dând voie clasei sau struct-urii să evite a face membrii publici. O implementare explicită a unui membru de interfață este scris folosind numele complet calificat al membrului de interfață. De exemplu, clasa EditBox poate implementa metodele IControl.Paint și IDataBound.Bind folosind implementări explicite de membri de interfață după cum urmează.

public class EditBox: IControl, IDataBound
{
    void IControl.Paint() { }
    void IDataBound.Bind(Binder b) { }
}

Membrii de interfață expliciți pot fi accesați doar prin tipul interfață. De exemplu, implementarea lui IControl.Paint furnizată de clasa anterioară EditBox poate fi invocată doar prin convertirea mai întâi a referinței EditBox la tipul interfață IControl.

EditBox editBox = new EditBox();
editBox.Paint();            // Eroare, nici o asemenea metodă
IControl control = editBox;
control.Paint();            // Ok

Tradus din această pagină oficială de documentație Microsoft.

Tablouri C#

Tradus din această pagină oficială de documentație Microsoft.

Un tablou este o structură de date care conține un număr de variabile care sunt accesate prin indici calculați. Variabilele conținute într-un tablou, de asemenea numite elemente ale tabloului, sunt toate de același tip, și acest tip se cheamă tipul de elemente al tabloului.

Tipurile tablou sunt tipuri referință, și declarația unei variabile tablou pur și simplu pune de o parte spațiu pentru o referință către o instanță de tablou. Instanțele propriu zise de tablou sunt create dinamic la momentul execuției folosind operatorul new. Operația new specifică lungimea unei noi instanțe de tablou, care este apoi fixă pentru toată durata de viață a instanței. Indicii elementelor unui tablou sunt cuprinse între 0 și Length - 1. Operatorul new inițializează automat elementele unui tablou la valoarea lor implicită, care, de exemplu, este zero pentru toate tipurile numerice și null pentru toate tipurile referință.

Următorul exemplu creează un tablou de elemente int, inițializează tabloul, și afișează conținutul tabloului.

using System;
class ArrayExample
{
    static void Main()
    {
        int[] a = new int[10];
        for (int i = 0; i < a.Length; i++)
        {
            a[i] = i * i;
        }
        for (int i = 0; i < a.Length; i++)
        {
            Console.WriteLine($"a[{i}] = {a[i]}");
        }
    }

}

Acest exemplu creează și operează pe un tablou uni-dimensional. C# suportă de asemenea tablouri multi-dimensionale. Numărul de dimensiuni ale unui tip tablou, cunoscut și ca rang-ul tipului tablou, este unu plus numărul de virgule scrise între parantezele drepte ale tipului tablou. Următorul exemplu alocă un tablou uni-dimensional, unul bi-dimensional, și, respectiv, unul tri-dimensional.

int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

Tabloul a1 conține 10 elemente, tabloul a2 conține 50 (10 × 5) elemente, și tabloul a3 conține 100 (10 × 5 × 2) elemente. Tipul de element al unui tablou poate fi oricare tip, inclusiv un tip tablou. Un tablou cu elemente de un tip tablou este uneori numit un tablou crestat (en. jagged array) deoarece lungimile tablourilor elemente nu trebuie să fie toate la fel. Următorul exemplu alocă un tablou de tablouri de int:

int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

Prima linie creează un tablou cu trei elemente, fiecare de tip int[] și fiecare cu o valoare inițială null. Liniile ulterioare apoi inițializează cele trei elemente cu referințe la instanțe de tablouri individuale de lungimi variabile.

Operatorul new permite valorilor inițiale ale elementelor tabloului să fie specificate folosind un inițializator de tablou, care este o listă de expresii scrise între delimitatorii { și }. Următorul exemplu alocă și inițializează un int[] cu trei elemente.

int[] a = new int[] {1, 2, 3};

Notați că lungimea tabloului este dedusă din numărul de expresii dintre { și }. Variabilele locale și declarațiile de câmpuri pot fi scurtate mai departe astfel încât tipul tablou nu trebuie să fie reafirmat.

int[] a = {1, 2, 3};

Ambele exemple anterioare sunt echivalente cu următorul:

int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;


Tradus din această pagină oficială de documentație Microsoft.

Struct-uri C#

Tradus din această pagină oficială de documentație Microsoft.

La fel ca clasele, struct-urile sunt structuri de date care pot conține membrii date și membri funcții, dar spre deosebire de clase, struct-urile sunt tipuri valoare și nu cer alocare pe heap. O variabilă de un tip struct stochează direct datele struct-urii, în timp ce o variabilă de un tip clasă stochează o referință la un obiect alocat dinamic. Tipurile struct nu suportă moștenire specificată de utilizator, și toate tipurile struct moștenesc implicit de la tipul ValueType, care la rândul lui moștenește implicit de la object.

Struct-urile sunt deosebit de folositoare pentru structuri mici de date care au înțeles de valoare. Numere complexe, puncte într-un sistem de coordonate, sau perechi cheie-valoare într-un dicționar sunt toate exemple bune de struct-uri. Utilizarea struct-urilor mai degrabă decât a claselor pentru structuri mici de date poate face o diferență enormă în numărul de alocări de memorie pe care le realizează o aplicație. De exemplu, următorul program creează și inițializează un tablou de 100 de puncte. Cu Point implementat ca o clasă, 101 de obiecte separate sunt instanțiate — unul pentru tablou și unul pentru fiecare din cele 100 de elemente.

public class PointExample
{
    public static void Main()
    {
        Point[] points = new Point[100];
        for (int i = 0; i < 100; i++)
            points[i] = new Point(i, i);
    }
}


O alternativă este să facem Point un struct.

struct Point
{
    public int x, y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}


Acum, doar un obiect este instanțiat unul pentru tablou și instanțele Point sunt stocate liniar în tablou.

Constructorii struct sunt invocați cu operatorul new, similar cu un constructor de clasă. Dar, în schimbul alocării dinamice a unui obiect pe heap-ul gestionat (en. managed) și a returnării unei referințe către el, un constructor de struct pur și simplu întoarce valoarea struct-urii în sine (de obicei ca o locație temporară pe stivă), și această valoare este apoi copiată după cât este necesar.

Cu clase, este posibil pentru două variabile să se refere la același obiect și deci posibil pentru operațiile asupra unei variabile să afecteze obiectul referit de cealaltă variabilă. Cu struct-uri, variabilele au fiecare propria lor copie a datelor, și nu este posibil pentru operațiile asupra uneia să afecteze cealaltă. De exemplu, ieșirea produsă de următorul fragment de cod depinde dacă Point este o clasă sau o struct.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);


Dacă Point este o clasă, ieșirea este 20 deoarece a și b se referă la același obiect. Dacă Point este o struct, ieșirea este 10 deoarece atribuirea lui a la b creează o copie a valorii, și această copie este neafectată de atribuiri ulterioare la a.x.

Exemplul anterior evidențiază două din limitările struct-urilor. In primul rând, copierea unei întregi struct este de obicei mai puțin eficientă decât copierea unei referințe de obiect, deci atribuirea și transmiterea parametrilor valoare pot fi mai scumpe cu struct-urile decât cu tipurile referință. In al doilea rând, cu excepția parametrilor in, ref, și out, nu este posibil să se creeze referințe la struct-uri, ceea ce exclude utilizarea lor într-un număr de situații.

Tradus din această pagină oficială de documentație Microsoft.

Clase și obiecte în C#

Tradus din această pagină oficială de documentație Microsoft.

Clasele sunt cele mai fundamentale dintre tipurile C#. O clasă este o structură de date care combină stare (câmpuri) și acțiuni (metode și alte membri funcții) într-o singură unitate. O clasă furnizează o definiție pentru instanțe create dinamic ale clasei, cunoscute și ca obiecte. Clasele suportă moștenire și polimorfism, mecanisme prin care clase derivate pot extinde și specializa clase bază.

Clase noi sunt create folosind declarații de clase. O declarație de clasă începe cu un antet care specifică atributele și modificatorii clasei, numele clasei, clasa bază (dacă este dată), și interfețele implementate de clasă. Antetul este urmat de corpul clasei, care consistă dintr-o listă de declarații de membri scrise între delimitatorii { și }.

Următoarea este o declarație a unei clase simple numită Point (ro. Punct):

public class Point
{
    public int x, y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}


Instanțe de clase sunt create folosind operatorul new, care alocă memorie pentru o nouă instanță, invocă un constructor să inițializeze instanța, și întoarce on referință către instanță. Următoarele instrucțiuni creează două obiecte Point și rețin referințe la aceste obiecte în două variabile:

Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);


Memoria ocupată de un obiect este automat revendicată când obiectul nu mai este accesibil. Nu este nici necesar și nici posibil să se dealoce obiecte explicit în C#.

Membri

Membrii unei clase sunt fie membri statici fie membri de instanță. Membrii statici aparțin claselor, și membrii de instanță aparțin obiectelor (instanțe ale claselor).

Cele ce urmează oferă o vedere de ansamblu a felurilor de membri pe care o clasă îi poate conține.
  • Constante
    • Valori constante asociate cu clasa
  • Câmpuri
    • Variabile ale clasei
  • Metode
    • Calcule și acțiuni care pot fi realizate de clasă
  • Proprietăți
    • Acțiuni asociate cu scrierea și citirea de proprietăți numite ale clasei
  • Indexatori
    • Acțiuni asociate cu indexarea instanțelor clasei ca un tablou
  • Evenimente
    • Notificări care pot fi generate de clasă
  • Operatori
    • Operatori de conversii și de expresii suportați de clasă
  • Constructori
    • Acțiuni necesare pentru inițializarea instanțelor clasei sau clasei în sine
  • Finalizatori
    • Acțiuni de realizat înainte ca instanțele clasei să fie permanent abandonate
  • Tipuri
    • Tipuri îmbricate declarate de clasă

Accesibilitate

Fiecare membru al unei clase are o accesibilitate asociată, care controlează regiunile textului programului care pot accesa membrul. Există șase forme posibile de accesibilitate. Acestea sunt sumarizate dedesubt.
  • public
    • Accesul nu este limitat
  • protected
    • Accesul este limitat la această clasă sau clase derivate din această clasă
  • internal
    • Accesul este limitat la ansamblul curent (.exe, .dll, etc.)
  • protected internal
    • Accesul este limitat la clasa conținătoare, clase derivate din clasa conținătoare, sau clase din același ansamblu
  • private
    • Accesul este limitat la clasa aceasta
  • private protected
    • Accesul este limitat la clasa conținătoare sau clase derivate din tipul conținător din același ansamblu

Parametri de tip

O definiție a unei clase poate specifica o mulțime de parametri de tip prin urmarea numelui clasei cu paranteze unghiulare încadrând o listă de nume de parametri de tip. Parametrii de tip pot fi apoi folosiți în corpul declarațiilor clasei pentru a defini membrii clasei. In următorul exemplu, parametrii de tip ai Pair sunt TFirst și TSecond:

public class Pair<TFirst,TSecond>
{
    public TFirst First;
    public TSecond Second;
}

Un tip clasă care este declarat să primească parametri de tip este numit un tip de clasă generic. Tipurile struct, interface și delegate pot de asemenea fi generice. Când clasa generică este folosită, argumentele de tip trebuie să fie furnizate pentru fiecare din parametrii de tip.

Pair<int,string> pair = new Pair<int,string> { First = 1, Second = "doi" };
int i = pair.First;     // TFirst este int
string s = pair.Second; // TSecond este string

Un tip generic cu argumente de tip furnizate, precum Pair<int,string> mai sus, este numit un tip construit.

Clase bază

O declarație de clasă poate specifica o clasă bază urmând numele clasei și parametri de tip cu două puncte și numele clasei bază. Omițând specificarea unei clase bază este același lucru cu derivarea din tipul object. In următorul exemplu, clasa bază a Point3D este Point, și clasa bază a Point este object:

public class Point
{
    public int x, y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}
public class Point3D: Point
{
    public int z;
    public Point3D(int x, int y, int z) :
        base(x, y)
    {
        this.z = z;
    }
}


O clasă moștenește membrii clasei sale bază. Moștenirea înseamnă că o clasă conține în mod implicit toți membrii clasei sale bază, cu excepția constructorilor de instanță și statici, și finalizatorii clasei bază. O clasă derivată poate adăuga noi membrii la aceia pe care îi moștenește, dar nu poate înlătura definiția unui membru moștenit. In exemplul precedent, Point3D moștenește câmpurile x și y de la Point, și fiecare instanță Point3D conține trei câmpuri, x, y, și z.

O conversie implicită există de la un tip clasă la oricare din tipurile claselor sale bază. Prin urmare, o variabilă de un tip clasă poate referi o instanță a acelei clase sau o instanță a oricărei clase derivate. De exemplu, date fiind declarațiile anterioare de clase, o variabilă de tip Point poate referi fie un Point fie un Point3D:

Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);

Câmpuri

Un câmp este o variabilă care este asociată cu o clasă sau cu o instanță a unei clase.

Un câmp declarat cu modificatorul static definește un câmp static. Un câmp static identifică exact o locație de stocare. Indiferent de câte instanțe ale unei clase sunt create, există întotdeauna doar o copie a câmpului static.

Un câmp declarat fără modificatorul static definește un câmp de instanță. Fiecare instanță a unei clase conține o copie separată a tuturor câmpurilor de instanță ale acelei clase.

In următorul exemplu, fiecare instanță a clasei Color are o copie separată a câmpurilor de instanță r, g, și b, dar există doar o singură copie a câmpurilor statice Black, White, Red, Green, și Blue:

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);
    private byte r, g, b;
    public Color(byte r, byte g, byte b)
    {
        this.r = r;
        this.g = g;
        this.b = b;
    }
}


Cum s-a arătat în exemplul anterior, câmpuri doar-pentru-citire pot fi declarate cu modificatorul readonly. Atribuirea la un câmp readonly poate avea loc doar ca parte a declarației câmpului sau într-un constructor în aceeași clasă.

Metode

O metodă este un membru care implementează un calcul sau o acțiune care pot fi realizate de un obiect sau de o clasă. Metodele statice sunt accesate prin clasă. Metodele de instanță sunt accesate prin instanțe ale clasei.

Metodele pot avea o listă de parametri, care reprezintă valori sau referințe de variabile transmise metodei, și un tip de întoarcere, care specifică tipul valorii calculate și întoarse de metodă. Tipul de întoarcere al unei metode este void dacă nu întoarce o valoare.

Precum tipurile, metodele pot de asemenea să aibă o mulțime de parametri de tip, pentru care argumente de tip trebuie să fie specificate când metoda este apelată. Spre deosebire de tipuri, argumentele de tip pot deseori fi deduse din argumentele unui apel de metodă și nu trebuie date explicit.

Semnătura unei metode trebuie să fie unică în clasa în care metoda este declarată. Semnătura unei metode consistă în numele metodei, numărul de parametri de tip și în numărul, modificatorii, și tipurile parametrilor ei. Semnătura unei metode nu include tipul de întoarcere.

Parametri

Parametrii sunt folosiți pentru a transmite valori sau referințe de variabile la metode. Parametrii unei metode își iau valorile reale din argumentele care sunt specificate când metoda este invocată. Există patru feluri de parametri: parametri valoare, parametri referință, parametri de ieșire, și tablouri de parametri.

Un parametru valoare este folosit pentru transmiterea argumentelor de intrare. Un parametru valoare corespunde unei variabile locale care își ia valoarea inițială de la argumentul care a fost transmis pentru parametru. Modificările pentru un parametru valoare nu afectează argumentul care a fost transmis pentru parametru.

Parametrii valoare pot fi opționali, specificând o valoare implicită astfel încât argumentele corespunzătoare pot fi omise.

Un parametru referință este folosit pentru transmiterea argumentelor prin referință. Argumentul transmis pentru un parametru referință trebuie să fie o variabilă cu o valoare definită, și în timpul execuției metodei, parametrul referință reprezintă aceeași locație de stocare ca variabila argument. Un parametru referință este declarat cu modificatorul ref. Următorul exemplu arată folosirea de parametri ref.

using System;
class RefExample
{
    static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }
    public static void SwapExample()
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"{i} {j}");    // Scrie "2 1"
    }
}


Un parametru de ieșire este folosit pentru transmiterea argumentelor prin referință. Este similar cu un parametru referință, cu excepția că nu cere să îi fie atribuită o valoare în mod explicit la argumentul furnizat de apelant. Un parametru de ieșire este declarat cu modificatorul out. Următorul exemplu arată folosirea de parametri out folosind sintaxa introdusă în C# 7.

using System;
class OutExample
{
    static void Divide(int x, int y, out int result, out int remainder)
    {
        result = x / y;
        remainder = x % y;
    }
    public static void OutUsage()
    {
        Divide(10, 3, out int res, out int rem);
        Console.WriteLine("{0} {1}", res, rem); // Scrie "3 1"
    }
}


Un tablou de parametri permite un număr variabil de argumente să fie transmise la o metodă. Un tablou de parametri este declarat cu modificatorul params. doar ultimul parametru al unei metode poate fi un tablou de parametri, și tipul unui tablou de parametri trebuie să fie un tip de tablou uni-dimensional. Metodele Write și WriteLine ale clasei System.Console sunt exemple bune de folosire de tablouri de parametri. Ele sunt declarate după cum urmează.

public class Console
{
    public static void Write(string fmt, params object[] args) { }
    public static void WriteLine(string fmt, params object[] args) { }
    // ...
}


Intr-o metodă care folosește un tablou de parametri, tabloul de parametri se comportă exact ca un parametru obișnuit de un tip tablou. Totuși, într-o invocare a unei metode cu un tablou de parametri, este posibil să transmiteți fie un singur argument de tipul tabloului de parametri sau orice număr de argumente de tipul elementelor tabloului de parametri. In cazul din urmă, o instanță de tablou este automat creată și inițializată cu argumentele date. Acest exemplu:

Console.WriteLine("x={0} y={1} z={2}", x, y, z);

este echivalent cu scrierea următoarelor.

string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

Corpul metodei și variabile locale

Corpul unei metode specifică instrucțiunile care să se execute când metoda este invocată.

Corpul unei metode poate declara variabile care sunt specifice invocării metodei. Aceste variabile se cheamă variabile locale. O declarație de variabilă locală specifică un nume de tip, un nume de variabilă, și posibil o valoare inițială. Următorul exemplu declară variabila locală i cu o valoare inițială de zero și o variabilă locală j fără valoare inițială.

using System;
class Squares
{
    public static void WriteSquares()
    {
        int i = 0;
        int j;
        while (i < 10)
        {
            j = i * i;
            Console.WriteLine($"{i} x {i} = {j}");
            i = i + 1;
        }
    }
}


C# cere unei variabile locale să fie în mod sigur atribuită înainte ca valoarea ei să poată fi obținută. De exemplu, dacă declarația anteriorului i nu includea o valoare inițială, compilatorul ar fi raportat o eroare pentru ulterioarele utilizări ale lui i deoarece i nu ar fi fost în mod sigur atribuit în acele puncte din program.

O metodă poate folosi instrucțiuni return pentru a întoarce controlul la apelantul ei. Intr-o metodă care întoarce void, instrucțiunile return nu pot specifica o expresie. Intr-o metodă care întoarce non-void, instrucțiunile return trebuie să includă o expresie care calculează valoarea de întoarcere.

Metode statice și de instanță

O metodă declarată cu modificatorul static este o metodă statică. O metodă statică nu operează pe o instanță specifică și poate accesa direct doar membri statici.

O metodă declarată fără un modificator static este o metodă de instanță. O metodă de instanță operează pe o instanță specifică și poate accesa și membri statici și cei de instanță. Instanța pe care o metodă instanță a fost invocata poate fi explicit accesată ca this. Este o eroare să se facă referire la this într-o metodă statică.

Următoarea clasă Entity are și membri statici, și membri de instanță.

class Entity
{
    static int nextSerialNo;
    int serialNo;
    public Entity()
    {
        serialNo = nextSerialNo++;
    }
    public int GetSerialNo()
    {
        return serialNo;
    }
    public static int GetNextSerialNo()
    {
        return nextSerialNo;
    }
    public static void SetNextSerialNo(int value)
    {
        nextSerialNo = value;
    }
}


Fiecare instanță Entity conține un număr de serie (și probabil câteva alte informații care nu sunt arătate aici). Constructorul Entity (care este ca o metodă de instanță) inițializează noua instanță cu următorul număr de serie disponibil. Deoarece constructorul este un membru de instanță, îi este permis să acceseze și câmpul de instanță serialNo și câmpul static nextSerialNo.

Metodele statice GetNextSerialNo și SetNextSerialNo pot accesa câmpul static nextSerialNo, dar ar fi o eroare pentru ei să acceseze direct câmpul de instanță serialNo.

Următorul exemplu arată utilizarea clasei Entity.

using System;
class EntityExample
{
    public static void Usage()
    {
        Entity.SetNextSerialNo(1000);
        Entity e1 = new Entity();
        Entity e2 = new Entity();
        Console.WriteLine(e1.GetSerialNo());            // Scrie "1000"
        Console.WriteLine(e2.GetSerialNo());            // Scrie "1001"
        Console.WriteLine(Entity.GetNextSerialNo());    // Scrie "1002"
    }
}


Notați că metodele statice SetNextSerialNo și GetNextSerialNo sunt invocate pe clasă în timp ce metoda de instanță GetSerialNo este invocată pe instanțe ale clasei.

Metode virtual, override și abstract

Când o declarație de metodă de instanță include modificatorul virtual, metoda se spune că este o metodă virtuală. Când nici un modificator virtual nu este prezent, metoda se spune că este o metodă nonvirtuală.

Când o metodă virtuală este invocată, tipul din timpul execuției al instanței pentru care acea invocare are loc determină reala implementare de invocat a metodei. Intr-o invocare de metodă nonvirtuală, tipul din momentul compilării al instanței este factorul determinant.

O metodă virtuală poate fi suprascrisă într-o clasă derivată. Când o declarație de metodă de instanță include un modificator override, metoda suprascrie o metodă virtuală cu aceeași semnătură. In timp ce o declarație de metodă virtuală introduce o nouă metodă, o declarație de metodă override specializează o metodă virtuală moștenită existentă furnizând o nouă impementare a acelei metode.

O metodă abstractă este o metodă virtuală fără implementare. O metodă abstractă este declarată cu modificatorul abstract și este permisă doar într-o clasă care este de asemenea declarată abstract. O metodă abstractă trebuie să fie suprascrisă în fiecare clasă derivată non-abstractă.

Următorul exemplu declară o clasă abstractă, Expression, care reprezintă un nod de arbore expresie, și trei clase derivate, Constant, VariableReference, și Operation, care implementează noduri de arbore expresie pentru constante, referințe de variabile, și operații aritmetice. (Acesta este similar, dar nu trebuie confundat cu tipurile arbore expresie).

using System;
using System.Collections.Generic;
public abstract class Expression
{
    public abstract double Evaluate(Dictionary<string,object> vars);
}
public class Constant: Expression
{
    double value;
    public Constant(double value)
    {
        this.value = value;
    }
    public override double Evaluate(Dictionary<string,object> vars)
    {
        return value;
    }
}
public class VariableReference: Expression
{
    string name;
    public VariableReference(string name)
    {
        this.name = name;
    }
    public override double Evaluate(Dictionary<string,object> vars)
    {
        object value = vars[name];
        if (value == null)
        {
            throw new Exception("Unknown variable: " + name);
        }
        return Convert.ToDouble(value);
    }
}
public class Operation: Expression
{
    Expression left;
    char op;
    Expression right;
    public Operation(Expression left, char op, Expression right)
    {
        this.left = left;
        this.op = op;
        this.right = right;
    }
    public override double Evaluate(Dictionary<string,object> vars)
    {
        double x = left.Evaluate(vars);
        double y = right.Evaluate(vars);
        switch (op) {
            case '+': return x + y;
            case '-': return x - y;
            case '*': return x * y;
            case '/': return x / y;
        }
        throw new Exception("Unknown operator");
    }
}


Cele patru clase precedente pot fi folosite pentru a modela expresii aritmetice. De exemplu, folosind instanțe ale acestor clase, expresia x + 3 poate fi reprezentată după cum urmează.

Expression e = new Operation(
    new VariableReference("x"),
    '+',
    new Constant(3));


Metoda Evaluate a unei instanțe Expression este invocată pentru a evalua expresia dată și a produce o valoare double. Metoda primește un argument Dictionary care conține nume de variabile (ca chei ale înregistrărilor) și valori (ca valori ale înregistrărilor). Deoarece Evaluate este o metodă abstractă, clasele non-abstracte derivate din Expression trebuie să suprascrie Evaluate.

Implementarea unei Constant a Evaluate pur și simplu întoarce constanta stocată. Implementarea unei VariableReference caută numele variabilei în dicționar si întoarce valoarea rezultată. Implementarea unei Operation mai întâi evaluează operanzii stâng și drept (invocând recursiv metodele lor Evaluate) și apoi realizează operația aritmetică dată.

Următorul program folosește clasele Expression pentru a evalua expresia x * (y + 2) pentru valori diferite ale x și y.

using System;
using System.Collections.Generic;
class InheritanceExample
{
    public static void ExampleUsage()
    {
        Expression e = new Operation(
            new VariableReference("x"),
            '*',
            new Operation(
                new VariableReference("y"),
                '+',
                new Constant(2)
            )
        );
        Dictionary<string,object> vars = new Dictionary<string, object>();
        vars["x"] = 3;
        vars["y"] = 5;
        Console.WriteLine(e.Evaluate(vars));  // Scrie "21"
        vars["x"] = 1.5;
        vars["y"] = 9;
        Console.WriteLine(e.Evaluate(vars));  // Scrie "16.5"
    }
}

Supraîncărcarea metodelor

Supraîncărcarea metodelor permite mai multor metode din aceeași clasă să aibă același nume cât timp ele au semnături unice. Când se compilează o invocare a unei metode supraîncărcate rezoluția de supraîncărcare pentru a determina metoda specifică de invocat. Rezoluția de supraîncărcare găsește singura metodă care se potrivește cel mai bine cu argumentele și raportează o eroare dacă nici o singură potrivire bună nu poate fi găsită. Următorul exemplu arată rezoluția de supraîncărcare în efect. Comentariul pentru fiecare invocare în metoda UsageExample arată care metodă este de fapt invocată.

using System;
class OverloadingExample
{
    static void F()
    {
        Console.WriteLine("F()");
    }
    static void F(object x)
    {
        Console.WriteLine("F(object)");
    }
    static void F(int x)
    {
        Console.WriteLine("F(int)");
    }
    static void F(double x)
    {
        Console.WriteLine("F(double)");
    }
    static void F<T>(T x)
    {
        Console.WriteLine("F<T>(T)");
    }
    static void F(double x, double y)
    {
        Console.WriteLine("F(double, double)");
    }
    public static void UsageExample()
    {
        F();            // Invoca F()
        F(1);           // Invoca F(int)
        F(1.0);         // Invoca F(double)
        F("abc");       // Invoca F<string>(string)
        F((double)1);   // Invoca F(double)
        F((object)1);   // Invoca F(object)
        F<int>(1);      // Invoca F<int>(int)
        F(1, 1);        // Invoca F(double, double)
    }
}


După cum a arătat exemplul, o metodă particulară întotdeauna poate fi selectată convertind explicit argumentele la tipurile exacte ale parametrilor și/sau furnizând argumentele de tip în mod explicit.

Alți membri funcții

Membri care conțin cod executabil sunt cunoscuți în mod comun ca membri funcții ai unei clase. Secțiunea precedentă descrie metode, care sunt felul principal de membri funcții. Această secțiune descrie celelalte feluri de membri funcții suportate de C#: constructori, proprietăți, indexatori, evenimente, operatori, și finalizatori.

Cele ce urmează arată o clasă generică numită List, care implementează o listă dezvoltabilă de obiecte. Clasa conține câteva exemple ale celor mai comune feluri de membri funcții.

public class List<T>
{
    // Constanta
    const int defaultCapacity = 4;


    // Campuri
    T[] items;
    int count;


    // Constructor
    public List(int capacity = defaultCapacity)
    {
        items = new T[capacity];
    }


    // Proprietati
    public int Count => count; 


    public int Capacity
    {
        get { return items.Length; }
        set
        {
            if (value < count) value = count;
            if (value != items.Length)
            {
                T[] newItems = new T[value];
                Array.Copy(items, 0, newItems, 0, count);
                items = newItems;
            }
        }
    }


    // Indexator
    public T this[int index]
    {
        get
        {
            return items[index];
        }
        set
        {
            items[index] = value;
            OnChanged();
        }
    }
   
    // Metode
    public void Add(T item)
    {
        if (count == Capacity) Capacity = count * 2;
        items[count] = item;
        count++;
        OnChanged();
    }
    protected virtual void OnChanged() =>
        Changed?.Invoke(this, EventArgs.Empty);


    public override bool Equals(object other) =>
        Equals(this, other as List<T>);


    static bool Equals(List<T> a, List<T> b)
    {
        if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
        if (Object.ReferenceEquals(b, null) || a.count != b.count)
            return false;
        for (int i = 0; i < a.count; i++)
        {
            if (!object.Equals(a.items[i], b.items[i]))
            {
                return false;
            }
        }
        return true;
    }


    // Eveniment
    public event EventHandler Changed;


    // Operatori
    public static bool operator ==(List<T> a, List<T> b) =>
        Equals(a, b);


    public static bool operator !=(List<T> a, List<T> b) =>
        !Equals(a, b);
}

Constructori

C# suportă și constructori de instanță și constructori statici. Un constructor de instanță este un membru care implementează acțiunile necesare pentru a inițializa o instanță a unei clase. Un constructor static este un membru care implementează acțiunile necesare pentru a inițializa clasa în sine când este prima dată încărcată.

Un constructor este declarat ca o metodă fără tip de întoarcere și cu același nume ca clasa conținătoare. Dacă declarația unui constructor include un modificator static, ea declară un constructor static. Altfel, ea declară un constructor de instanță.

Constructorii de instanță pot fi supraîncărcați, și pot avea parametri opționali. De exemplu, clasa List<T> declară doi constructori de instanță, unul fără parametri și unul care primește un parametru int. Constructorii de instanță sunt invocați folosind operatorul new. Următoarele instrucțiuni alocă două instanțe List<string> folosind constructorul clasei List cu și fără argumentul opțional.

List<string> list1 = new List<string>();
List<string> list2 = new List<string>(10);


Spre deosebire de alți membri, constructorii de instanță nu se moștenesc, și o clasă nu are nici un alt constructor de instanță decât cei declarați de fapt în clasă. Dacă nici un constructor de instanță nu este furnizat pentru o clasă, atunci unul gol fără parametri este furnizat automat.

Proprietăți

Proprietățile sunt o extensie naturală a câmpurilor. Ambele sunt membri numiți cu tipuri asociate, și sintaxa pentru accesarea câmpurilor și proprietăților este aceeași. Totuși, spre deosebire de câmpuri, proprietățile nu denotă locații de stocare. In schimb, proprietățile au accesori care specifică instrucțiunile de executat când valorile lor sunt citite sau scrise.

O proprietate este declarată ca un câmp, exceptând faptul că declarația se termină cu un accesor get și/sau un accesor set scris între delimitatori { și } în loc de a se termina cu punct și virgulă. O proprietate care are și accesor get și accesor set este o proprietate citire-scriere, o proprietate care are doar un accesor get este o proprietate doar-pentru-citire, și o proprietate care are doar un accesor set este o proprietate doar-pentru-scriere.

Un accesor get corespunde cu o metodă fără parametri cu valoarea de întoarcere având tipul proprietății. Cu excepția când este ținta unei atribuiri, când o proprietate este referită într-o expresie, accesorul get al proprietății este invocat pentru a calcula valoarea proprietății.

Un accesor set corespunde unei metode cu un singur parametru numit value și nici un tip de întoarcere. Când o proprietate este referită ca ținta unei atribuiri sau ca operandul lui ++ sau --, accesorul set este invocat cu un argument care furnizează noua valoare.

Clasa List<T> declară două proprietăți, Count și Capacity, care sunt doar-pentru-citire și, respectiv, citire-scriere. In cele ce urmează, este un exemplu de utilizare a acestor proprietăți.

List<string> names = new List<string>();
names.Capacity = 100;   // Invoca accesorul set
int i = names.Count;    // Invoca accesorul get
int j = names.Capacity; // Invoca accesorul get


Similar cu câmpurile și metodele, C# suportă și proprietăți de instanță și proprietăți statice. Proprietățile statice sunt declarate cu modificatorul static, și proprietățile de instanță sunt declarate fără el.

Accesorul/ii unei proprietăți pot fi virtuali. Când o declarație de proprietate include un modificator virtual, abstract, sau override, el se aplică accesorului/ilor proprietății.

Indexatori

Un indexator este un membru care dă voie obiectelor să fie indexate în același fel ca un tablou. Un indexator este declarat ca o proprietate cu excepția că numele membrului este this urmat de o listă de parametri scrisă între delimitatorii [ și ]. Parametrii sunt disponibili în accesorul/ii indexatorului. Similar proprietăților, indexatorii pot fi citire-scriere, doar-pentru-citire, și doar-pentru-scriere, și accesorul/ii unui indexator pot fi virtuali.

Clasa List declară un singur indexator citire-scriere care primește un parametru int. Indexatorul face posibilă indexarea instanțelor List cu valori int. De exemplu:

List<string> names = new List<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++)
{
    string s = names[i];
    names[i] = s.ToUpper();
}


Indexatorii pot fi supraîncărcați, aceasta însemnând că o clasă poate declara mai mulți indexatori cât tip numărul sau tipurile parametrilor lor diferă.

Evenimente

Un eveniment este un membru care dă voie unei clase sau unui obiect să ofere notificări. Un event este declarat ca un câmp cu excepția că declarația include un cuvânt cheie event și tipul trebuie să fie un tip delegat.

Intr-o clasă care declară un membru eveniment, evenimentul se comportă exact ca un câmp de un tip delegat (cu condiția că evenimentul nu este abstract și nu declară accesori). Câmpul stochează o referință la un delegat care reprezintă gestionarul de eveniment care a fost adăugat la eveniment. Când nici un gestionar de eveniment nu este prezent, câmpul este null.

Clasa List<T> declară declară un singur membru eveniment chemat Changed. care indică faptul că un nou element a fost adăugat la listă. Evenimentul Changed este ridicat de metoda virtuală OnChanged, care mai întâi verifică dacă evenimentul este null (însemnând că nici un gestionar nu este prezent). Noțiunea de ridicare a unui eveniment este precis echivalentă cu invocarea delegatului reprezentat de eveniment — în concluzie, nu există nici un construct special de limbaj pentru ridicarea evenimentelor.

Clienții reacționează la evenimente prin gestionare de evenimente. Gestionarele de evenimente sunt atașate folosind operatorul += și înlăturate folosind operatorul -=. Următorul exemplu atașează un gestionar de eveniment la evenimentul Changed al unui List<string>.

class EventExample
{
    static int changeCount;
    static void ListChanged(object sender, EventArgs e)
    {
        changeCount++;
    }
    public static void Usage()
    {
        List<string> names = new List<string>();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(changeCount);  // Scrie "3"
    }
}


Pentru scenarii avansate în care controlul stocării care stă la baza unui eveniment este dorit, o declarație de eveniment poate furniza explicit accesori add și remove, care sunt întrucâtva similare cu accesorul set al unei proprietăți.

Operatori

Un operator este un membru care definește înțelesul aplicării unui operator de expresie particular pe instanțe ale unei clase. Trei tipuri de operatori pot fi definiți: operatori unari, operatori binari, și operatori de conversie. Toți operatorii trebuie să fie declarați ca public și static.

Clasa List<T> declară doi operatori, operator == și operator !=, și deci dă înțeles nou expresiilor care aplică aceste operații pe instanțe List. Specific, operatorii definesc egalitatea a două instanțe List<T> precum compararea fiecărui obiect conținut folosind metodele lor Equals. Următorul exemplu folosește operatorul == pentru a compara două instanțe List<int>.

List<int> a = new List<int>();
a.Add(1);
a.Add(2);
List<int> b = new List<int>();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b);  // Scrie "True"
b.Add(3);
Console.WriteLine(a == b);  // Scrie "False"


Primul Console.WriteLine scrie True deoarece cele două liste conțin același număr de obiecte cu aceleași valori în aceeași ordine. Dacă List<T> nu definea operator ==, primul Console.WriteLine ar fi afișat False deoarece a și b referă instanțe List<int> diferite.

Finalizatori

Un finalizator este un membru care implementează acțiunile necesare pentru a finaliza o instanță a unei clase. Finalizatorii nu pot avea parametri , ei nu pot avea modificatori de accesibilitate, și ei nu pot fi invocați explicit. Finalizatorul pentru o instanță este invocat automat în timpul „colectării gunoiului”.

„Colectorulului de gunoi” îi este permisă libertate mare în deciderea când să colecteze obiectele și să ruleze finalizatorii. Specific, sincronizarea invocărilor finalizatorilor nu este deterministă, și finalizatorii pot fi executați pe oricare fir de execuție. Pentru acestea și alte motive, clasele ar trebui să implementeze finalizatori doar când nici o altă soluție nu este fezabilă.

Instrucțiunea using oferă o mai bună abordare a distrugerii obiectelor.

Tradus din această pagină oficială de documentație Microsoft.

Problemă rezolvată matrice clasa a XI-a mate-info neintensiv

Enunț

Fiind dată o matrice a cu m linii și n coloane cu elemente numere întregi, scrieți un program care:

1. Determină media aritmetică a elementelor matricii;
2. Afișează numărul elementelor pozitive, numărul elementelor negative și numărul elementelor nule din matrice;
3. Tipărește elementele pare aflate pe linii impare și coloane divizibile cu 3.

Rezolvare

#include <fstream>
using namespace std;


const int maxn = 101;
const int maxm = 101;


int main()
{
 int N, M, A[maxm][maxn];
 int nrElPoz = 0, nrElNeg = 0,
  nrElNule = 0, contor = 0,
  ma = 0;


 ifstream in("fisier.in");
 in >> N >> M;
 for (int i = 1; i <= N; ++i)
 {
  for (int j = 1; j <= M; ++j)
  {
   in >> A[i][j];


   // adunarea numerelor pt. calculul
   // mediei aritmetice:
   ma += A[i][j];


   // cele 4 contoare:
   if (A[i][j] == 0)
   {
    ++nrElNule;
   }
   else if (A[i][j] > 0)
   {
    ++nrElPoz;
   }
   else // if (A[i][j] < 0)
   {
    ++nrElNeg;
   }


   if (A[i][j] % 2 == 0 && (i + 1) % 2 != 0 &&
    (j + 1) % 3 == 0)
   {
    ++contor;
   }
  }
 }
 ma /= N * M;
 in.close();


 ofstream out("fisier.out");
 out << "Media aritmetica: " << ma << endl;
 out << "Nr. elemente pozitive: " << nrElPoz << endl;
 out << "Nr. elemente nule: " << nrElNule << endl;
 out << "Nr. elemente negative: " << nrElNeg << endl;
 out << "Nr. elemente pare pe linii impare si coloane div. cu 3: " <<
  contor << endl;


 out.close();

 return 0;
}

Schemă

Notă: în unele locuri s-a referit la A[i][j] prin sintaxa A(i, j).


Explicații

Rezolvarea aceasta citește și scrie în fișier, nu pe ecran. Fișier de intrare: fisier.in, fișier de ieșire: fisier.out.

Cu excepția împărțirii sumei pentru aflarea mediei aritmetice, toate calculele se fac pe măsură ce se citește din fișierul de intrare.