PredicateSmak NR.2

Predicate to gotowy szablon delegaty/wskaźnika do metody.

Metoda referowana w Predicate musi zwrócić wartość true/false.

Spełnia ona inną role niż klasyczne szablony delegat jak “Action” i “Func”, które opisałem wcześniej. Pytania brzmi na czym polega różnica.

Możesz sobie zadać pytanie dlaczego w ogóle z nich korzystać. Dla czytelności systemu może łatwiej jest użyć delegaty Func<T,Bool>. Dlaczego Predicate w ogóle istnieje?

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();

        Func<bool> check = p.Check;
        Func<int,bool> checkIf = p.CheckIfNumberMoreThan10;

        //Predicate<> bez parametrow nie istnieje
        Predicate<int> checkif = p.CheckIfNumberMoreThan10;
    }

    public bool Check()
    {
        return true;
    }

    public bool CheckIfNumberMoreThan10(int n)
    {
        return n > 10;
    }
}

Otóż istnieje różnica pomiędzy nimi.

Predicate<int T>

Pierwsza rzecz, która rzuca się w oczy to fakt, że Predicate przyjmuje tylko jeden parametr określający jaki 1 argument ma przyjąć metoda.

Predicate<int T> only one

Jeśli więc mamy metodę która przyjmuje więcej parametrów to warto skorzystać z delegaty Func.

To oczywiście zadaje ponownie inne ważne pytanie “po co ją w ogóle stosować”?

Odpowiedź na pytanie czy w ogóle używać delegaty Predicate leży w samym frameworku .NET. Sam framework .NET najczęściej korzysta z delegaty Func.

Oto przykłady z LINQ;

Przy projekcji typów.

Select

 list.Select(x => x.Property)

Przy filtrowaniu obiektów.

Where LINQ

list.Where(x => x.Value == otherValue)

Przy scalaniu dwóch kolekcji swoim kluczami.

JOIN Linq

 list.Join(otherList, x => x.FirstKey, y => y.SecondKey, ...)

Gdzie jest więc używany Predicate.

W .NET 2.0 był on używany do znajdowania elementów w kolekcjach. W czasach gdy nie było jeszcze LINQ.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> list = new List<int> { 11, 2, 13, 9, 22 };

        Predicate<int> predicate = new Predicate<int>(greaterThanTen);

        List<int> newList = list.FindAll(predicate);
    }

    static bool greaterThanTen(int arg)
    {
        return arg > 10;
    }
}

Widać wyraźnie, że to był wtedy Trend. Wszystkie metody Find* dla kolekcji generycznych w tym wypadku listy przyjmują delegatę Predicate. Tak samo jest z metodami “Remove*”.

FindAll

W C# 2.0 delegata Predicate była deklarowana najczęściej w taki sposób przy użyciu anonimowych metod.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> list = new List<int> { 14, 33, 13 };

        List<int> newList = list.FindAll(delegate(int number)
                           {
                               return number > 10;
                           });
    }
}

Teraz gdy C# 3.0 i Linq istnieje ten sam kod można napisać dużo lepiej. Nie jawnie użyć delegaty Predicate przy pomocy wyrażenia Lambda.

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        List<int> list = new List<int> { 4, 55, 33, 11, 7, 8, 31 , 12 };

        List<int> newList = list.FindAll(i => i > 10);
    }
}

Oczywiście w .NET 3.5 gdzie Linq zostało zaprezentowane do dyspozycji mam metodę Where, która spełnia ten sam cel.

List<int> list = new List<int> { 4, 55, 33, 11, 7, 8, 31, 12 };

var listA = list.Where(n => n > 10).ToList();

Wszystko więc wskazuje na to, że delgata Predicate i metody takie jak FindAll istnieją tylko po to by kod był kompatybilny wstecz.

Nie istnieje więc powód by stosować tą delegatę w swoim kodzie.

Predicate jest specjalnym przykładem Func<T,bool> i było on potrzebny ponieważ delegaty jak Func wtedy nie istniały.

Zapewne gdyby delegaty takie jak Action, Func zostały przedstawione wcześniej Predicate w ogóle by nie istniał.

Nie ma sensu mieć dwie delegaty, które spełniają taką sama funkcję.

Nawet jeśli stworzy kod, który ma iterować po kolekcji według delegaty Predicate.

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();

        Predicate<Person> predicatePointer = p.MoreThan20;
        Person[] lstPeople = (new Person[]
        {
           new Person(){ Name = "Tomek", Age = 23},
           new Person(){ Name = "Franko", Age = 21},
           new Person(){ Name = "Doman", Age = 18},
           new Person(){ Name = "Alicja", Age = 19},
        });

        Person tempPerson = Array.Find(lstPeople, predicatePointer);
        Console.WriteLine("Person older than 22 is :" + tempPerson.Name);
        Console.ReadKey();
    }

    public bool MoreThan22(Person employee)
    {
        return employee.Age > 22;
    }
}

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Obecnie zawsze może użyć metody Where.

class Program
{
    static void Main(string[] args)
    {
        Program p = new Program();

        Func<Person,bool> predicatePointer = p.MoreThan20;
        Person[] lstPeople = (new Person[]
        {
           new Person(){ Name = "Tomek", Age = 23},
           new Person(){ Name = "Franko", Age = 21},
           new Person(){ Name = "Doman", Age = 18},
           new Person(){ Name = "Alicja", Age = 19},
        });

        Person tempPerson = lstPeople.Where(predicatePointer).First();
        Console.WriteLine("Person older than 22 is  :" + tempPerson.Name);
        Console.ReadKey();
    }

    public bool MoreThan20(Person employee)
    {
        return employee.Age > 22;
    }
}

To wszystko co musisz wiedzieć na temat delegaty Predicate.