Menu

Tuple ב-C#: tuples עם שמות, החזרת כמה ערכים ופירוק (deconstruction)

tuple ב-C# מקבץ כמה ערכים לערך אחד בלי להצהיר על טיפוס. למדו את התחביר של value tuple, איברים עם שמות, החזרת כמה ערכים ממתודה, פירוק עם var (a, b), מפתחות tuple במילון, ובמה ValueTuple שונה מ-System.Tuple הישן.

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

tuple מקבץ מספר קטן וקבוע של ערכים לערך אחד בלי להצהיר בשבילו על מחלקה. ("Ana", 31) הוא tuple של string ו-int. tuples הם הדרך המקובלת להחזיר שניים או שלושה דברים ממתודה ולהחזיק זוגות קצרי חיים בתוך מתודה.

יצירת tuple

גם הטיפוס נכתב עם סוגריים: (string, int) הוא טיפוס tuple שהאיבר הראשון שלו הוא string והשני int.

פלט:

Ana is 31
(3, 4)
179.70
(Ana, 32)

בלי שמות, האיברים הם Item1, Item2, Item3 וכן הלאה. ה-tuples האלה הם structs מסוג System.ValueTuple, כך שהשמה של אחד למשתנה אחר מעתיקה את הערכים, ושינוי העותק לא נוגע במקור.

איברים עם שמות

Item1 לא אומר כלום על מה הערך. תנו לאיברים שמות, בטיפוס או בליטרל:

פלט:

Ana, 31
Desk lamp costs 24.99
Ana
Ana

השמות הם נוחות של זמן קומפילציה. בזמן ריצה שני ה-tuples שלמעלה הם ערכי ValueTuple<string, int> רגילים, ולכן השמה של person ל-tuple עם שמות אחרים מתקמפלת: רק הטיפוסים של האיברים והסדר שלהם חייבים להתאים.

C# 7.1 ומעלה גם מסיקות שמות מהמשתנים שבליטרל, כך ש-var t = (name, age); נותן t.name ו-t.age בלי לכתוב אותם פעמיים. ב-C# 7.0 האיברים האלה הם רק Item1 ו-Item2.

החזרת כמה ערכים ממתודה

זו הסיבה העיקרית ש-tuples נוספו לשפה. לפניהם האפשרויות היו פרמטרי out, מחלקה חד-פעמית, או System.Tuple עם ה-Item1 האנונימי שלו.

פלט:

Lowest 60, highest 95
Average 78.75

שמות האיברים בטיפוס ההחזרה הופכים לשמות שהקורא רואה. השוו לאותה מתודה עם פרמטרי out:

static void Stats(int[] scores, out int min, out int max, out double average) { ... }

Stats(scores, out int min, out int max, out double avg);

פרמטרי out נשארים המוסכמה לצורת TryParse, שבה bool אומר אם זה עבד והערך חוזר לצדו. עבור "חשבו כמה דברים והחזירו אותם", tuple קריא יותר. ראו ref ו-out.

פירוק (deconstruction)

פירוק פורס tuple למשתנים נפרדים בפקודה אחת:

פלט:

Lima 24.5
Lima
24.5
2 1

שמות המשתנים ב-var (city, temp) הם שלכם; הם לא צריכים להתאים לשמות האיברים של ה-tuple, כי הפירוק נעשה לפי מיקום. ההחלפה (a, b) = (b, a) בונה קודם tuple מהערכים הישנים ורק אז משימה, כך ששום ערך לא הולך לאיבוד.

המחלקות שלכם יכולות לתמוך בפירוק אם מצהירים בהן על מתודת Deconstruct עם פרמטרי out. מ-C# 7 עם .NET Core 2.0 ומעלה, ל-KeyValuePair יש מתודה כזו, כך שלולאה על מילון יכולה לפרק כל רשומה בכותרת הלולאה:

class Point
{
    public int X { get; }
    public int Y { get; }
    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

var (x, y) = new Point(3, 4);

foreach (var (name, score) in scoresByName)   // Dictionary<string, int>
{
    Console.WriteLine($"{name}: {score}");
}

אותה צורה, foreach (var (a, b) in listOfTuples), עובדת עבור רשימה של tuples. לקחת את כל ה-tuple כמשתנה לולאה אחד ולקרוא את האיברים עם השמות שלו, כמו שהקטע הבא עושה, עובד באותה מידה.

tuples ברשימות, ב-LINQ ובמילונים

רשימה של tuples עם שמות היא דרך קלה להחזיק שורות של נתונים בתוך מתודה:

פלט:

Chloe 95
Ana 88
Ben 72
(Ana, B), (Chloe, A)
Ben
False

ValueTuple מממש את Equals ואת GetHashCode איבר אחר איבר, כך ש-(2, 7) שנבנה בשני מקומות שונים מוצא את אותה רשומה במילון. זה הופך את ה-tuple למפתח המורכב הפשוט ביותר: בלי מחלקה, בלי פונקציית hash שנכתבה ידנית, ובלי שרשור מחרוזות כמו row + ":" + col.

שוויון של tuples

Equals משווה איבר אחר איבר ועובדת מאז ש-tuples הוצגו:

פלט:

True
False

C# 7.3 הוסיפה == ו-!= ל-tuples, שמתקמפלים לאותה השוואה איבר אחר איבר ומתעלמים גם הם מהשמות:

if ((order.Status, order.Paid) == ("shipped", true)) { ... }
Console.WriteLine((1, "x") == (1, "x"));   // True

System.Tuple מול ValueTuple

.NET 4 הציגה את System.Tuple, מחלקה שנוצרת עם Tuple.Create. היא עדיין קיימת ב-framework ועדיין מופיעה בקוד וב-APIs ישנים, ולכן חיפושים של "C# tuple" נוחתים עליה לעיתים קרובות.

ValueTuple (C# 7)System.Tuple (.NET 4)
תחביר(1, "a"), (int, string)Tuple.Create(1, "a"), Tuple<int, string>
סוגstruct (טיפוס ערך)class (טיפוס הפניה)
שמות לאיבריםכןלא, רק Item1, Item2
ניתן לשינויכן, האיברים הם שדותלא, מאפיינים לקריאה בלבד
פירוקכןכן, דרך מתודות הרחבה

פלט:

Ben 25
(Ben, 25)

ToValueTuple() ו-ToTuple() ממירות בין השניים. העדיפו value tuples בקוד חדש: הם לא מקצים כלום ב-heap והם יכולים לשאת שמות.

מתי להשתמש במחלקה או ב-record במקום

tuples הכי טובים כשהקיבוץ מקומי וברור: מתודה מחזירה שני מספרים, שאילתת LINQ מעבירה זוג לשלב הבא, מילון צריך מפתח משני חלקים. עברו לטיפוס עם שם כש:

  • אותה צורה מופיעה בכמה חתימות של מתודות ציבוריות. (string, string, int) בחמישה מקומות הוא מחלקה שעוד לא נכתבה.
  • הערכים צריכים התנהגות, ולידציה, או יותר משלושה או ארבעה איברים.
  • הנתונים חוצים גבול כמו סריאליזציה. השמות לא קיימים בזמן ריצה, כך ש-Newtonsoft.Json כותבת Item1 ו-Item2, ו-System.Text.Json כותבת {} כי איברי tuple הם שדות, שהיא מדלגת עליהם כברירת מחדל.

records של C# 9 נותנים את רוב הנוחות של tuple עם שם אמיתי. ל-record Score(string Name, int Points); יש שוויון ערכים, ToString קריא ופירוק מובנים. ראו records.

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

  • ציפייה שהשמות ישרדו בזמן ריצה. reflection ו-serializers רואים Item1, Item2 (או בכלל כלום).
  • שינוי של עותק. tuples הם structs: var t2 = t1; t2.Item1 = 5; משאיר את t1 ללא שינוי.
  • tuples עם הרבה איברים. מעבר לשלושה או ארבעה, טיפוס עם שם קריא יותר.
  • בלבול בסדר הפירוק. הוא לפי מיקום: עם tuple מסוג (Name, Age), var (age, name) = person; שם את השם ב-age, ומכיוון ש-var מסיק את הטיפוסים, שום דבר לא מזהיר אתכם.

שאלות נפוצות

איך מחזירים כמה ערכים ממתודה ב-C#?

מחזירים tuple: מצהירים על טיפוס ההחזרה כ-(int Min, int Max) וכותבים return (lowest, highest);. הקורא קורא את result.Min ואת result.Max, או מפרק עם var (min, max) = MinMax(data);. החלופות הן פרמטרי out, שמתאימים לתבנית TryParse, או מחלקה קטנה או record כשלתוצאה יש משמעות משלה.

מה זה tuple עם שמות ב-C#?

tuple שלאיברים שלו יש שמות במקום Item1, Item2: (string Name, int Age) person = ("Ana", 31); מאפשר לכתוב person.Name. השמות קיימים רק בזמן קומפילציה; מתחת לפני השטח זה עדיין ValueTuple<string, int>, ו-Item1 ממשיך לעבוד.

מה ההבדל בין Tuple ל-ValueTuple ב-C#?

System.Tuple (מ-.NET 4) היא מחלקה: מוקצית ב-heap, בלתי ניתנת לשינוי, והאיברים שלה תמיד רק Item1, Item2. System.ValueTuple (C# 7) הוא struct עם שדות שניתנים לשינוי, תומך בשמות לאיברים ובתחביר (a, b), והוא מה שכל ליטרל tuple יוצר. השתמשו ב-value tuples בקוד חדש.

איך פירוק של tuple עובד ב-C#?

var (name, age) = person; מצהיר על שני משתנים ומשים להם את איברי ה-tuple לפי הסדר. אפשר גם להשים למשתנים קיימים עם (a, b) = (b, a), שמחליף ביניהם, ולדלג על איברים עם ה-discard _: var (_, age) = person;.

אפשר להשוות tuples עם == ב-C#?

מאז C# 7.3, == ו-!= משווים tuples איבר אחר איבר: (1, "a") == (1, "a") הוא true. השמות לא נלקחים בחשבון; רק המיקומים והערכים חשובים. לפני C# 7.3, השתמשו ב-Equals, ש-value tuples מממשים באותו אופן.

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

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

להתחיל