Oto trzy przykładowe scenariusze, które wykorzystują moc daną nam przez Andersa Hejlsberga i spółkę:
1. Sprawdzenie, czy wszystkie textboxy są wypełnione
Przykładzik banalny i w wielu sytuacjach niepraktyczny, ale ładnie prezentujący wygodę pisania kodu “the 3.0 way”.
Stary kod:
1: private bool AreAllTextboxesFilledOldWay()
2: {
3: foreach (Control c in this.Controls)
4: {
5: TextBox tb = c as TextBox;
6: if (tb == null)
7: continue;
8:
9: if (tb.Text == string.Empty)
10: return false;
11: }
12:
13: return true;
14: }
Nowy kod:
1: private bool AreAllTextboxesFilledNewWay()
2: {
3: return this.Controls.OfType<TextBox>().Any(tb => tb.Text == string.Empty) == false;
4: }
Cool.
2. Wysłanie wyłącznie niezbędnych danych z serwera do przeglądarki w postaci JSON
Założenie: na stronie wyświetlamy DropDown z użytkownikami w określonym wieku. Wiek można podać jakkolwiek np przez wpisanie go do textboxa. Po wpisaniu wieku wysyłamy na serwer AJAXową strzałkę po dane przesłane w formacie JSON potrzebne do wyrenderowania listy. Co robiliśmy na serwerze przed erą .NET 3.5? Trzeba było mieć klasę reprezentującą tylko potrzebne do wysłania dane, trzeba było mieć klasę odpowiedzialną za serializację JSON, trzeba wreszcie było odfiltrować zbędnych użytkowników. Wraz z udogodnieniami takimi jak LINQ (filtrowanie danych), typy anonimowe (dbanie o serializowanie wyłącznie potrzebnych w przeglądarce danych), metody rozszerzające (żeby wywołanie “ToJSON()” wyglądało ładnie) i wyrażenia lambda cała operacja sprowadza się do jednej czytelnej (po zapoznaniu się z w/w mechanizmami) linijki kodu.:
1: _usersSrv.GetUsers().Where(u => u.Age == age).Select(u => new { Text = u.Name, Value = u.Id }).ToList().ToJSON();
3. SqlDataReader.HasColumn()
No i ostatni jak na razie przykład. Oto śliczny sposób na dowiedzenie się, czy DataReader posiada kolumnę o nazwie podanej w parametrze:
1: public static bool HasColumn(this SqlDataReader reader, string columnName)
2: {
3: return reader.GetSchemaTable().Rows.Cast<DataRow>().Any(dr => dr[0].ToString() == columnName);
4: }
A wy jakie macie godne pokazania praktyki związane z językowymi nowościami (o ile można w ogóle mówić o “nowościach” tyle miesięcy po premierze C# 3.0)?
chciec wiecej przyklady! :)
ql :)
Wydaje mi się, że fakt istnienia LINQ i metod rozszerzających nie powinien służyć za usprawiedliwienie stylu kodowania “byleby zmieścić wszystko w jednym wyrażeniu”, za który przez lata programistów straszono, że zacytuję pewnego znanego wszystkim Zine’owego blogera, ucinaniem rączek. Niestety bardzo często do tego się to sprowadza (do usprawiedliwania, nie rozczłonkowania).):
Przyjrzyjmy się, co właściwie robi ta “_czytelna_” linia:
_usersSrv.GetUsers().Where(u => u.Age == age).Select(u => new { Text = u.Name, Value = u.Id }).ToList().ToJSON();
1) przygotowuje zbiór danych:
_usersSrv.GetUsers()
2) deklaruje przekształcenia do wykonania na zbiorze – selekcję i projekcję:
.Where(u => u.Age == age).Select(u => new { Text = u.Name, Value = u.Id })
3) przekształca zbiór zgodnie z wcześniejszymi deklaracjami i zwraca wynik w postaci listy:
.ToList()
4) konwertuje listę par etykieta-wartość do łańcucha w notacji JSON:
.ToJSON()
Podumowując, w jednej linii dostajemy wyrażenie składające się z 5 wywołań metod i realizujące 4 zadania. Czy to oby na pewno dobry pomysł? Według mnie – taki sobie.
Osobiście rozbiłbym to wyrażenie na kilka mniejszych, mając na uwadze właśnie odpowiedzialności poszczególnych fragmentów:
1) zdefiniuj zbiór danych:
UserCollection users = _usersSrv.GetUsers();
2) zdefiniuj przekształcenia dla zbioru*:
var query =
from u in users
where u.Age == age
select new { Text = u.Name, Value = u.Id };
3) wygeneruj dane w formacie JSON uwzlędniając zadeklarowane przekształcenia na zbiorze danych (konwersję do listy pominę, gdyż ja bym metodę ToJson zaimplementował jako rozszerzenie IEnumerable
string formattedData = query.ToJson();
W wyniku dostajemy taki fragment kodu:
UserCollection users = _usersSrv.GetUsers();
var query =
from u in users
where u.Age == age
select new { Text = u.Name, Value = u.Id };
string formattedData = query.ToJson();
który jest może nieco dłuższy, ale według mnie czytelniejszy i łatwiejszy w utrzymaniu. Po jednym zerknięciu na kod możemy powiedzieć, że w pierwszym wyrażeniu uzyskujemy jakiś zbiór, w drugim jakieś jego przekształcenia, a w trzecim jakiś wynik na ich podstawie, czyli możemy na bardzo ogólnym poziomie odtworzyć logikę. Gdy wszystko zapiszemy w jednej linii, po jednym zerknięciu możemy co najwyżej zakrzyknąć “D’oh!”.
P.S. Tak przy okazji, o tym, że zbyt intensywnie wykorzystując składnię LINQ można się zapędzić w kozi róg pisał dziś Ayende Rahien w poście o dużo mówiącym tytule “Unreadable LINQ”:
http://ayende.com/Blog/archive/2008/08/02/Unreadable-Linq.aspx
______
* Tak naprawdę nie byłoby większym złem połączenie punktów 1) i 2) w jeden, pod warunkiem, że wyrażenie pobierające zbiór danych jest trywialne (proste wywołanie metody lub właściwości).
@Gutek: to zaiste dobry pomysł, w powyższym przypadku została ona dopięta do object przy wykorzystaniu biblioteki Newtonsoft, stąd ToList().
W tym poście można znaleźć kilka dodatkowych “skróceń” osiągniętych dzięki LINQ: http://igoro.com/archive/7-tricks-to-simplify-your-programs-with-linq/ .
@apl:
Oczywiście, że pakowanie WSZYSTKIEGO co się da w jedną linię jest bez sensu. Gdybym uważał że ma to sens to ten post zawierałby 100 przykładów a nie 3 :).
Efekt taki jak w punkcie 2 można było i tak osiągnąć wcześniej, jednak jak by to wyglądało… Moim zdaniem w tym konkretnym przypadku robijanie owej linii na 4 fragmenty można spokojnie zastąpić umieszczeniem jej w metodzie o wszystkomówiącej nazwie, np GetUsersByAgeToJSON(int age).
A na koniec: implementacja ToJSON jako rozszerzenie IEnumerable
Przykład 1 jest wg mnie zapisany nieelegancko. Jeżeli już bawimy się w takie skróty napisałbym().All(tb => tb.Text != string.Empty);
private bool AreAllTextboxesFilledNewWay()
{
return this.Controls.OfType
}
@MichalCz:
Oczywiście to rozwiązanie jest lepsze, dzięki.
Zgodzę się z Olkiem, że takie upychanie w jednolinijkowce nie sprzyja czytelności. Przypominają mi się wszelkie wariacje na temat “:?”.(this T controlToInvokeOn, Func code) where T : Control
Jeśli zaś chodzi o ciekawe nowe cechy C# 3.0 to extension methods i lambdy są absolutnie genialne.
Podam mój ulubiony przykład. Typowym idiomem w aplikacjach desktopowych jest znane
delegate void FunHandler();
void Fun()
{
if (control.InvokeRequired)
{
control.Invoke(Fun);
}
else
{
control.Text = “blah blah”;
}
}
Aktualnie metodę Fun można uprościć do postaci:
void Fun()
{
control.Invoke(() => control.Text = “blah blah”);
}
oczywiście nasz nowy Invoke może też zwracać wartość. Wszystko dzięki dwóm metodom rozszerzającym(?) o postaci:
static class ControlEx
{
public static TResult Invoke
{
if (controlToInvokeOn.InvokeRequired)
{
return (TResult)controlToInvokeOn.Invoke(code);
}
else
{
return (TResult)code();
}
}
public delegate void Func();
public static void Invoke(this Control controlToInvokeOn, Func code)
{
if (controlToInvokeOn.InvokeRequired)
{
controlToInvokeOn.Invoke(code);
}
else
{
code();
}
}
}
Btw: Czy silnik zine.net.pl posiada obsługę tagów w komentarzach? Mam na myśli [code] ;).
Filip
Chcialbym sie podzielic malymi fragmentami kodu, bedacymi przspieszaczami czasu. Podobny tekst opublikowal