Jak być lepszym C# Developerem – 5 wskazówek
Większość osób nie uwierzy, ponieważ jako ludzie mamy tendencję do zakładania, że umiejętności i doświadczenie są cechami wrodzonymi, z którymi rodzą się tylko najbardziej utalentowani programiści.
Choć częściowo może być to prawdą, to jeśli chodzi o umiejętność tworzenia oprogramowania, większość tego, co czyni wyjątkowego kodera wyjątkowym, nie sprowadza się do naturalnego talentu, ale do celowej praktyki i umiejętności krytycznego myślenia.
Wskazówki w tym artykule zaproponował nam Stefan Djokic, Senior Software Engineer pracujący w EXLRT, cyfrowej agencji customer experience specjalizującej się w handlu detalicznym, podróżach i hotelarstwie pracującej z dużymi firmami, takimi jak Adidas, IBM czy Disneyland Paris.
Bardzo polecam obserwowanie go na Linkedinie, ponieważ zawsze dzieli się wartościowymi treściami na temat C#, .NET i nie tylko!
Używaj yield, gdzie tylko możesz
W pierwszej wskazówce przekazanej przez Stefana, mówi o tym, że programiści zwykle używają zmiennych tymczasowych do przechowywania elementów kolekcji, podczas gdy używają pętli do tworzenia nowej kolekcji elementów.
Sprawdźmy jego przykładowy kod:
List<int> tempResult = new();
foreach(var item in collection)
{
tempResult.Add(item);
}
return tempResult;
W tym przypadku Stefan zaleca użycie yield return
, ponieważ zapewni to stanową i niestandardową iterację:
Kontroler jest zwracany do metody wywołującej za każdym razem, gdy napotkana i wykonana jest instrukcja
yield return
.
Dodał jeszcze:
Co najważniejsze, przy każdym takim wywołaniu informacje o stanie kontrolera są zachowane, aby wykonanie mogło być kontynuowane natychmiast po instrukcji yield, gdy kontroler powróci.
A wynik jest następujący:
foreach(var item in collection)
{
yield return item;
}
Na koniec skomentował kilka swoich uwag, o których należy zawsze pamiętać:
- Nie możemy używać instrukcji
yield return
lubyield break
wewnątrz anonimowych i niebezpiecznych metod. Typem zwracanym metody, w której używamyyield
, powinien byćIEnumerable
lubIEnumerator
. Nie możemy używać instrukcjiyield
return
w blokutry-catch
, ale możemy ją mieć wewnątrz blokutry-finally
.
Pamiętajcie o tym drodzy deweloperzy. Tutaj znajdziecie oryginalny link do tych wskazówek: Używaj yield
gdzie tylko możesz.
Uzyskaj indeks iteracji w pętli foreach
Przy drugiej wskazówce, Stefan mówi o najlepszych praktykach dla indeksów i stawia pytanie:
Jak uzyskać indeks bieżącej iteracji pętli foreach?
W ślad za tym sam odpowiedział na to pytanie, aby rozjaśnić nasze wątpliwości:
Najprostszym sposobem śledzenia indeksów jest ustawienie zmiennej indeksowej przed pętlą i zwiększanie jej w każdej iteracji – tak jak w przypadku pracy w pętli
while
A proponowany przez niego scenariusz jest następujący:
foreach (var (value, i) in users.Select((value, u) => (value, i)))
{
User value = user.value;
int index = user.i;
}
Wyjaśniwszy to, Stefan przechodzi do przedstawienia scenariusza, w którym można to osiągnąć bez użycia zmiennej.
Jest to możliwe, łącząc metodę Select()
LINQ
i ValueTuple
, ale Stefan komentuje, że nie jest to jego ulubiony sposób i szczegółowo opisuje przyczyny:
- Zmniejszona czytelność kodu;
- Gorsza wydajność (miejsce i czas);
- Trudny do utrzymania;
- Ale dlaczego?;
No właśnie. Dlaczego? Jestem pewien, że niektórzy z was zadali już sobie to pytanie.
Dlatego Stefan pokazuje nam najlepszy sposób, aby uzyskać indeks iteracji pętli foreach
:
Użyj pętli for
(jeśli możesz). Użyj właściwości MoveNext()
& Current
.
Kolejna dobra rada i poprawiamy się o 1%. Pamiętaj, że sukces tkwi w wytrwałości. Tutaj znajdziecie oryginalny link do tych wskazówek: Uzyskaj indeks iteracji w pętli foreach.
Nie używaj konkatenacji stringa + w pętlach
Pętle w C# są głównym punktem trzeciej wskazówki i Stefan ostrzega, że nie jest dobrą praktyką używanie konkatenacji stringa +
.
Dzieje się tak dlatego, ponieważ string jest niezmienny, i jak komentuje sam Stefan:
Gdy stworzymy string, nie można go zmienić.
Aby lepiej to zrozumieć, ten świetny senior deweloper podał praktyczny przykład:
Mamy nowy string „Test” – Ten string będzie zajmował miejsce w pamięci na stercie. Zmieniamy oryginalny string na „Test na string”. Utworzymy nowy obiekt string na stercie. Zamiast modyfikować oryginalny string pod tym samym adresem pamięci.
Fragment kodu wygląda tak:
string finalStr = "";
foreach (var str in stringArray)
{
finalStr = finalStr + str;
}
Jeśli jesteś ciekawy, co się stanie, gdy zrobisz to w ten sposób, to główną rzeczą jest możliwa utrata wydajności.
Dlaczego? Ponieważ wielokrotne alokacje pamięci byłyby używane podczas wielokrotnego modyfikowania stringa.
Ulubionym rozwiązaniem tego problemu przez Stefana jest użycie StringBuilder
. Jak stwierdza:
StringBuilder
nie tworzy nowego obiektu w pamięci, ale dynamicznie rozszerza pamięć, aby pomieścić zmodyfikowany string.”
Mając na uwadze takie rozwiązanie, powyższy przykład kodu wyglądałby bezbłędnie w następujący sposób:
StringBuilder builder = new StringBuilder();
foreach( var str in stringArray)
{
builder.Append(str);
}
string finalStr= builder.ToString();
Na koniec inżynier oprogramowania Exlrta stawia pytanie wszystkim deweloperom:
Czy gdzieś w kodzie masz konkatenację
+
w pętli? Czy spotkałeś się z tym problemem w realnej sytuacji?
Zarówno Stefan, jak i ja chcielibyśmy wiedzieć, czy spotkało Was to kiedykolwiek i czy sami zauważyliście jakieś problemy.
Tutaj znajdziecie oryginalny link do tej wskazówki: Nie używaj konkatenacji stringa '+' w pętlach.
Typy zapieczętowane
Żaba z 6 nogami to nazwa, jaką Stefan nadał opowiadaniu o swojej czwartej wskazówce.
Pewnie zastanawiacie się teraz, od kiedy to żaba ma sześć nóg, prawda🐸? Cóż, pozwólcie, że Stefan opowie wam tę mini historię:
A: Dlaczego masz klasę MyFrog, skoro istnieje standardowa klasa Frog? Do tego Twoja żaba ma sześć nóg?? 😮
B: Haha, pozwoliłeś mi na to 😅
A: Jak?
B: Pozwoliłeś mi przedłużyć standardową żabę o dodatkowe nogi. Pozwoliłeś mi odziedziczyć twoją klasę.
A: A mogłem użyć słowa kluczowego
sealed
🤦♂️
Gdyby Stefan napisał książkę o C# wykorzystując tego typu mini historyjki przykładowe i swój sposób wyjaśniania rzeczy, to jestem pewien, że byłby to najlepszy nabytek mojego życia, a książka odniosłaby ogólny sukces (znasz Stefana 😉).
Wracając do wskazówki, głównym powodem (wśród kilku innych, które zobaczymy później), dlaczego używanie typów zapieczętowanych jest dużo lepsze, jest ograniczenie funkcji dziedziczenia.
Obok tego stwierdza:
Kiedy zdefiniujemy klasę jako zapieczętowaną, nie możemy już jej dziedziczyć.
Kolejną wskazówką, którą tutaj podsuwa to, że na metodzie lub właściwości, która nadpisuje wirtualną metodę lub właściwość w klasie bazowej, możemy również użyć słowa kluczowego sealed
.
Zapobiega to nadpisywaniu przez inne klasy pewnych wirtualnych metod lub atrybutów, jednocześnie umożliwiając im dziedziczenie po klasie bazowej.
A jeśli zastanawiasz się, jakie są zalety używania zapieczętowanych typów, to Stefan wymienia tutaj 3 punkty:
- Zabiera użytkownikom klasy funkcję dziedziczenia, aby nie mogli wyprowadzić z niej klasy.
- Najlepiej, jeśli mamy klasę z elementami statycznymi.
- Może prowadzić do poprawy wyników.
Spójrzmy na przykład Stefana!
sealed class Users
{
public string name = "Stefan Djokic";
public void GetName()
{
Console.WriteLine("Name: {0}", name);
}
}
//Derived Class
public class Details : users //Error
{
public int age = 27;
public void GetAge()
{
Console.WriteLine("Age: {0}", age);
}
}
Tutaj znajdziecie oryginalny link do tej wskazówki: Typy zapieczętowane.
Select() vs SelectMany()
W piątej wskazówce, Stefan postanowił podzielić się z nami studium przypadku klienta.
Poznajmy historię:
Klient zażądał wszystkich technologii, które znają twoi pracownicy. ...mamy klasę Pracownik z nazwą pracownika i listą technologii, w których umie pracować
Tak prezentuje się ten scenariusz:
List<Employee> employees = new();
Employee emp1 = new Employee { Name = "Stefan", Skills = new List<string> {"C", "C++", "Java"}};
Employee emp2 = new Employee { Name = "Karan", Skills = new List<string> {"SQL Server", "C#", "ASP.NET"}};
Employee emp3 = new Employee { Name = "Lalit", Skills = new List<string> {"C#", "ASP.NET MVC", "Windows Azure", "SQL Server"}};
employees.Add(emp1);
employees.Add(emp2);
employees.Add(emp3);
Teraz, aby podołać wyzwaniu zaproponowanemu przez swojego klienta, Stefan wymyślił dwa rozwiązania:
.Select()
.SelectMany()
Przede wszystkim Stefan zaproponował przypuszczalny scenariusz z .Select()
:
IEnumerable<List<String>> resultSelect = employees.Select(e=> e.Skills);
foreach (List<String> skillList in resultSelect)
{
foreach (string skill in skillList)
{
Console.WriteLine(skill);
}
}
Sprawdźmy szybko, co Stefan mówi nam o takim sposobie postępowania:
- Używamy 2 pętli.
- Pierwsza pętla przechodzi przez wszystkich pracowników.
- Druga pętla przechodzi przez wszystkie umiejętności każdego pracownika.
Mając to na uwadze, zobaczmy, jak wyglądałby zakładany scenariusz, gdybyśmy użyli .SelectMany()
:
IEnumerable<string> resultSelectMany = employees.SelectMany(emp => emp.Skills);
foreach (string skill in resultSelectMany)
{
Console.WriteLine(skill);
}
Już na pierwszy rzut oka widać, że kod wynikowy jest krótszy przy użyciu .SelectMany()
w porównaniu do .Select()
.
Jeśli zrobimy to z .SelectMany()
, jak twierdzi Stefan, proces wygląda inaczej:
- Od razu dostajemy listę umiejętności.
- Pierwsza i jedyna pętla przechodzi przez umiejętności każdego pracownika.
W ten sposób możemy zaoszczędzić 1 pętlę, co zrekompensuje nam wydajność.
Na koniec proponuje nam jeszcze jedną małą, ale dobrą opcję do rozważenia:
Możemy użyć SelectMany
do „spłaszczenia” kolekcji zagnieżdżonej lub warstwowej do prostej kolekcji jednopoziomowej.
Tutaj znajdziecie oryginalny link do tej wskazówki: Select() vs SelectMany().
Jeszcze raz dziękuję Stefanowi Djokicowi za udostępnienie tych wskazówek i wniesienie wartości do wielkiej i wspaniałej społeczności programistów .NET. Jeśli podobały Ci się te wskazówki, to polecam Ci obserwować go na Linkedinie ponieważ zawsze jest aktywny i wrzuca dużo wartościowych treści .NET.