Unirea colecțiilor
Unirea în LINQ este utilizată pentru a combina două seturi de tipuri diferite într-unul singur. Pentru unire se folosește operatorul join sau metoda Join(). De obicei, această operațiune se aplică la două seturi care au un criteriu comun.
Operatorul join
Operatorul join are următoarea sintaxă formală:
from obiect1 in set1
join obiect2 in set2 on obiect2.proprietate2 equals obiect1.proprietate1
După operatorul join urmează selecția obiectelor din a doua colecție. După operatorul on se specifică criteriul de unire - proprietatea obiectului din a doua selecție, iar după operatorul equals - proprietatea obiectului din prima selecție, căreia trebuie să-i fie egală proprietatea obiectului din a doua selecție. Dacă aceste proprietăți sunt egale, ambele obiecte ajung în rezultatul final.
De exemplu, avem două clase:
record class Person(string Name, string Company);
record class Company(string Title, string Language);
Clasa Person reprezintă un utilizator și stochează două proprietăți: Name (numele utilizatorului) și Company (compania utilizatorului). Clasa Company reprezintă o companie și stochează două proprietăți: Title (numele companiei) și Language (limbajul principal de programare al companiei).
Obiectele ambelor clase vor avea un criteriu comun - numele companiei. Vom uni aceste două seturi de obiecte după acest criteriu:
Person[] people =
{
new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
new Company("Microsoft", "C#"),
new Company("Google", "Go"),
new Company("Oracle", "Java")
};
var employees = from p in people
join c in companies on p.Company equals c.Title
select new { Name = p.Name, Company = c.Title, Language = c.Language };
foreach (var emp in employees)
Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})");
Folosind expresia:
join c in companies on p.Company equals c.Title
obiectul p din lista people (adică obiectul Person) se unește cu obiectul c din lista companies (adică obiectul Company), dacă valoarea proprietății p.Company coincide cu valoarea proprietății c.Title. Rezultatul unirii va fi un obiect de tip anonim care va conține trei proprietăți. Rezultatul afișării:
Tom - Microsoft (C#)
Sam - Google (Go)
Mike - Microsoft (C#)
Observați că în array-ul people există obiectul new Person("Bob", "JetBrains"), dar în array-ul companies nu există o companie cu numele "JetBrains", deci acest obiect nu apare în rezultat. La fel, în lista people nu există obiecte Person care să corespundă companiei new Company("Oracle", "Java").
Metoda Join
Ca alternativă, putem folosi metoda Join():
Join(IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, TInner, TResult> resultSelector);
Metoda Join() primește patru parametri:
- al doilea set, pe care îl unim cu cel curent
- delegat care definește proprietatea obiectului din setul curent, după care se face unirea
- delegat care definește proprietatea obiectului din al doilea set, după care se face unirea
- delegat care definește noul obiect în rezultatul unirii
Rescriem exemplul anterior folosind metoda Join:
Person[] people =
{
new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
new Company("Microsoft", "C#"),
new Company("Google", "Go"),
new Company("Oracle", "Java")
};
var employees = people.Join(companies, // al doilea set
p => p.Company, // proprietatea selector a obiectului din primul set
c => c.Title, // proprietatea selector a obiectului din al doilea set
(p, c) => new { Name = p.Name, Company = c.Title, Language = c.Language }); // rezultat
foreach (var emp in employees)
Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})");
Metoda GroupJoin
Metoda GroupJoin() nu doar unește secvențele, ci realizează și gruparea.
GroupJoin(IEnumerable<TInner> inner,
Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector,
Func<TOuter, IEnumerable<TInner>, TResult> resultSelector);
Metoda GroupJoin() primește patru parametri:
- al doilea set, pe care îl unim cu cel curent
- delegat care definește proprietatea obiectului din colecția curentă, după care se face unirea și gruparea
- delegat care definește proprietatea obiectului din a doua colecție, după care se face unirea
- delegat care definește noul obiect în rezultatul unirii. Acest delegat primește un grup - obiectul colecției curente, după care se face gruparea, și setul de obiecte din a doua colecție care compun grupul
De exemplu, să luăm array-urile people și companies definite mai sus și să grupăm toți utilizatorii după companii:
Person[] people =
{
new Person("Tom", "Microsoft"), new Person("Sam", "Google"),
new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"),
};
Company[] companies =
{
new Company("Microsoft", "C#"),
new Company("Google", "Go"),
new Company("Oracle", "Java")
};
var personnel = companies.GroupJoin(people, // al doilea set
c => c.Title, // proprietatea selector a obiectului din primul set
p => p.Company, // proprietatea selector a obiectului din al doilea set
(c, employees) => new // rezultat
{
Title = c.Title,
Employees = employees
});
foreach (var company in personnel)
{
Console.WriteLine(company.Title);
foreach(var emp in company.Employees)
{
Console.WriteLine(emp.Name);
}
Console.WriteLine();
}
Rezultatul execuției programului va fi:
Microsoft
Tom
Mike
Google
Sam
Oracle
Metoda GroupJoin, la fel ca metoda Join, primește toți aceiași parametri. Doar că acum, în ultimul parametru - delegat, se transmit obiectul companiei și setul de utilizatori ai acestei companii.
Observați că pentru compania "Oracle" în array-ul people nu există utilizatori, totuși pentru aceasta se creează un grup.
Metoda Zip
Metoda Zip() combină secvențial elementele corespunzătoare ale secvenței curente cu cele ale celei de-a doua secvențe, care este transmisă metodei ca parametru. Adică primul element din prima secvență este combinat cu primul element din a doua secvență, al doilea element din prima secvență este combinat cu al doilea element din a doua secvență și așa mai departe.
Rezultatul metodei este o colecție de tupluri, în care fiecare tuplu conține o pereche de elemente corespunzătoare din ambele secvențe:
var courses = new List<Course> { new Course("C#"), new Course("Java") };
var students = new List<Student> { new Student("Tom"), new Student("Bob") };
var enrollments = courses.Zip(students);
foreach (var enrollment in enrollments)
Console.WriteLine($"{enrollment.First} - {enrollment.Second}");
record class Course(string Title); // curs
record class Student(string Name); // student
Aici metoda Zip combină elementele corespunzătoare din listele courses și students. Rezultatul este o nouă colecție care conține un set de tupluri. Fiecare tuplu din ea are două elemente.
Primul element, din proprietatea First, reprezintă obiectul colecției curente (în acest caz, obiectul Course), iar al doilea element, din proprietatea Second, reprezintă obiectul celei de-a doua secvențe (în acest caz, obiectul Student). Afișarea pe consolă:
Course { Title = C# } - Student { Name = Tom }
Course { Title = Java } - Student { Name = Bob }