C# 9.0 כבר כאן!
-
אז כמו שכתוב בכותרת C# גרסה 9 שוחררה באופן רשמי לפני כשבועיים.
זה חלק ממהלך יותר רחב של .net 5 שבעצם מאחד את 2 האפשרויות שהיו עד היום (core, framework) לבסיס אחד.
יש בה כמה חידושים מעניינים, נראה לי שאחד הרציניים והגדולים שבהם הוא ההכרזה על Record.אפשר לקרוא על כך כאן, ותיכף אסביר בקצרה. אולם רק אקדים ואומר שבשביל זה צריך גם לעדכן את ויזואל סטודיו לגרסה 16.8 כדי שיתמוך בפיצ'רים החדשים.
אז מה זה בעצם רקורד? מאחורי הקלעים זוהי מחלקה (קלאס בלעז) רגילה לכל דבר וענין שהקומפיילר יוצר עבורנו, אבל מבחינתנו זה כעין סוג חדש של טיפוס נתונים - כעין 'מבנה' (struct, דהיינו כמו int וכדו').
אבאר את דברי:
כשאנו כותבים כך:public record Person(string FirstName, string LastName);
אנחנו בעצם מקבלים מאחורי הקלעים קלאס עם המאפיינים של firstname וlastname, וגם קונסטרקטור שמאפשר לנו לתת את הערכים הללו והוא כבר ידאג להזין אותם למאפיינים, ועוד הרבה יכולות שתיכף נסקור.
רק חשוב להעיר, שrecord יוצר לנו קלאס שהוא Immutable דהיינו בלתי ניתן לשינוי. ובעיקר את הצורך הזה הוא בא לפתור.
אבל בואו נעשה סדר, ונתחיל מההתחלה.רקורד כשמו כן הוא, רשומה.
הוא בא לפתור לנו את החסרון באובייקט רגיל שמכיל מאפיינים, שכדי להצהיר עליהם מיד לאחר השימוש היינו חייבים לעשות להם גטר וסטר ציבוריים, אחרת לא יכלנו מיד לאחר ההכרזה עליהם להזין להם ערכים.
לכן הוסיפו תמיכה בסטר חדש שנקרא init, כזה:public class Person { public string? FirstName { get; init; } public string? LastName { get; init; } }
שמאפשר להזין ערך במשתנה רק דרך הקונסטרקטור, או מיד באופן הזה:
var person = new Person { FirstName = "Mads", LastName = "Nielsen" };
אולם לאחר מכן, כל ניסיון לגשת אליהם ולשנות להם את הערך נידון לכשלון!
הדבר הזה הוא נדרש הרבה פעמים כשמתשמשים בpattern של dto (data transfer object) כמו בגישה לדטהבייסים וכדו'.בשביל זה לא צריך להשתמש בrecord, זה נתמך גם בclass רגיל. אולם הrecord מוסיף לנו עוד הרבה יכולות באופן מובנה.
with
למשל, מה נעשה כשאנו כן צריכים לשנות את הנתונים? בשביל זה אנו צריכים ליצור עותק של האובייקט הראשון, וכדי לחסוך לנו את העבודה אנו יכולים להשתמש ב with, ככה:var person = new Person { FirstName = "Mads", LastName = "Nielsen" }; var otherPerson = person with { LastName = "Torgersen" };
זה ישכפל לנו את כל המאפיינים שיש באובייקט person חוץ ממה ששינינו עכשיו.
Equal
יתרון נוסף שאנו מקבלים בשימוש בrecord, הוא שמאחורי הקלעים יש לנו דריסה של הפונקציה Equal שלא הייתה שמישה באובייקטים, מכיוון שהיא משווה את הרפרפנס שלהם, ותמיד הם יהיו שונים מכיון שכל אובייקט מצביע למיקום אחר.וממילא לא יכלנו להשתמש בה כדי לבדוק האם האובייקטים מכילים את אותם ערכים למרות שהם שונים. עכשיו כשאנו משתמשים ברקורד, אנו יכולים להשתמש בפונקציה equal ואע"פ שהמצביעים הם שונים, אם הערכים שווים - התשובה תהיה חיובית. (מאחורי הקלעים יש כאן ovveride לפונקציה המקורית שיש לכל אובייקט בC#).כמו כן, ניתן לכתוב כמו שכתבתי למעלה בשורה אחת, וזה חוסך לנו לכתוב קונסטרקטור שמקבל את הפרמטרים ומציב בפרופרטיס, וגם חוסך לנו לכתוב את הפרופרטיז הללו.
כמו כן הוא חוסך לנו לכתוב פונקציה שמקבל ערכים ומציבה בהם את הערכים הללו, ולכן אפשר עכשיו לכתוב ככה:var (f, l) = person;
ונקבל במשתנה f את הערך שנמצא בfirstname וכן בl את הערך השני.
מה שנקרא deconstructor.כל מה שכתבתי כאן מבוסס על הפוסט שהפניתי אליו, ואני ממש ממליץ לקרוא אותו למי שרוצה. כי נוספו שם עוד הרבה דברים מעניינים, ובקצרה רק אוסיף עוד 2 דברים מעניינים, שלא קשורים לrecord אבל נוספו לתמיכה של השפה:
not operator
נוסף גם האופרטור not שמאפשר לנו לכתוב ככה:if(x is not null)
Target-typed new expressions
מה זה אומר? שאפשר לחסוך בהצהרה של שם המאפיין.
עד היום יכלנו לכתובvar x = new Person();
ולא היינו צריכים לכתוב פעמיים person, עכשיו אפשר לכתוב באותה מידה:
Person x = new ();
והמהדר ידע לבד שהכוונה לPerson, כי זה מה שהצהרנו קודם. נחמד, אבל לא כזה מרגש, נכון? אבל עכשיו תראו איפה זה חוסך לנו הרבה כתיבה.
למשל אם אני רוצה להצהיר על ליסט של פרסון וגם לאתחל אותו עם הרבה ערכים. עד היום הייתי צריך לכתוב ככה:var members = new List<Person>(){new Person("avi","cohen")} ;
וכן על זה הדרך.
היום מספיק לכתוב רק ככה:var members = new List<Person>(){new ("avi","cohen"),new ("avi","cohen"),new ("avi","cohen")} ;
ותן לחכם ויחכם עוד, וכאן יש את הפירוט המלא של כל החידושים שנכנסו לשפה.