Menu

Substring w C#: bezpieczne wycinanie fragmentu stringa

Jak działa String.Substring w C#: argumenty indeksu początkowego i długości, wyjątek ArgumentOutOfRangeException i jak go uniknąć, wycinanie tekstu wokół wyników IndexOf, pobieranie pierwszych lub ostatnich n znaków oraz operator zakresu z C# 8.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Substring zwraca nowy string złożony z części istniejącego. Ma dwa przeciążenia:

  • s.Substring(startIndex): od startIndex do końca.
  • s.Substring(startIndex, length): length znaków, zaczynając od startIndex.

Indeksy zaczynają się od 0, a drugi argument to długość, a nie pozycja końcowa. Na tym polega różnica względem substring(start, end) z JavaScriptu i substring(begin, end) z Javy, i to źródło wielu błędów o kilka pozycji przy przenoszeniu kodu.

Wynik:

4271-BLK
SHOE
4271
BLK
0
SHOE-4271-BLK

Jak każda metoda stringa, Substring nie zmienia oryginału; zwraca nowy string, więc przypisz wynik.

ArgumentOutOfRangeException i jak go uniknąć

Substring rzuca ArgumentOutOfRangeException, gdy żądany zakres nie mieści się w stringu: ujemny startIndex, startIndex większy niż Length, ujemne length albo startIndex + length większe niż Length. Nie ma cichego obcinania, w przeciwieństwie do wycinków w Pythonie.

Wynik:

Substring(2, 4) -> "ffee"
Substring(2, 5) -> ArgumentOutOfRangeException
Substring(6) -> ""
Substring(7) -> ArgumentOutOfRangeException
Substring(-1) -> ArgumentOutOfRangeException
Substring(0, 10) -> ArgumentOutOfRangeException

Typowym zabezpieczeniem jest ograniczenie długości do tego, co zostało ze stringa, przez Math.Min:

Wynik:

ffee
ea
[]
[]

Wywołanie Substring na stringu null rzuca NullReferenceException, a nie ArgumentOutOfRangeException, dlatego metoda pomocnicza najpierw sprawdza null.

Pierwsze i ostatnie n znaków

Pobranie prefiksu albo sufiksu to najczęstsze zastosowanie Substring. Oba rzucają wyjątek, gdy string jest krótszy niż n, więc kod produkcyjny sprawdza długość:

Wynik:

**** 1111
An Unexpecte...
Hi
Hi

Gdy chcesz odrzucić początek stringa, wystarczy samo Substring(n): "#FF8800".Substring(1) to "FF8800". Pokrewne Remove(start, count) robi odwrotność Substring: zwraca string z usuniętym tym zakresem.

Substring z IndexOf

Większość prawdziwych fragmentów się znajduje, a nie odlicza. IndexOf zwraca pozycję znaku lub stringa (albo -1, jeśli go nie ma), a LastIndexOf szuka od końca. Połącz je z Substring, żeby ciąć tekst wokół separatora:

Wynik:

maya.lopez
example.com
pdf
report.final.v2
WARN
Disk almost full

Arytmetyka opiera się na jednej zasadzie: żeby wziąć tekst między pozycjami a i b (bez żadnej z nich), początek to a + 1, a długość to b - a - 1.

Niebezpieczeństwem jest IndexOf zwracające -1. email.Substring(0, -1) rzuca wyjątek, a co gorsza, email.Substring(-1 + 1) po cichu zwraca cały string. Zawsze sprawdzaj:

Wynik:

shop.io
(none)

Split jako alternatywa

Gdy string to lista pól z separatorem, Split jest prostszy niż łańcuch wywołań IndexOf i Substring i nie ma w nim arytmetyki indeksów, w której można się pomylić:

Wynik:

Ana Silva
Porto
maya.lopez
beach.jpg

Split alokuje tablicę i po jednym stringu na pole, więc przy pojedynczym cięciu w gorącej pętli IndexOf i Substring są tańsze. We wszystkich innych przypadkach wygrywa czytelność. Więcej opcji (kilka separatorów, usuwanie pustych wpisów, maksymalna liczba części) znajdziesz w metodach stringów. Przy wzorcach typu "cyfry po ID:" najczytelniejszym narzędziem jest zwykle wyrażenie regularne.

Operator zakresu (C# 8 i nowsze)

C# 8 dodał składnię indeksów i zakresów, która działa na stringach i tablicach. ^n oznacza "n od końca", a a..b to zakres od a do b, ale bez b, więc druga liczba to indeks końcowy, a nie długość:

// C# 8 and later
string sku = "SHOE-4271-BLK";

string first4 = sku[..4];      // "SHOE"        same as Substring(0, 4)
string middle = sku[5..9];     // "4271"        same as Substring(5, 4)
string last3  = sku[^3..];     // "BLK"         same as Substring(sku.Length - 3)
string noEnds = sku[1..^1];    // "HOE-4271-BL" drop first and last
char lastChar = sku[^1];       // 'K'

Na stringu zakres kompiluje się do wywołania Substring, więc alokuje nowy string i rzuca ArgumentOutOfRangeException w tych samych przypadkach wyjścia poza zakres. Wybór to kwestia stylu; zakresy czyta się lepiej, gdy oba końce są mierzone z różnych stron ([1..^1]).

Fragmenty bez kopiowania: Span

Każde wywołanie Substring alokuje nowy string. W kodzie parsującym, gdzie liczy się wydajność (czytanie dużych plików, obsługa wielu żądań), .NET Core 2.1 i nowsze oferują ReadOnlySpan<char>, widok na część oryginalnego stringa bez kopiowania:

ReadOnlySpan<char> line = "2026-09-24,42.50,EUR".AsSpan();
ReadOnlySpan<char> amount = line.Slice(11, 5);          // "42.50", no allocation
decimal value = decimal.Parse(amount, provider: CultureInfo.InvariantCulture);

Spanów nie można przechowywać w polach zwykłych klas ani używać przez await, więc pasują do zwartego, synchronicznego kodu. W typowym kodzie aplikacji właściwym narzędziem jest Substring.

Częste błędy

  • Przekazywanie indeksu końcowego jako długości. s.Substring(2, 5) oznacza pięć znaków od indeksu 2, a nie "od indeksu 2 do 5". Żeby wziąć tekst od indeksu a do indeksu b bez niego, długość wynosi b - a.
  • Niesprawdzanie, czy IndexOf zwrócił -1. Kończy się to wyjątkiem albo, z + 1, przypadkowo całym stringiem.
  • Zakładanie, że krótkie stringi są wystarczająco długie. Imię, kod albo linia z pliku mogą być krótsze, niż się spodziewasz. Ogranicz przez Math.Min albo sprawdź Length.
  • Wyrzucanie wyniku. Samo s.Substring(1); w osobnej linii nie robi nic użytecznego.

Najczęściej zadawane pytania

Jak działa Substring w C#?

s.Substring(start) zwraca znaki od indeksu start do końca; s.Substring(start, length) zwraca length znaków, zaczynając od start. Indeksy zaczynają się od 0, a drugi argument to długość, a nie indeks końcowy: "Hello".Substring(1, 3) to "ell". Oryginalny string się nie zmienia.

Dlaczego Substring rzuca ArgumentOutOfRangeException?

Bo start jest ujemny albo większy niż długość stringa, albo start + length wychodzi poza koniec. Najczęstszą przyczyną jest długość obliczona dla dłuższego stringa albo wynik IndexOf równy -1 użyty jako początek. Ogranicz długość przez Math.Min(length, s.Length - start) i sprawdzaj wyniki IndexOf przed użyciem.

Jak pobrać ostatnie n znaków stringa w C#?

s.Substring(s.Length - n) zwraca ostatnie n znaków, o ile n <= s.Length. Żeby było bezpiecznie dla krótkich stringów, użyj s.Length <= n ? s : s.Substring(s.Length - n). W C# 8 i nowszych możesz też napisać s[^n..], które rzuca wyjątek tak samo, gdy n jest za duże.

Jak pobrać fragment między dwoma znakami w C#?

Znajdź obie pozycje przez IndexOf, a potem weź część między nimi: int start = s.IndexOf('(') + 1; int end = s.IndexOf(')', start); string inside = s.Substring(start, end - start);. Przed wywołaniem Substring sprawdź, czy każde IndexOf coś znalazło (nie -1). Przy złożonych wzorcach wyrażenie regularne jest czytelniejsze.

Czy Substring w C# liczy od zera?

Tak. Pierwszy znak jest pod indeksem 0, a ostatni pod Length - 1. Substring(0, 3) zwraca pierwsze trzy znaki. Przekazanie samego Length jako początku jest dozwolone i zwraca pusty string.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ