Menu

Substring ב-C#: חילוץ חלק ממחרוזת בבטחה

איך String.Substring עובדת ב-C#: הארגומנטים של אינדקס ההתחלה והאורך, ArgumentOutOfRangeException ואיך להימנע ממנה, חילוץ טקסט סביב תוצאות של IndexOf, לקיחת n התווים הראשונים או האחרונים, ואופרטור הטווח מ-C# 8.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

Substring מחזירה מחרוזת חדשה שמורכבת מחלק של מחרוזת קיימת. יש לה שתי העמסות:

  • s.Substring(startIndex): מ-startIndex ועד הסוף.
  • s.Substring(startIndex, length): length תווים, החל מ-startIndex.

האינדקסים מתחילים מ-0, והארגומנט השני הוא אורך, לא מיקום סיום. זה ההבדל מ-substring(start, end) של JavaScript ומ-substring(begin, end) של Java, והמקור להרבה באגים של סטייה באינדקס כשמעבירים קוד בין שפות.

פלט:

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

כמו כל מתודת מחרוזת, Substring לא משנה את המקור; היא מחזירה מחרוזת חדשה, אז השימו את התוצאה במשתנה.

ArgumentOutOfRangeException ואיך להימנע ממנה

Substring זורקת ArgumentOutOfRangeException כשהטווח המבוקש לא נכנס בתוך המחרוזת: startIndex שלילי, startIndex גדול מ-Length, length שלילי, או startIndex + length גדול מ-Length. אין קיצוץ שקט, בניגוד ל-slices של Python.

פלט:

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

ההגנה המקובלת היא להגביל את האורך למה שנשאר מהמחרוזת עם Math.Min:

פלט:

ffee
ea
[]
[]

קריאה ל-Substring על מחרוזת null זורקת NullReferenceException, לא ArgumentOutOfRangeException, ולכן פונקציית העזר בודקת קודם null.

n התווים הראשונים והאחרונים

לקיחת תחילית או סיומת היא השימוש הנפוץ ביותר ב-Substring. שתיהן זורקות כשהמחרוזת קצרה מ-n, ולכן קוד אמיתי בודק את האורך:

פלט:

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

כשמה שרוצים הוא להשמיט את החלק הראשון של המחרוזת, Substring(n) לבדה מספיקה: "#FF8800".Substring(1) הוא "FF8800". המתודה הקשורה Remove(start, count) עושה את ההפך מ-Substring: היא מחזירה את המחרוזת בלי הטווח הזה.

Substring עם IndexOf

רוב תתי-המחרוזות האמיתיות נמצאות בחיפוש, לא בספירה. IndexOf מחזירה את המיקום של תו או מחרוזת (או -1 אם הם לא שם), ו-LastIndexOf מחפשת מהסוף. שלבו אותן עם Substring כדי לחתוך טקסט סביב מפריד:

פלט:

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

החשבון פועל לפי כלל אחד: כדי לקחת את הטקסט שבין המיקומים a ו-b (בלי אף אחד מהם), ההתחלה היא a + 1 והאורך הוא b - a - 1.

הסכנה היא ש-IndexOf תחזיר -1. email.Substring(0, -1) זורקת, וגרוע מזה, email.Substring(-1 + 1) מחזירה בשקט את כל המחרוזת. בדקו תמיד:

פלט:

shop.io
(none)

Split כחלופה

כשמחרוזת היא רשימה של שדות עם מפריד, Split פשוטה יותר משרשרת של קריאות IndexOf ו-Substring, ואין בה חשבון אינדקסים שאפשר לטעות בו:

פלט:

Ana Silva
Porto
maya.lopez
beach.jpg

Split מקצה מערך ועוד מחרוזת אחת לכל שדה, כך שלחיתוך בודד בלולאה חמה IndexOf ו-Substring זולות יותר. לכל השאר, הקריאות מנצחת. אפשרויות נוספות (כמה מפרידים, הסרת רשומות ריקות, מספר מקסימלי) נמצאות ב-מתודות מחרוזת. לתבניות כמו "הספרות שאחרי ID:", ביטוי רגולרי הוא בדרך כלל הכלי הברור ביותר.

אופרטור הטווח (C# 8 ומעלה)

C# 8 הוסיפה תחביר של אינדקסים וטווחים, שעובד על מחרוזות ועל מערכים. ^n פירושו "n מהסוף", ו-a..b הוא הטווח מ-a ועד b בלי לכלול אותו, כך שהמספר השני הוא אינדקס סיום, לא אורך:

// 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'

על מחרוזת, טווח מתקמפל לקריאה ל-Substring, כך שהוא מקצה מחרוזת חדשה וזורק ArgumentOutOfRangeException באותם מקרים של חריגה מהגבולות. במה להשתמש זה עניין של סגנון; טווחים קריאים יותר כששני הקצוות נמדדים מצדדים שונים ([1..^1]).

תתי-מחרוזות בלי העתקה: Span

כל קריאה ל-Substring מקצה מחרוזת חדשה. בקוד פענוח שרגיש לביצועים (קריאת קבצים גדולים, טיפול בהרבה בקשות), .NET Core 2.1 ומעלה מציעות את ReadOnlySpan<char>, תצוגה של חלק מהמחרוזת המקורית בלי העתקה:

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);

אי אפשר לשמור spans בשדות של מחלקות רגילות או להשתמש בהם לאורך await, כך שמקומם בקוד סינכרוני והדוק. לקוד אפליקציה רגיל, Substring היא הכלי הנכון.

טעויות נפוצות

  • העברת אינדקס סיום בתור אורך. s.Substring(2, 5) פירושו חמישה תווים מאינדקס 2, לא "מאינדקס 2 עד 5". כדי לקחת את הטקסט מאינדקס a ועד אינדקס b בלי לכלול אותו, האורך הוא b - a.
  • אי-בדיקה אם IndexOf החזירה -1. או חריגה, או, עם + 1, כל המחרוזת בטעות.
  • הנחה שמחרוזות קצרות ארוכות מספיק. שם, קוד או שורה מקובץ יכולים להיות קצרים ממה שמצפים. הגבילו עם Math.Min או בדקו את Length.
  • השלכת התוצאה. s.Substring(1); בשורה משלה לא עושה שום דבר מועיל.

שאלות נפוצות

איך Substring עובדת ב-C#?

s.Substring(start) מחזירה את התווים מהאינדקס start ועד הסוף; s.Substring(start, length) מחזירה length תווים החל מ-start. האינדקסים מתחילים מ-0, והארגומנט השני הוא אורך, לא אינדקס סיום: "Hello".Substring(1, 3) הוא "ell". המחרוזת המקורית לא משתנה.

למה Substring זורקת ArgumentOutOfRangeException?

כי start שלילי או גדול מהאורך של המחרוזת, או ש-start + length עובר את הסוף. הסיבה הנפוצה ביותר היא אורך שחושב עבור מחרוזת ארוכה יותר, או תוצאה -1 של IndexOf שמשמשת כהתחלה. הגבילו את האורך עם Math.Min(length, s.Length - start) ובדקו את התוצאות של IndexOf לפני שמשתמשים בהן.

איך מקבלים את n התווים האחרונים של מחרוזת ב-C#?

s.Substring(s.Length - n) מחזירה את n התווים האחרונים, בתנאי ש-n <= s.Length. כדי להיות בטוחים גם עם מחרוזות קצרות, השתמשו ב-s.Length <= n ? s : s.Substring(s.Length - n). ב-C# 8 ומעלה אפשר לכתוב גם s[^n..], שזורק באותו אופן כש-n גדול מדי.

איך מקבלים את תת-המחרוזת שבין שני תווים ב-C#?

מצאו את שני המיקומים עם IndexOf, ואז קחו את החלק שביניהם: int start = s.IndexOf('(') + 1; int end = s.IndexOf(')', start); string inside = s.Substring(start, end - start);. בדקו שכל IndexOf מצאה משהו (לא -1) לפני הקריאה ל-Substring. לתבניות מורכבות, ביטוי רגולרי ברור יותר.

האם Substring מתחילה מאפס ב-C#?

כן. התו הראשון נמצא באינדקס 0 והאחרון ב-Length - 1. Substring(0, 3) מחזירה את שלושת התווים הראשונים. מותר להעביר את Length עצמו כהתחלה, וזה מחזיר מחרוזת ריקה.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל