interface și tipuri generice în C# (.NET)

OOP modern și programare asincronă C# 2.0+ (default interface methods în C# 8+)

Definește contracte decuplate prin interfețe și scrie clase sau metode reutilizabile și sigure la compilare prin parametri generici <T>.

Sintaxă C#
public interface IRepository<T> where T : class { Task<T?> GetByIdAsync(int id); }

Ce face interface și tipuri generice <T>

O interfață (interface) definește un contract de metode și proprietăți pe care orice clasă sau structură îl poate implementa. În arhitectura aplicațiilor moderne .NET (ASP.NET Core), interfețele stau la baza Dependency Injection (DI) și a testării unitare: un controller sau serviciu depinde de o abstracție precum IEmailSender sau IOrderRepository, nu de implementarea concretă.

Tipurile și metodele generice (<T>) îți permit să scrii algoritmi, containere (precum List<T>, Result<T>, PagedList<T>) și interfețe (IRepository<T>) fără a duplica codul pentru fiecare tip de date și fără a pierde siguranța tipurilor prin conversii la object (boxing / unboxing). Prin clauza where T : ... poți impune constrângeri asupra parametrului generic, astfel încât compilatorul să garanteze că T implementează o anumită interfață, este o clasă (class) sau are un constructor fără parametri (new()).

Parametri, metode și opțiuni

Parametru / Metodă Descriere
public interface INume { ... } Definește un contract; prin convenție în .NET, numele interfețelor încep întotdeauna cu litera I.
class Serviciu : IInterfata1, IInterfata2 O clasă poate moșteni cel mult o clasă de bază, dar poate implementa oricâte interfețe.
Clasa<T> / Metoda<T>(T param) Introduce un parametru de tip generic rezolvat la compilare fără costuri de boxing.
where T : class / struct / notnull Constrânge T să fie un tip referință, un tip valoare non-nullable sau orice tip non-null.
where T : IComparable<T>, new() Obligă tipul T să implementeze interfața specificată și să expună un constructor public fără parametri.

Exemple

Exemplul 1: Tip generic Rezultat<T> și interfață decuplată pentru procesarea plăților

Definim o interfață IProcesatorPlata și o înregistrare generică Rezultat<T> care încapsulează elegant succesul sau eroarea unei operațiuni.

IProcesatorPlata procesator = new ProcesatorCardStripe();
Rezultat<string> raspuns = procesator.Incaseaza(250.00m, "RON");

if (raspuns.Succes)
{
    Console.WriteLine($"Plată reușită! ID tranzacție: {raspuns.Valoare}");
}

public record Rezultat<T>(bool Succes, T? Valoare, string? Eroare)
{
    public static Rezultat<T> Ok(T valoare) => new(true, valoare, null);
    public static Rezultat<T> Esec(string eroare) => new(false, default, eroare);
}

public interface IProcesatorPlata
{
    Rezultat<string> Incaseaza(decimal suma, string moneda);
}

public class ProcesatorCardStripe : IProcesatorPlata
{
    public Rezultat<string> Incaseaza(decimal suma, string moneda) =>
        suma > 0
            ? Rezultat<string>.Ok($"ch_stripe_{suma:0}_{moneda}")
            : Rezultat<string>.Esec("Suma trebuie să fie pozitivă.");
}

Ieșire în consolă

Plată reușită! ID tranzacție: ch_stripe_250_RON

Exemplul 2: Metodă generică cu constrângere where T : IEntity

Datorită constrângerii where T : IEntitate, metoda generică GasesteDupaId poate accesa proprietatea .Id pe orice tip T care implementează interfața.

var clienti = new List<Client> { new(10, "SC Alfa SRL"), new(20, "SC Beta SA") };
Client? gasit = GasesteDupaId(clienti, 20);
Console.WriteLine($"Entitate găsită: {gasit?.Denumire}");

static T? GasesteDupaId<T>(IEnumerable<T> colectie, int idCautat) where T : IEntitate
    => colectie.FirstOrDefault(x => x.Id == idCautat);

public interface IEntitate
{
    int Id { get; }
}

public record Client(int Id, string Denumire) : IEntitate;

Ieșire în consolă

Entitate găsită: SC Beta SA

Greșeli frecvente

  • Folosirea tipului object în locul unui parametru generic <T> — stocarea unui tip valoare (int, decimal, struct) într-un object provoacă o alocare pe heap (boxing) și necesită conversie explicită (cast) nesigură la citire.
  • Uitarea constrângerii where T : class sau where T : IInterfata atunci când încerci să accesezi metode specifice pe o variabilă de tip generic T în interiorul metodei.

Funcții și concepte înrudite

  • Class, Properties { get; init; } și Primary Constructors: Definește clase moderne cu proprietăți auto-implementate, inițializare sigură (init, required) și constructori primari conciși.
  • record și imutabilitate: Definește într-o singură linie modele de date imutabile cu egalitate bazată pe valoare, afișare automată și clonare nedistructivă (with).
  • List<T>: Stochează o listă generică redimensionabilă dinamic, cu acces rapid după index și metode de adăugare, ștergere și căutare.
  • async / await și Task<T>: Execută operațiuni I/O neblocante (rețea, baze de date, fișiere) și coordonează sarcini concurente cu Task și Task.WhenAll.