Menu / szukaj

LINQ nie takie straszne

Coraz częściej w projektach zaczynam używać LINQ i zaczynam dostrzegać zalety. Nie dość, że można w ten sposób trochę pisania kodu oraz zyskać na przejrzystości. Poniżej zamieszczę parę przykładów, w których pomału przerzucam się na LINQ. W celach demonstracyjnych stwórzmy klasę Person, której obiekty będą reprezentowały osoby.

class Person : IComparable
  {
    public Person(string name, string surname, int age, bool married)
    {
      this.Name = name;
      this.Surname = surname;
      this.Age = age;
      this.Married = married;
    }

    public Person()
    {}

    public string Name { get; set; }

    public string Surname { get; set; }

    public int Age { get; set; }

    public bool Married { get; set; }

    public int CompareTo(object obj)
    {
      Person otherPerson = obj as Person;
      if (otherPerson != null)
        return Surname.CompareTo(otherPerson.Surname) == 0 ? (Name.CompareTo(otherPerson.Name) == 0
           ? Age.CompareTo(otherPerson.Age)
           : Name.CompareTo(otherPerson.Name)) : Surname.CompareTo(otherPerson.Surname);
      else
        throw new ArgumentException("Object is not a Person");
    }

  }

Jak widać klasa ma cztery własności:

  • imię,
  • nazwisko,
  • wiek,
  • informację, czy ktoś ma żonę lub męża.

Dodatkowo w klasie zostały zdefiniowane dwa konstruktory. Jeden domyślny, a drugi przypisujący wartości do wcześniej wspomnianych własności. Klasa również implementuje interfejs IComparable, ale o tym później.

Pierwszy scenariusz, w którym chcę zademonstrować wyższość LINQ nad klasycznym podejściem jest sytuacja w której należy wybrać obiekty spełniające określone kryteria. Dla przykładu załóżmy, że należy wybrać obiekty, które spełniają następujące warunki:

  • osoby muszą mieć więcej niż 20 lat,
  • osoby są żonate, bądź też zamężne.

Standardowo takie problem rozwiązuje się budując pętlę, która przechodzi przez wszystkie elementy i sprawdza, czy dany obiekt spełnia zadane kryteria. W sytuacji, gdy kryteria zostają spełnione dany obiekt kopiuje się do docelowej listy. Powyższy algorytm prezentuje poniższy kod:

List<Person> filtered = new List<Person>();
foreach (Person pearson in people)
{
  if (pearson.Age > 20 && pearson.Married)
  {
    filtered.Add(pearson);
  }
}

To samo można zrobić teoretycznie za pomocą jednej linijki kodu przy pomocy LINQ:

var filtered2 = from currentPearson in people
        where currentPearson.Age > 20 && currentPearson.Married
        select currentPearson;

Wydaje mi się, że rozwiązanie za pomocą LINQ jest o wiele bardziej czytelniejsze i łatwiejsze do późniejszego utrzymania.

Kolejny przykład jest modyfikacją obecnego. Dodajmy do przykładu, że chcemy pobrać tylko dwa pierwsze elementy. W klasycznym podejściu można zrobić to w następujący sposób:

List<Person> filtered = new List<Person>();
for (int i = 0; i < people.Count && filtered.Count < 2; i++)
{
  if (people&#91;i&#93;.Age > 20 && people[i].Married)
  {
    filtered.Add(people[i]);
  }
}

W LINQ kod ten można zastąpić w następujący sposób:

var filtered2 = people.Where(p => p.Age > 20 && p.Married).Take(2);

I ponownie można zauważyć, że rozwiązanie LINQ jest bardziej proste i czytelniejsze.

W moim kodzie dość często pojawia się również potrzeba pobrania pierwszego elementu z kolekcji. W klasyczny sposób należy sprawdzić czy kolekcja została poprawnie zainicjalizowana oraz czy zawiera jakieś elementy. Dopiero potem można pobrać interesujący nas element.

Person person1;
if (people != null && people.Count > 0)
{
  person1 = people[0];
}

W LINQ zadanie to jest banalne:

Person person2 = people.FirstOrDefault();

Ostatni przykład związany jest z sortowaniem. Klasycznie, aby posortować kolekcję należy w obiekcie zaimplementować interfejs IComparable i użyć metody Sort(). Implementację tego interfejsu można znaleźć na początku artykułu w miejscu gdzie przedstawiona klasa Person. Rozwiązanie jest stosunkowo proste, jeśli chce się posortować listę zawsze w ten sam sposób. Ale już w sytuacji, kiedy chcemy, aby była możliwość sortowania na różne sposoby sprawa się już trochę komplikuje.

people.Sort();

Sprawa wygląda o wiele bardziej optymistycznie w LINQ. Wystarczy tylko jedna linijka, aby osiągnąć to co dała nam implementacja interfejsu IComparable:

var sortedPeople = people.OrderBy(u => u.Surname).ThenBy(u => u.Name).ThenBy(u => u.Age);

Dodatkowo, jeśli chcemy w następnej linijce chcemy zmienić kolejność sortowania to nic nie stoi, ku temu na przeszkodzie.

sortedPeople = people.OrderBy(u => u.Surname).ThenBy(u => u.Name).ThenByDescending(u => u.Age);

Jak również nie ma przeszkód, aby przed sortowanie zawęzić wyniki i wybrać tylko te elementy, które nas interesują:

sortedPeople = people.Where(p => p.Age > 20 && p.Married).OrderBy(u => u.Surname)
.ThenBy(u => u.Name).ThenByDescending(u => u.Age);

Komentarze

avatar
aks
Odpowiedz

Cześć,
potrzebuję dopasować zapytanie w linq do wzorca regularnego mam coś takiego ale mi to nie działa…
Regex rgx = new Regex(pattern);
string passtru = db.Users.Where(s => s.login == log && s.password.rgx.IsMatch(pattern)).FirstOrDefault().name;

jakaś podpowiedź?

avatar
Michał Jankowski
Odpowiedz

Łap przykład:

var names = new string[] { „Michal”, „Adam”};

string pattern = „M*”;
Regex rgx = new Regex(pattern);

var nameOnM = names.Where(name => rgx.IsMatch(name)).FirstOrDefault();

avatar
Yelrty
Odpowiedz

Mimo że lubię LINQ, uważam że należy używać go z głową i rozumieć jak działa.
Ja w firmie znajduję np. takie kwiatki:

private int Get(IList lista)
{
var filteredItems = lista.Where(item => CanSomething(item));
var value = filteredItems.Count() > 0 ? filteredItems.First().Value : 0;
return paymentValue;
}

A można przecież napisać też czytelnie i wydajniej (bez LINQ) :
private int Get(IList lista)
{
for (var i = 0; i < lista.Count; i++)
{
if (CanSomething(lista[i]))
return lista[i].Value;
}
return 0;
}

Dodaj komentarz

imię*

e-mail* (nie publikowany)

strona www

This site uses Akismet to reduce spam. Learn how your comment data is processed.

.