Delegaty, zdarzenia i ułatwianie życia z wyrażeniami lambda

0

Zastanawiam się w jakich sytuacjach faktycznie zachodzi potrzeba definiowania własnych delegatów albo ich krótszej odmiany zdarzeń?
Czy jest praktyczne korzystanie z takich ułatwień jak wyrażenia lambda i metody anonimowe? Czy może są to jedynie cukierki składniowe, które należy traktować jako ciekawostkę?

Czy prawidłowe jest stosowanie zdarzenia zdefiniowanego w klasie w sytuacji, kiedy chcemy informować zewnętrzną metodę o stanie obiektu np. "wystąpił błąd połączenia z bazą" albo "nieprawidłowe hasło dla połączenia" czy lepiej wtedy zastosować zwyczajną metodę Error() czy coś w tym rodzaju?

1

Czy jest praktyczne korzystanie z takich ułatwień jak wyrażenia lambda i metody anonimowe? Czy może są to jedynie cukierki składniowe, które należy traktować jako ciekawostkę?

To jest pytanie retoryczne? Sam nazwałeś je "ułatwieniami", czemu z nich nie korzystać? W wielu sytuacjach potrzebuję metodę którą użyję w jednym miejscu i która ma dwie linijki kodu, po co mam jej nadawać nazwę i "zaśmiecać" kod.

4

Hmm, dziwne pytanie. Jak sam stwierdziłeś, to są ułatwienia. Patrz np:

foo.Where(x => x > 2).Select(x => x.ToString()); // wyrażenia lambda
this.Click += OnClick; // zdarzenia
this.Click += (sender, e) => { MessageBox.Show("hmm?"); } // czy wręcz połączenie tych dwóch

Zdarzenia są przydatne, bo fajnie mieć wzorce projektowe wbudowane w język.
A bez wyrażeń lambda C# by był dużo uboższy...

3
MVC napisał(a):

Zastanawiam się w jakich sytuacjach faktycznie zachodzi potrzeba definiowania własnych delegatów albo ich krótszej odmiany zdarzeń?

Zdarzenia to nie jest krótsza odmiana delegata!
Delegat jest takim jakby wskaźnikiem na metodę, natomiast zdarzenie jest wbudowaną w język obsługą wzorca Obserwator. Delegaty służą do wskazania zdarzeniu, które metody ma wykonać w momencie swojego wystąpienia.

Czy jest praktyczne korzystanie z takich ułatwień jak wyrażenia lambda i metody anonimowe? Czy może są to jedynie cukierki składniowe, które należy traktować jako ciekawostkę?

Chociażby całe LINQ jest oparte na lambdach, jakże moglibyśmy ich nie używać?

Czy prawidłowe jest stosowanie zdarzenia zdefiniowanego w klasie w sytuacji, kiedy chcemy informować zewnętrzną metodę o stanie obiektu np. "wystąpił błąd połączenia z bazą" albo "nieprawidłowe hasło dla połączenia" czy lepiej wtedy zastosować zwyczajną metodę Error() czy coś w tym rodzaju?

Zdarzenia służą do informowania raczej o zmianach stanu obiektu. Jeśli mamy jakiś obiekt połączony z bazą, i nagle następuje przerwanie tego połączenia, to jak najbardziej możemy użyć zdarzenia, do poinformowania jakichś tam innych obiektów o tym. Ale to wszystko zależy od konkretnego przypadku, więc nie zawsze zdarzenie w tym momencie ma sens. Zazwyczaj połączenie z bazą jest krytyczne dla działania aplikacji, więc w przypadku jego zgubienia lepiej rzucić wyjątek.

1

Jeszcze jedno.
Wyczytałem, że zgodnie z zaleceniami Microsoftu należy tworzyć metody dla zdarzeń w ten sposób:

 static string CrashMethod(object sender, CarEventArgs e)
        {
            Console.WriteLine("Message from Car object");
            Console.WriteLine("=> {0}", e.msg);
            return e.msg;

        }

Gdzie CarEventArs jest potomkiem klasy EventArgs.
No i w przykładzie wygląda to tak:

  class CarEventArgs : EventArgs
    {
        public readonly string msg;
        public CarEventArgs(string message)
        {
            msg = message;
        }
    }

A ja zrobiłem tak:

  class CarEventArgs
    {
        public readonly string msg;
        public CarEventArgs(string message)
        {
            msg = message;
        }
    }

i też działa - więc po co to dziedziczenie? I tak nie korzystamy ze pól i metod tamtej klasy.

2
MVC napisał(a):

i też działa - więc po co to dziedziczenie? I tak nie korzystamy ze pól i metod tamtej klasy.

Taka konwencja, wszystkie klasy *EventArgs w .NET dziedziczą z EventArgs.

3

Jeszcze dodam przykład gdzie dziedziczenie z EventArgs faktycznie coś zmienia, nawet w przypadku Twojej klasy:
http://msdn.microsoft.com/en-us/library/db0etb8x%28v=vs.100%29.aspx
Możesz napisać po prostu:

public event EventHandler<CarEventArgs> CarEvent;

Generyczny EventHandler<T> wymaga żeby T dziedziczyło z EventArgs. A to całkiem przyjemna klasa, nie trzeba tworzyć niepotrzebnych klas FooEventHandler do każdego FooEventArgs.
Tzn. wymagał, bo z tego co widzę w .NET 4.5 już tego where nie ma i nie jestem pewien dlaczego...

0

Chociażby całe LINQ jest oparte na lambdach, jakże moglibyśmy ich nie używać?
Hmm, parafrazując przykład MSM-a:

        static bool GreaterThanTwo(int i)
        {
            return i > 2;
        }

        static string IntToString(int i)
        {
            return i.ToString();
        }

        static void Main(string[] args)
        {
            List<int> foo = new List<int> { 1, 2, 3, 4 };
            var bar = foo.Where(GreaterThanTwo).Select(IntToString);
        }

;-)

0

O ile jeszcze takie coś nie wygląda najgorzej to gdybyśmy zrezygnowali z lambd w przypadku prawdziwych domknięć (closures) będzie już gorzej, bo pociągnie za sobą konieczność tworzenia dodatkowych obiektów / klas czy wrapperów.

2

Byłbyś taki dobry i dał przykład?

@Azarien już podał przykład domknięcia, ale ja się trochę do tego dopiszę.

Mianowicie ciekawostka, która bywa bardzo przydatna a jednocześnie można sobie łatwo strzelić w stopę nie wiedząc o tym:

// Lista elementów które będziemy filtrować
var elems = new List<int> { 1, 6, 12, 0, 19 };

// Oraz najmniejszy element który nas interesuje - proste, prawda?
var min = 3;

// Wybieramy elementy większe od `min` - dalej, co tutaj skomplikowanego?
var selected = elems.Where(x => x > min);

// Look ma, no hands!
min = 10;

// Zagadka, co się tutaj stanie (skoro pytam to oczywiste co się stanie...)
foreach (var elem in selected) {
    Console.WriteLine(elem);
}
0

Nie jestem specem od pojęć, ale z tego co rozumiem to prawdziwą zaletą domknięcia nie jest leniwość, a to że w ogóle masz dostęp do elems i min. Gdybyś chciał utworzyć tradycyjną metodę musiałbyś przekazać elems i min jako argument lub udostępnić jako pola/właściwości, przynajmniej dla mnie jest to super sprawa w wielu sytuacjach.

1
MSM napisał(a)

Które jest cechą lambd związaną (w tym przypadku) z domknięciami

To nie jest cecha lambd, to jest cecha LINQ. Zapytanie w tym przypadku jest wykonane dopiero podczas pętli foreach, a nie w linijce z definicją zapytania.

Zwracam uwagę na słowo „podczas”:

            // Lista elementów które będziemy filtrować
            var elems = new List<int> { 1, 6, 12, 0, 19 };

            // Oraz najmniejszy element który nas interesuje - proste, prawda?
            var min = 3;

            // Wybieramy elementy większe od `min` - dalej, co tutaj skomplikowanego?
            var selected = elems.Where(x => x > min).Select(x => min);

            // Zagadka, co się tutaj stanie (skoro pytam to oczywiste co się stanie...)
            foreach (var elem in selected)
            {
                Console.WriteLine(elem);
                min++; // aahaha. i z każdą iteracją pętli lambdy widzą nową wartość min
            }
3
4
5

Widzimy że z każdym przebiegiem pętli foreach wyrażenie x => min zwraca nową wartość min, która jest przecież zmieniana podczas pracy pętli…

LINQ działa leniwie.

Jest to o tyle niebezpieczne, że może nam się wydawać że jakaś zmienna lokalna już nie jest potrzebna (i użyjemy jej do nowego celu), a tymczasem cały czas siedzi w zapytaniu...

0
Azarien napisał(a):

Jest to o tyle niebezpieczne, że może nam się wydawać że jakaś zmienna lokalna już nie jest potrzebna (i użyjemy jej do nowego celu), a tymczasem cały czas siedzi w zapytaniu...

Po co w ogóle kiedykolwiek używać zmiennej lokalnej do innego celu niż została utworzona?

2
somekind napisał(a):

Po co w ogóle kiedykolwiek używać zmiennej lokalnej do innego celu niż została utworzona?

Z nudów albo z zemsty :P

1 użytkowników online, w tym zalogowanych: 0, gości: 1