Na początek prosty przykład
using System;
class Test
{
static void Main()
{
LoopIn();
Console.WriteLine();
LoopOut();
}
static void LoopIn()
{
for (int i = 0; i < 3; i++)
{
int x = i + 1;
Console.WriteLine(x);
}
}
static void LoopOut()
{
int x;
for (int i = 0; i < 3; i++)
{
x = i + 1;
Console.WriteLine(x);
}
}
}
Output:
1
2
3
1
2
3
Oczywiście wynik działania obu metod nie jest dla nikogo żadnym zaskoczeniem. Bo niby czemuż miałby być? Różnica między obiema metodami polega wyłącznie na wyprowadzeniu zmiennej lokalnej x poza pętlę, co dla wielu jest oczywiste i traktowane jako dobra praktyka programistyczna. Ja również należę do tej grupy i uważam, że nadmiarowe tworzenie bytów nie jest dobrą praktyką. Przejdźmy jednak do kolejnego przykładu, a zobaczymy, czy nasze ‘dobre praktyki’ mają sens:
using System;
delegate void D();
class Test
{
static void Main()
{
foreach (D d in LoopInWithAnonymous()) d();
Console.WriteLine();
foreach (D d in LoopOutWithAnonymous()) d();
}
static D[] LoopInWithAnonymous()
{
D[] result = new D[3];
for (int i = 0; i < 3; i++)
{
int x = i + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
static D[] LoopOutWithAnonymous()
{
D[] result = new D[3];
int x;
for (int i = 0; i < 3; i++)
{
x = i + 1;
result[i] = delegate { Console.WriteLine(x); };
}
return result;
}
}
Różnica w stosunku do poprzedniego przykładu polega na wprowadzeniu metod anonimowych służących do definicji tablicy delegatów i wykorzystaniu ich do wypisania na ekran wartości zmiennej x.
A oto wynik działania:
1
2
3
3
3
3
A zatem - wyprowadzenie zmiennej x poza pętlę w przypadku użycia metod anonimowych skończyło się całkowicie odmiennym wynikiem! Nie będę ukrywał, że ten przykład łamie moją intuicję co do ‘bezkarności i konieczności’ wykonania tej operacji, ale być może należę do wyjątków i dla innych jest to oczywiste.
Zauważmy, że ten przykład można przepisać z wykorzystaniem wyrażeń lambda:
using System;
delegate void D();
class Test
{
static void Main()
{
foreach (D d in LoopInWithLambda()) d();
Console.WriteLine();
foreach (D d in LoopOutWithLambda()) d();
}
static D[] LoopInWithLambda()
{
D[] result = new D[3];
for (int i = 0; i < 3; i++)
{
int x = i + 1;
result[i] = () => { Console.WriteLine(x); };
}
return result;
}
static D[] LoopOutWithLambda()
{
D[] result = new D[3];
int x;
for (int i = 0; i < 3; i++)
{
x = i + 1;
result[i] = () => { Console.WriteLine(x); };
}
return result;
}
}
I wynik w tym przypadku jest taki sam jak w poprzednim przykładzie.
Pozostaje jeszcze wyjaśnienie dlaczego tak się dzieje, bo odpowiedź na pytanie o cel jak dla mnie pozostaje zagadką, choć wydaje mi się, że prawdopodobnie jest to efekt uboczny stosowania funkcji anonimowych, niezamierzony przez twórców języka.
Wyjaśnienie spróbuję opisać w najbliższym czasie, ale może do tego czasu znajdzie się jakiś śmiałek, który opisze w komentarzach co też takiego się wydarzyło i dlaczego wynik jest taki, a nie inny??? Udało mi się nawet zdobyć nagrodę dla tegoż osobnika (osobniczki) - Arek zgodził się zasponsorować ‘uścisk dłoni prawicy prezia’ przy pierwszej nadarzającej się okazji, czyli np. podczas najbliższego spotkania WG.NET :D, a może dorzuci coś jeszcze :)
Przy przygotowywaniu przykładów posiłkowałem się specyfikacją języka C#, a dokładniej rozdziałem 7.14.4.2 tegoż dokumentu.