Projektowanie dobrych rozszerzeń
Stosuj dobre praktyki i unikaj typowych problemów.
Projektowanie dobrych rozszerzeń to bezpłatna lekcja C# Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej C# Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs C# Academy zawiera 4 lekcji w sumie.
Projektowanie lubianych metod rozszerzających
Metody rozszerzające są potężnym narzędziem, ale ich możliwości sprzyjają niewłaściwemu użyciu. Dobre rozszerzenia wydają się naturalną częścią typu, łatwo je znaleźć i nigdy nie zaskakują wywołującego. Ta lekcja omawia praktyczne wskazówki.
Rozszerzaj zachowanie, nie tożsamość
Metody rozszerzające warto stosować do pomocniczych operacji, które dobrze prezentują się w potoku. Jeśli metoda rzeczywiście należy do typu, którego jest się właścicielem, powinna być jego prawdziwym składnikiem.
using System;
public static class TimeExtensions
{
// A pleasant, focused helper
public static bool IsWeekend(this DayOfWeek day)
=> day == DayOfWeek.Saturday || day == DayOfWeek.Sunday;
}
public class Program
{
public static void Main()
{
Console.WriteLine(DayOfWeek.Sunday.IsWeekend());
Console.WriteLine(DayOfWeek.Monday.IsWeekend());
}
}W miarę możliwości zachowuj czystość metod
Najłatwiejsze do zrozumienia i łączenia są rozszerzenia, które zwracają nową wartość i unikają skutków ubocznych. Warto dążyć do modelu: dane wejściowe na wejściu, wartość na wyjściu.
using System;
public static class StringExtensions
{
// Pure: depends only on input, returns a new string
public static string Truncate(this string s, int max)
=> s.Length <= max ? s : s.Substring(0, max) + "...";
}
public class Program
{
public static void Main()
{
Console.WriteLine("Extension methods are great".Truncate(10));
}
}Zabezpiecz się przed wartościami null
Ponieważ wywołujący może wywołać metodę rozszerzającą dla odbiorcy o wartości null, należy świadomie zdecydować, co powinno się wtedy stać. Można łagodnie obsłużyć wartość null albo zgłosić jasny wyjątek ArgumentNullException.
using System;
public static class StringExtensions
{
public static string SafeUpper(this string s)
{
if (s is null) throw new ArgumentNullException(nameof(s));
return s.ToUpper();
}
}
public class Program
{
public static void Main()
{
try { ((string)null).SafeUpper(); }
catch (ArgumentNullException ex) { Console.WriteLine("Caught: " + ex.ParamName); }
Console.WriteLine("ok".SafeUpper());
}
}Nazywaj je z myślą o łatwym odnajdywaniu
Programiści znajdują metody rozszerzające, wpisując kropkę i przeglądając IntelliSense. Jasne nazwy czasownikowe, które opisują wynik, ułatwiają znalezienie metod pomocniczych i sprawiają, że same się dokumentują.
using System;
using System.Collections.Generic;
public static class CollectionExtensions
{
public static bool HasItems<T>(this ICollection<T> c)
=> c != null && c.Count > 0;
}
public class Program
{
public static void Main()
{
var list = new List<int> { 1 };
Console.WriteLine(list.HasItems());
}
}Spójnie grupuj powiązane rozszerzenia
Rozszerzenia dotyczące jednego pojęcia należy umieścić w jednej dobrze nazwanej klasie statycznej i przestrzeni nazw. Dzięki temu importy zachowują znaczenie, a użytkownicy mogą dołączyć dokładnie te metody pomocnicze, których potrzebują.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<T> TakeFirst<T>(this IEnumerable<T> source, int n)
{
int taken = 0;
foreach (var item in source)
{
if (taken++ >= n) yield break;
yield return item;
}
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3, 4 };
Console.WriteLine(string.Join(",", nums.TakeFirst(2)));
}
}Nie ukrywaj zaskakującego działania
Rozszerzenie, które wygląda na niedrogie, ale wykonuje kosztowne operacje lub pracuje ze stanem, łamie oczekiwania. Koszt powinien być oczywisty; jeśli metoda wykonuje zapytanie do bazy danych lub modyfikuje stan, należy to uwzględnić w jej nazwie.
using System;
public static class MathExtensions
{
// Cheap and obvious: no hidden cost
public static int ClampMin(this int value, int min)
=> value < min ? min : value;
}
public class Program
{
public static void Main()
{
Console.WriteLine((-3).ClampMin(0));
Console.WriteLine(5.ClampMin(0));
}
}Unikaj rozszerzania object
Rozszerzanie object zaśmieca każdy typ w programie własną metodą, przez co wszędzie zaśmieca także IntelliSense. Zamiast tego należy wybrać najbardziej konkretny odpowiedni typ lub interfejs.
using System;
public static class IntExtensions
{
// Specific to int, not object - far less noisy
public static string ToOrdinal(this int n)
{
if (n % 10 == 1 && n % 100 != 11) return n + "st";
if (n % 10 == 2 && n % 100 != 12) return n + "nd";
if (n % 10 == 3 && n % 100 != 13) return n + "rd";
return n + "th";
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(1.ToOrdinal());
Console.WriteLine(22.ToOrdinal());
}
}Preferuj zwracanie IEnumerable dla sekwencji
W przypadku operatorów sekwencji należy zwracać IEnumerable<T> i korzystać z odroczonego wykonania. Zapewnia to dobre możliwości kompozycji i nie zmusza wywołujących do materializowania danych, których mogą nie potrzebować.
using System;
using System.Collections.Generic;
public static class SeqExtensions
{
public static IEnumerable<int> Doubled(this IEnumerable<int> source)
{
foreach (var n in source) yield return n * 2;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
foreach (var n in nums.Doubled()) Console.WriteLine(n);
}
}Lista kontrolna dobrych rozszerzeń
Przed opublikowaniem rozszerzenia należy zapytać: Czy nazwa jest jasna? Czy metoda jest czysta, a jeśli nie, czy skutki uboczne są oczywiste? Czy obsługiwana jest wartość null? Czy typ docelowy jest konkretny? Czy rozszerzenie jest sensownie pogrupowane? Jeśli na wszystkie pytania można odpowiedzieć twierdząco, powstała metoda pomocnicza, z której inni będą chętnie korzystać.
using System;
public static class StringExtensions
{
public static string OrDefault(this string s, string fallback)
=> string.IsNullOrEmpty(s) ? fallback : s;
}
public class Program
{
public static void Main()
{
Console.WriteLine("".OrDefault("N/A"));
Console.WriteLine("value".OrDefault("N/A"));
}
}Wypróbuj samodzielnie
Proszę zastosować listę kontrolną: konkretny typ docelowy, jasna nazwa, tolerowanie wartości null i czysty wynik. To małe rozszerzenie spełnia wszystkie wytyczne.
using System;
using System.Collections.Generic;
public static class DictionaryExtensions
{
// Clear name, specific type, null-safe, pure
public static TValue GetOrDefault<TKey, TValue>(
this IReadOnlyDictionary<TKey, TValue> dict, TKey key, TValue fallback)
{
if (dict == null) return fallback;
return dict.TryGetValue(key, out var value) ? value : fallback;
}
}
public class Program
{
public static void Main()
{
var ages = new Dictionary<string, int> { ["Ann"] = 30 };
Console.WriteLine(ages.GetOrDefault("Ann", -1));
Console.WriteLine(ages.GetOrDefault("Bob", -1));
}
}Szybkie sprawdzenie
Proszę zastosować wytyczne projektowe.
Podsumowanie
Dobrze zaprojektowane rozszerzenia wydają się naturalne i przewidywalne.
- Warto preferować czyste metody pomocnicze zwracające wartości i dobrze nadające się do łączenia.
- Należy zdecydować o sposobie obsługi wartości null i go udokumentować.
- Należy używać jasnych nazw czasownikowych, aby ułatwić znalezienie metod.
- Należy kierować rozszerzenia do konkretnych typów lub interfejsów, a nie do
object. - Należy grupować powiązane metody pomocnicze i zwracać
IEnumerable<T>dla sekwencji.
Ucz się C# dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 93
- Lekcje
- 346
Często zadawane pytania
Czy lekcja „Projektowanie dobrych rozszerzeń” jest bezpłatna?
Tak — pełny tekst „Projektowanie dobrych rozszerzeń” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu C# Academy, przejdź na CoddyKit PRO. Kurs C# Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Projektowanie dobrych rozszerzeń”?
Stosuj dobre praktyki i unikaj typowych problemów. Ćwiczysz C# Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć C# Academy?
Nie wymagamy żadnego doświadczenia. C# Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Projektowanie dobrych rozszerzeń”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji C# Academy?
Tak. Każda lekcja C# Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Definiowanie metod rozszerzających
- Rozszerzanie interfejsów i typów generycznych
- Rozstrzyganie metod rozszerzających
- Projektowanie dobrych rozszerzeń