סיכום קורס C#בסיסי :מרצה :הרב דוטנט.
סיכום קורס – CSharpבסיסי.
על ידי שלמה גולדברג )הרב דוטנט(
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. תוכן העניינים: •
הקדמה ל – Microsoft.net
•
היכרות עם ה – Type System
•
עבודה עם מחלקות
•
מנגנון ניקוי הזיכרון.
•
מערכים ו – .List
•
עבודה עם מחרוזות.
•
Object Oriented
•
מבנים
•
nullable
•
enums
•
טיפול בשגיאות
•
Operator Overload
•
Reflection
•
Attributes
•
ממשקים.
•
Delegates and events
הקדמה ל – .Microsoft net framework
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. Microsoft net frameworkבאה לאפשר לנו כמתכנתים לכתוב קוד בכל השפות )התומכות (netעבור כל הסביבות ) (web, win, selularבמגוון טכנולוגיות )עבודה עם קבצים ,קישור לבסיס נתונים ועוד( והכול מתוך סביבה אחת ללא תלות במנגנונים חיצוניים. Microsoft net frameworkמחולקת לשני חלקים עיקריים: •
– FCLהמוכר בשם Framework Class Library
•
– CLRהמוכר בשם Common Language Runtime
ה – FCLהינו ספרייה גדולה מאוד של מאות ואלפי פונקציות מובנות שנוכל להשתמש בה מבלי שנצרך לכתוב את הקוד לבד )לדוגמא ,אם נרצה להצפין מחרוזת ,לא נצטרך לכתוב את כל האלגוריתם של הצפנה ופיענוח ,אלא נוכל להשתמש בקוד מוכן(. ה – CLRלעומת זאת זה המנוע העיקרי של פיתוח קוד בטכנולוגיית ,netהוא זה שאחראי להריץ את הקוד ,לזרוק או לטפל בשגיאות לנקות את הזיכרון ועוד. כדי שנוכל להריץ קוד שנכתב ב – ,netנצטרך לוודא שעל מחשב הלקוח מותקנת גרסת ה – frameworkהמתאימה שבעזרתה פיתחנו את המוצר.
קומפילציה וזמן ריצה. בשפות אחרות ,תהליך הקומפילציה לוקח את הקוד שכתבנו ומקמפל אותו לשפת מכונה ,כך שיש בקובץ קוד בינארי שניתן להבנה על ידי החומרה ,בשפות netלעומת זאת הקומפילציה בסך הכול ממירה את הקוד מהשפה בה כתבנו )C#, VB.NET, F# ועוד( לשפת ביניים של ,netמוכרת בשם .IL בזמן ריצה הקוד נטען לזיכרון והפונקציה הראשונה מתקפלת לשפת מכונה )בעזרת תהליך שנקרא ,(JITהקוד בפנים יבוצע ,במידה שבפנים יש קריאות לפונקציות אחרות )בין אם זה קוד שכתבתנו ובין אם זה קוד של ה – FCLלמשל( אותם פונקציות יקומפלו לפני הביצוע שלהם לשפת מכונה) .זה הסיבה דרך אגב שבזמן מדידות ביצועים נתעלם מהקריאה הראשונה של הפונקציה מכיוון שהיא לוקחת קצת יותר זמן )הזמן של ההידור משפת ILלשפת מכונה( ,אולי זה נראה קצת פוגע בביצועים ,אבל לעומת זאת היכולת של השפה להעביר את הקוד לשפת מכונה מותאם אישית לפי הסביבה בה היא רצה ,נותן הרבה מאוד יכולות מאשר הידור רגיל למכנה המשותף הכי נמוך של כל המעבדים והסביבות.
סיכום קורס C#בסיסי :מרצה :הרב דוטנט.
שפות ו Types - אחת מהיכולות של ,netזה היכולת לכתוב בשפה אחת ולהשתמש בקוד משפה אחרת ,הסיבה שניתן לעשות זאת )בשונה משפות אחרות( היא במושג – CTS ) ,(Common Type Systemלמעשה בכל השפות של סביבת ,netכל ה – typesהם אותו דבר ) intבשפת C#ו – Integerבשפת ,vbזה למעשה .(Int32 לאחר הקומפילציה כל הקוד שאנחנו כותבים הופך ב – ILל – typesהמשותפים ,ברגע שכל השפות מדברים באותם ה – ,typesאין בעיה לכתוב קוד בשפה אחת ולהשתמש בשפה אחרת.
היכרות עם ה – Type System ישנם שני סוגים של Typesעיקריים ,האחד נקרא Value Typeוהשני נקרא Reference ,Typeלהלן רשימת הסוגים: •
– Value Typeהם כל הסוגים הפרימיטיביים )מספרים ,כן/לא וכו'( ,כל מי שהוא מסוג Structוכל מי שהוא מסוג .enum
•
– Reference Typeכל מי שהוא מסוג ,classוכל מי שהוא מסוג מערך )כולל מערכים של סוגים פרימיטיביים.
ההבדל הטכני בין שני הסוגים ,זה היכן זה יושב בזיכרון ,המשתנים מסוג Value Type מוגדרים ב – Stackלעומת המשתנים מסוג Reference Typeיושבים ב – .Heap ההבדל המעשי ביניהם זה בהשמה ובהשוואה ,לדוגמה ,נניח שיש לנו משתנה מסוג int בשם ,i1שיש בו את הערך ,5ונכתוב שורת קוד המגדירה משתנה חדש מסוג intבשם ,i2שנציב בו את הערך שיש ב – (int i2 = i1) .i1כעת יהיה כמובן גם במשתנה i2את הערך ,5מה יקרה במידה ונשנה את אחד מהמשתנים ,כמובן שזה ישנה רק אותו ,לדוגמה אם נשנה את המשתנה ,i1זה לא ישנה בכלל את הערך של המשתנה .i2 לעומת זאת במידה את אותו סיפור רק עם משתנים מסוג ) ,Reference Typeאובייקט מסוג ,(Personשיש לו משתנה מסוג מספר בשם ageעם הערך ,30וכעת נגדיר משתנה חדש מסוג Personונציב בו את הראשון ) ,(Person p1 = p2שינוי של הגיל של אחד ישנה מידית
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. גם את השני ) (p1.age = 90יגרום גם ל – p2.ageלקבל את הערך החדש ,מכיוון ששניהם מצביעים לאותו מקום בזיכרון. אותו דבר יקרה גם בהשוואה ,השוואה של שני משתנים מסוג VTמשווה את הערך האמתי שלהם ,לעומת השוואה של שני משתנים מסוג ,RTישווה את הכתובת של שני המשתנים, כלומר בדיקה האם p1שווה ל – p2ייתן את התוצאה "כן" ,לעומת השוואה למשתנה חדש בשם ,(p3 = new Person) p3גם הערך ב – ageיהיה ) 90כמו ב – (p1עדיין יחזיר "לא", מכיוון שמדובר בשני אובייקטים שונים.
המרה בין סוגי משתנים יש כמה סוגי המרות ,ההמרה הבסיסית נקראת ,castזהו התחזות של אובייקט מסוג אחד לאחר ,לדוגמה אם יש לנו משתנה מסוג longשיש בו ערך כלשהו ,ונרצה להגדיר משתנה מסוג intולהציב את הערך של המשתנה מסוג ,longזה יכול להוות בעיה ,מכיוון שייתכן שבמשתנה מסוג longיש ערך גדול יותר מאשר יכול להיכנס ב – ,intולכן נאלץ לבצע ,(int i1 = (int)theLongNum) ,castבמקרה זה אנחנו לוקחים את האחריות על עצמנו, תהליך ה – castיכול לעבוד רק על משתנים שיש ביניהם הגדרת תהליך המרה. לפעמים נרצה להמיר בין סוגים ללא המרה ,עבור המרת מחרוזות למספרים יש את פונקציית ) xx.Parseכש – xxמתייחס לסוג )לדוגמה – (int.parseוכן הלאה( ,פונקציה זו יודעת לקבל מחרוזת ולהחזיר מספר ,אופציה נוספת היא פונקציות ה – .Convert.ToXX
Boxing and unboxing במידה ונציב משתנה מסוג VTלתוך משתנה מסוג ) objectוזה חוקי ,כפי שנלמד בנושא של ) Object Orientedשלאבא מותר להצביע על בן(( ,יקרה תהליך שנקרא ,boxingמכיוון שכפי שציינו זה חוקי ,אך מצד שני VTאמור לשבת ב – Stackלעומת RTשאמורים לשבת ב – .Heap תהליך ה – ,boxingמעתיק את המידע לזיכרון המתאים ) (heapאבל מציין במפורש את סוג המשתנה המועתק ,בתהליך ה – (int I = (int)obj) unboxingצריך לציין מה המידע שאנחנו מוציאים ,התחביר נראה די דומה ל – ,castאבל הוא מטעה ,מכיוון ש – castהינו
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. המרה ,ואם יש ב – heapכרגע מספר מסוג ,intוננסה להוציא החוצה למספר מסוג long )אע"פ שאין בעיה בגודל( זה יתרסק.
השוואה בין אובייקטים ניתן להשוות בין אובייקטים באחד מהאופציות הבאות: •
האופרטורים == - =! ,משוה עבור VTאת התוכן ,ועבור RTאת הכתובת.
•
מתודת Equalשל האובייקט )מגיעה מ – (objectאותו דבר כמו האופרטור )אלא אם כן הוגדר אחרת(
•
מתודת Equalסטטית שמקבלת שני פרמטרים ,מפעילה את ה – Equalהרגיל של הפרמטר הראשון ,אך מוודא שהוא לא .null
•
מתודת ReferenceEqualתמיד משווה כתובות.
עבודה עם מחלקות כדי להתחיל לעבוד עם מחלקות ,עלינו להכיר מספר אלמנטים ,הראשון בהם נקרא Access .Modifierישנם חמשה אופציות: •
– privateשאומר שהאלמנט יהיה פרטי ונגיש רק למחלקה בה זה הוגדר.
•
- publicשאומר שהאלמנט יהיה נגיש מכל מקום.
•
– protectedרק מי שיורש מהאלמנט.
•
– internalרק אלמנטים בתוך ה – .dll
•
– protected internalאו מי שיורש או מי שמוגדר בתוך ה – .dll
בתוך מחלקה יכול להיות הרבה מאוד אלמנטים. •
משתנים.
•
פונקציות
•
בנאים )(constructor
•
מאפיינים
•
מידע סטטי.
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. נעבור על כל אחד מהאלמנטים הללו ,ונסביר את המשמעות שלהם. משתנים: כל מחלקה יכולה להכיל משתנים שזהו למעשה המידע שיש למחלקה ,לדוגמא – למחלקת "אדם" יכול להיות משתנים שיחזיקו את המידע על הגיל ,השם או גובה ,וכד'. למשתנים של מחלקה יש לי ערך ברירת מחדל ,כלומר לא צריך לאתחל אותם לפני שמשתמשים בהם )בשונה ממשתנים שמוגדרים בתוך פונקציות שנקבל שגיאת קומפילציה ,אם נשתמש בהם לפני אתחול(. פונקציות בפונקציות נשתמש בדרך כלל כשנרצה לעשות פעולות כלשהם על המחלקה כשבדרך כלל הפעולות יכללו שימוש או שינוי של המידע הקיים במשתנים. בנאים כמפתחים לפעמים נרצה לשלוט בזמן יצירת האובייקט בקוד שייווצר ,ולכן נוכל להגדיר בנאי ,הבנאי הוא מתודה מיוחדת ששמה כשם המחלקה ואין לה הגדרה של ערך מוחזר )גם לא ,(voidבזמן שמישהו יבצע פעולת newעל האובייקט מתודת הבנאי תופעל ונוכל לאתחל את המשתנים שלנו. מאפיינים בדרך כלל משתנים יוגדר כ – ,privateבמידה וכך לא נוכל לגשת למשתנים מבחוץ ,הדרך לאפשר בכל זאת קריאה או כתיבה ,נוכל להגדיר מאפיינים ,מאפיין למעשה הינו מתודה שנכתבת בתחביר מיוחד ) (get, setשמאפשרת לנו להשתמש בפונקציות הללו בצורה כאילו הם משתנים ,כדי להקל עלינו את הכתיבה )בעיקר במקרים בהם אין לנו שום לוגיקה לבצע( נוכל לכתוב ) Automatic Propertiesמאפיינים ללא לוגיקה(. סטטי מידע סטטי הינו כזה שהוא לא קשור למופע מסוים של מחלקה אלא להגדרת כל האובייקט )למשל מידע על שכר המינימום ,אינו קשור לעובד כזה או אחר ,אלא להגדרת העובד בכללותו( ,כמו כן ניתן להגדיר פונקציות סטטיות שתפקידם לטפל באותם משתנים, לפונקציות סטטיות אין דרך לגשת למשתנים רגילים ,מכיוון שאין להם גישה למילה .this כדי לאתחל משתנים סטטיים נוכל להגיר בנאי סטטי ,שיופעל אוטומטית על יד ה – ,clr בזמן שייעשה פנייה לאלמנט כלשהו של המחלקה ,לבנאי סטטי אי אפשר לשלוח
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. פרמטרים ,מכיוון שבשונה מהבנאי הרגיל שאנחנו יודעים מתי הוא יפעל )בזמן שימוש במילה (newאנחנו לא יודעים מתי הבנאי הסטטי יופעל.
מנגנון ניכוי הזיכרון מנגנון ה – ,GCמטפל בכל מה שקשור לניכוי הזיכרון שאנחנו מקצים ,בכל פעם שניצור מופע של מחלקה ,ייווצר אובייקט כלשהו ב – ,heapמישהו צריך לנקות אותו משם כאשר הוא הופך להיות זבל ,ההגדרה של משתנה זבל הינו כאשר אי אפשר להגיע אליו ,לדוגמה ,הגדרת משתנה )מסוג (Referenceבפונקציה ,לאחר היציאה מהפונקציה האובייקט נשאר בדד ב – .heap מתי שהוא ,כשאין ל – clrמה לעשות ,או שהמקום בזיכרון התמלא ,ה – GCנכנס לפעולה ,הוא מתחיל לסמן מיהם האובייקטים אותם צריך לנקות ,כאשר הוא מסיים לסמן ,הוא עוצר את פעולת התוכנית ,ומוחק את אובייקט הזבל ,כדי למנוע בעיית פרגמנטציה )כלומר – חורים בזיכרון( ה – GCמצמצם את כל הזיכרון הפנוי ,כך שכל האובייקטים אשר עדיין בשימוש ,יהיו אחד ליד השני ,פעולה גורמת לעדכון המצביעים ב – ,stackולכן יש צורך לעצור את פעולת התוכנית. כדי לשפר ביצועים ,ה – GCמחלק את הזיכרון לשלוש דורות ,כאשר הוא מבצע ניקוי על הדור הראשון ,הוא מעביר את יתרת האובייקטים ששרדו לדור השני ,כעת הניקויים הבאים של ה – GCיתמקדו רק בדור הראשון ,ורק כאשר הדור השני יתמלא )מכיוון שעברו מספר סיבובים על הדור הראשון( הוא ינקה את הדור השני ויעביר את היתרה לדור השלישי ,כך הוא מוודא שלא יבדקו לשווא אובייקטים שכבר שרדו סיבוב אחד או יותר. לפעמים אובייקטים צריכים להגדיר ) dtorמפרק – כמו ,ctorרק עם ~ בשם של המתודה( ,במקרים אלו ה – GCאמור להריץ את הקוד שלהם בזמן הניקוי )למשל סגירה של קובץ וכדו'( כדי למנוע את עצירת הקוד לזמן בלתי צפוי ,הוא מכניס את אותם אובייקטים שיש להם מפרק לתור מיוחד ,שלאחר מכן הוא יריץ את קוד הניקוי שלהם בתהליך נפרד ,ההמלצה היא להגיר מתודה בשם ) Disposeמומלץ להגדיר על המחלקה שהיא מממשת את – IDisposableנלמד על ממשקים מאוחר יותר( ,לכתוב את קוד הניקוי ,בפנים – ולקוות שהמתכנת יזכור להפעיל את המתודה ,בלי קשר לכתוב מפרק ולקרוא למתודת ה – ,Disposeוכדי לוודא שהמתודה לא תופעל פעמיים,
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. יש להוסיף במתודת ה – Disposeהגדרה ל – GCלא להפעיל אותה שוב )(GC.Suppress
מערכים ו – .List מערכים הינם הגדרה רצופה של אובייקטים מאותו סוג ,כל מי שהוא מסוג מערך יושב על ה – ,heapכל המערכים יורשים ממחלקה בשם ,Arrayשיש לה גם מתודות סטטיות שניתן להיעזר בהם לעבודה עם מערכים )כמו למשל ,Array.Copyשמאפשר להעתיק מערך אחד לשני(. הבעיה עם מערכים היא שצריך להגדיר מראש את גודלם ,כדי להתמודד עם זה יש אובייקט מיוחד בשם ) Listשמוגדר ,Genericמכיוון שצריך להגיר את הסוג בין שני סוגרים > List
מחרוזות משתנה מסוג stringהוא משתנה מיוחד ,מצד הוא הוא ,classכך שהוא מוגדר כ – ,Reference typeמצד שני העבודה אתו מאוד דומה למשתנים רגילים )ואפילו אפשר להגדיר אותו כ – .(const כל מניפולציה על מחרוזת )= Remove, Replace, +ועוד( יוצרת מחרוזת חדשה ,ואי אפשר לשנות את המקור ,ולכן כשיש קצת יותר ממעט מניפולציות על מחרוזת אחת, מומלץ להשתמש ב – ,StringBuilderכדי לשפר את ביצועי המערכת )הרבה מניפולציות על מחרוזות ,יגרום להרבה זבל בזיכרון )כל שינוי על המחרוזת זורק את הקודמת ומגדיר אחד חדשה( מה שאומר כמובן הרבה קריאות של ה – (GC
Object Oriented
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. C#הינה שפה המוגדרת כ – ,Object Orientedזה אומר שלכל אובייקט במערכת יש אבא כלשהו ,האבא הראשי של כל האובייקטים נקרא ,objectכולם יורשים ממנו והוא מדיר מספר פונקציות כלליות שכולם מקבלים ) ToString, Equalועוד(. כדי לרשת מאובייקט כלשהו ,נשתמש בסימן ) :נקודתיים( לדוגמא – Employee : ,Personבהנחה שיש אובייקט בשם Personשמכיל מידע ויכולות כלשהם ,הוגדר מחלקה בשם Employeeשיורש אותו ,ניתן להסתכל על המושג ירושה בתור הרחבה, מכיוון שאנחנו מקבלים את כל מה שיש אצל האבא ,ומרחיבים אותו עם יכולות ומידע נוסף ,בדוגמה שלנו ,לאובייקט Personיש יכולות ומידע על אדם ,אך לאובייקט Employeeיש מידע נוסף כגון השכר ,שנות וותק ועוד. לפעמים אצל האבא יוגדר מתודה )או מאפיין( שהוא ירצה לאפשר לבן לדרוס אותה )כלומר לשנות את המימוש שלה( כדי לעשות זאת הוא יכול לכתוב את המילה virtual בהגדרת המתודה ,הבן לעומת זאת כשהוא ירצה לשנות את המימוש יצטרך להשתמש במילה ,overrideכשזה יקרה ,במידה ויפעילו את המתודה )גם אם המצביע הוא של האבא – לדוגמא (Person p = new employee ,המתודה שתופעל תהיה של הבן )זהו פולימורפיזם(. ישנם מקרים בהם רוצים להגדיר אבא שהינו אבסטרקטי – כלומר ,רעיון כגון "צורה" אין משמעות לצורה ,יש משמעות לריבוע ,עיגול וכדו' ,ניתן להגדיר אובייקט אבסטרקטי בעזרת המילה ,abstractכך שלא ניתן יהיה ליצור מופע מהאובייקט ,אך הוא יוכל לשמש אבא למי שיירש ממנו )כך ניתן יהיה למשל להגדיר מערך של צורות( ובנוסף האבא יכול להגדיר מתודות )בעזרת (abstractשהבן חייב לממש אותם. במידה והמצביע הינו של האבא ,בזמן פיתוח אי אפשר לקבל ברשימה את המתודות של הבן ,ישנם שלושה דרכים כדי לקבל: •
– castהמרה רגילה ,תתרסק אם הבן אינו האובייקט האמתי שאנחנו מנסים להמיר.
•
– isמאפשר לנו לבדוק ,האם אובייקט הוא מסוג מסוים.
•
– asהמרה בטוחה ,שתחזיר nullבמידה והאובייקט לא עבר המרה בהצלחה.
מבנים )(struct
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. מבנים מאוד דומים למחלקות ) ,(classרק שיצירת מופעים של מחלקות מושיבה אותם ב – heapוגורמת ל – GCלעבוד קשה יותר ,לעומת יצירת מופעים של מבנים שמה אותם ב – stackוזה יעלם ברגע שנצא מהפונקציה. מבנים אמורים להיות קטנים )לא יותר מ – (16מכיוון שבכל העברה לפונקציה הם מועתקים )תחשבו על לולאה של אלף ,שכל פעם קוראת לפונקציה ושולחת מבנה – המבנה יועתק המון פעמים בזיכרון(. אי אפשר לרשת ממבנים ומבנים אינם יכולים לרשת ,תמיד יהיה להם בנאי ברירת מחדל )בשונה ממחלקה שיש לו ברירת מחדל ,אבל אם כתבנו בנאי משלנו נצטרך ליצור אחד נוסף אם נרצה בנאי ברירת מחדל(. בכל הבנאים שנכתוב עלינו לאתחל את כל המשתנים המוגדרים במבנה ,במידה ויהיה לנו ,Automatic propertiesנהיה חייבים להפעיל בבנאים שלנו את הבנאי ברירת המחדל.
nullable לפעמים גם עבור משתנים שהם Value Typeנצטרך להכניס את הערך ,nullלדוגמא – התשובה "כמה כסף יש למישהו בחשבון" יכולה להיות מספר כלשהו ,אבל גם התשובה של "לא ידוע" הינה לגיטימית ,כדי לתמוך בזה ,יש אפשרות להגדיר nullable ,typeההגדרה היא בעזרת סימן השאלה )? ,(bool? ,intמשתנה מסוג זה יכול להכיל את הערך שלו או .null אי אפשר לגשת למשתנה מסג זה בלי לוודא ראשית שיש לו ערך )אחרת התוכנה תתרסק( ניתן לשאול האם הוא שונה מ – ,nullאו להשתמש ב – ,HasValueכדי להוציא את הערך ניתן להשתמש ב – .Value אם נרצה לקבל את הערך ואם אין כלום לקבל ערך ברירת מחדל ניתן להשתמש בצורה הבאה )) (i.GetValueOrDeafult(10בהנחה שיש משתנה בשם ,iאו בצורה הקצרה יותר ) ,(I ?? 10שני הצורות יחזירו את הערך של iאו 10אם אין לו שום ערך.
.enums
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. ישנם מקרים בהם יש לנו רשימות שנרצה לתת למשתמש לבחור אחד מהם )לדוגמה, רשימת פירות ,או רשימת צבעים( בקוד זה אומר להיות לרוב משתנה מסוג מספר או משתנה מסוג מחרוזת ,לכל אחד מהם יש בעיה אחרת ,שימוש במשתנה מסוג מספר הופך את הקוד לבלתי קריא וקשה לתחזוקה )בעוד שנה מי יזכור מה היה המשמעות של הערך ,(23לעומת זאת שימוש במחרוזות יכול להביא לבאגים אם מתכנת כלשהו יטעה בתו )וזאת תהיה שגיאה לוגית ולא שגיאת קומפילציה( כדי לפתור את הבעיה ניתן להגדיר ,enumהמאפשר לנו לתת רשימה קבועה של שמות )ולכל אחד מהן יש ערך( ,כך המצד אחד הקוד קריא ומצד שני לא יכולות להיות טעויות. היות שבסופו של דבר enumהם מספרים ,המשתנים מסוג enumהם Value Type ונשמרים ב – .stack כדי לעבוד עם enumנוכל להשתמש במחלקה הסטטית Enumשיש לה מספר פונקציות )כמו GetNamesשמחזירה מערך של מחרוזות עם כל השמות שיש ברשימה. במידה ונרצה שהמשתנה יחזיק יותר מערך אחד בו זמנית )למשל שני פירות( ,נצטרך להגדיר שהערכים של ה – enumיהיו בחזקות של ) 2הראשון ,1 :השני ,2 :השלישי: ,4הרביעי 8 :וכן הלאה( כדי לוודא שלא תהיה שום קומבינציה של ערכים שמביאים את אותו מספר ,כדי להציב משתנה שמחזיק יותר מערך ערך נכתוב בצורה הבאה ) ,(Color c = Color.Red | Color.Blueכדי לבדוק האם cמכיל צבע מסוים ,נשתמש ב – ,HasFlagכדי שהדפסה של cתחזיר את הערך הנכון ,נוכל להוסיף על enumאת ה – attributeבשם ) Flagsעוד על הנושא בהמשך(
טיפול בשגיאות במידה והקוד שנריץ יקרום לשגיאה יתעורר מצב חריג והתוכנית תתרסק ,מצב חריג הינו כל מצב בו אנו לא יודעים או לא יכולים לטפל )חלוקה ב – ,0גישה למערך במקום שלא קיים ,גישה למשתנה שיש בו nullוכדו'( ,המנגנון של הקרסת התוכנה ,מוודא קודם האם ניסינו לטפל בבעיה ,ניסיון לטפל בבעיה יגרום לכך שהתוכנה לא תתרסק. כדי לעשות זאת ,נצטרך לעטוף את הקוד שיכול לזרוק שגיאה בבלוג של – try, catch המנגנון עובד כך:
סיכום קורס C#בסיסי :מרצה :הרב דוטנט.
•
הקוד הרגיל )בתוך בלוק ה – (tryרץ: oבמידה והכול עבר בהצלחה ,מדלגים על קטע ה – catchוממשיכים הלאה. oבמידה ונכשל ,מדלגים על הקוד בבלוק ה – ,tryלקוד בבלוק ה – catchומבצעים אותו ,ומשם ממשיכים קדימה.
קוד ה – tryיכול להיות בפונקציה שקוראת לפונקציה ,וכל הקוד הפנימי לא ירוץ במידה ויש שגיאה. לא מומלץ לכתוב סתם קטעי קוד של תפיסת שגיאות כשכל המטרה היא רק להתעלם מהשגיאה ,מכיוון שבצורה זו האפליקציה תבצע הרבה שגיאות לוגיות ,מומלץ לתפוס את השגיאות )במקום גלובלי( ולתעד אותם ,כדאי לכתוב בלוק ,tryרק כשנרצה באמת להתעלם מהשגיאה )למשל ,אם אנחנו שולחים מייל ,ולא אכפת לנו שהמייל לא הגיע ליעדו( או שיש לנו קוד ספציפי שיכול לתקן את הבעיה )כמו למשל בקשה לשם קובץ חדש ,אם שם הקובץ ששלחנו לא קיים(.
Operator Overload נושא זה מדבר על היכולת שלנו להגדיר כיצד יתנהגו אופרטורים על אובייקטים שלנו ,ישנם שלושה סוגים שונים: •
כל האופרטורים )< > = ! ++וכד'(
•
אופרטור ) indexerסוגריים מרובעות ][(
•
) Castingהגדרת המרה בין סוגים שונים(
יש לכתוב קוד מסוג זה רק אם הוא ברור ואינטואיטיבי למתכנת ,למשל אם אנחנו כותבים מחלקה שמייצגת מטבע ,ונרצה לבדוק האם ,c1 > c2זה מאוד הגיוני לכתוב קוד של Operator Overloadשל השוואה )זה יהיה פחות הגיוני על שני – Personאע"פ שאפשר להתייחס לגיל – אבל זה כבר לא אינטואיטיבי(.
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. הגדרת מתודה זו היא בעזרת הקוד הבאpublic static operator > (Currency c1, ) : .(Currency c2 אופרטור ,indexerמאפשר לנו להתייחס לאובייקט שלנו כאילו הוא מערך ,למשל אם נכתוב מחלקה בה יש private arrayונרצה לחשוף מידע לפי המיקום של המערך ,נוכל לעשות זאת בעזרת ,indexerהתחביר הינו מאוד דומה למאפיינים ,רק שבמקום שם של מאפיין נכתוב את המילה ] ,this[int indexאו כל סוג של פרמטר שנרצה לקבל. ,Castingמאפשר לנו לכתוב קוד שממיר אוטומטית בין סוגים שונים ,למשל אם יש לנו מחלקת מטבע ,ונרצה לכתוב קוד כזה ) (Currency c1 = 10זה לא ממש יעבוד אלא אם כן נגדיר המרה בין מספר למטבע ,וזה יראה כך ) public static implicit operator ) ,((Currency(int numניתן להגדיר שההמרה מרומזת ) (implicitאו מפורשת )(explicit ואז המתכנת יצטרך לכתוב ,Castלדוגמה ).((Currency c = (Currency)10
Reflection אחד היכולות המדהימות של השפה היא היכולת שלנו לחקור את האלמנטים בזמן ריצה ,חקירת האלמנטים נקראת ) Reflectionהשתקפות( ,בדרך כלל הקוד שלנו מתייחס למאפיינים ולמתודות של המופעים שלנו ,לפעמים נרצה לקבל מידע על ההגדרה עצמה ,לדוגמה :אם נרצה לתת למשתמש את היכולת להחליט איזה מתודה להפעיל מכל אובייקט שרק יהיה ,אין לנו דרך לכתוב את הקוד בצורה רגילה ,מכיוון שאנחנו לא יודעים איזה אובייקט ואיזה מתודה נרצה להפעיל ,אמנם זוהי דוגמה טיפשית במקצת ,אבל דוגמה טובה מתי נרצה לעשות משהו שמתאפשר רק בעזרת .Reflection הקוד האמתי שנעשה יהיה לרוב במקומות בהם נרצה לכתוב תשתיות ,למשל בהינתן שיש לנו מחלקה אבסטרקטית בשם ,Loggerשיש מספר מחלקות שיורשים ממנה ) (Console Logger, Mail Logger, File Loggerונרצה לטעון דינמית לפי הגדרה בקונפיג את הלוגר המתאים ,בקוד סטנדרטי ,נקראת את התוכן מקובץ הקונפיג ,ונבצע פעולת ifכדי לבדוק איזה מחלקה צריכה להיטען ,יש עם זה מספר בעיות ,ראשית זה די Hard codeואם יהיה לנו מחלקה חדשה ) (Web Service Loggerנצטרך לשנות את הקוד שלנו ,שנית תמיד נצטרך להכיר מראש בקוד שלנו את כל סוגי ה – .Loggers
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. כדי לפתור זאת ניתן להשתמש ב – Reflectionוליצור דינמית לפי שם המחלקה את האובייקט. Reflectionמאפשר לנו ,לבדוק אילו מתודות ,מאפיינים ,משתנים וכדו' קיימים במחלקה ,ומאפשר לנו להפעיל אותם או לשנות אותם לפי השם שלהם )גם הם מוגדרים כ – ,(Privateחשוב לזכור Reflectionהוא לא משהו גרוע ,זה מאפשר לנו לכתוב תשתיות שלפעמים אין דרך אחרת לממש אותם ,אבל מצד שני זה יכול לאפשר לנו לעשות שטויות )כמו לשנות privateשל מחלקות( ,יש להשתמש במנגנון לפי הצורך ובצורה חכמה.
Attribute כבר ראינו דוגמה לשימוש ב – Attributeלמי שזוכר ,כשלמדנו על enumsהכרנו את Flagsשמגדיר כיצד להדפיס enumsשיכול להיות יותר מערך אחד. attributeהינו מנגנון שמאפשר לנו לשים "מדבקות" או מידע נוסף על אובייקט ,אנחנו מגדירים אותם כמידע על האובייקט בשונה ממאפיינים למשל שזה מידע של האובייקט )שימו לב להבדל בין "על" לבין של"( ,מידע על האובייקט הוא מידע שלא מעניין את האובייקט אלא מתייחס לאלמנטים אחרים שירצו להשתמש באובייקט וצריכים לדעת כיצד ,כמו למשל Serializableשמגדיר למחלקה שמתעסקת עם סירליזציה ל – xml האם האובייקט ניתן להמרה ל – ,xmlאו DebuggerDisplayשמאפשר לנו להגדיר ל – debuggerכיצד להציג את האובייקט כשיש ,Break Pointואנחנו מסתכלים על האובייקט. יצירת Attributeהיא פשוטה ,בסך הכול צריך לכתוב מחלקה היורשת מ – ,Attribute וכל מה שצריך לעשות זה לשים את המחלקה החדש שלנו על אלמנט כלשהו )מחלקה, מאפיין וכדו'( ,הנקודה שזה לא ישפיע על שום דבר ,מכיוון שאם אף אחד )שום קוד חיצוני( לא יבדוק האם השתמשו בו ,לא יקרה שום דבר. כדי לבדוק האם יש Attributeיש צורך להשתמש ב – ,Reflectionכל אלמנט כמו ) FieldInfo, PropertyInfoוכדו( יש להם מתודה בשם GetCustomAttribute שמאפשר לקבל גישה לאותם מחלקות שהשתמשנו בהם. ממשקים
סיכום קורס C#בסיסי :מרצה :הרב דוטנט. ראשית כדי לא להתבלבל בין ירושה לבין ממשק ) ,(Interfaceנגדיר כך :אנחנו יורשים )מרחיבים( מחלקות ,ואנחנו מממשים ממשקים ) .(interfacesההבדל הינו מהותי, ירושה מקבלת את מה שיש לאבא ומאפשר להוסיף או לשנות ,מימוש ,הינו בסך הכול הצהרה על יכולות שיש לנו ,יכול להיות שיהיה לנו את היכולות הללו בלי קשר ,אבל אם נצהיר עליהם נוכל לקבל תוספות. לדוגמא ,יש פונקציה בשם Array.Sortשיודעת לקבל כל מערך שהוא ולמיין אותו, בתנאי שהצהיר על עצמו שהוא מממש את ,IComparableשבסך הכול מגדיר שיש למממש מתודה שמקבלת שני אובייקטים ומחזירה האם צריך להחליף ביניהם או לא, יכולנו להחזיק את המתודה ללא קשר למימוש ,אבל מתודת Array.Sortלא תדע להשתמש בפונקציה מכיוון שלא הצהרנו על כך. הגדרת ממשק מאפשרת שימוש בדומה לירושות ,שמצביע של אבא יכול להסתכל על בן ,מה שמאפשר לנו לכתוב מתודה שמקבלת ממשק ,ונוכל לשלוח אליה כל מה שמממש את הממשק ,בלי שיהיה אכפת לנו בזמן כתיבת הקוד מיהו אותו אובייקט. דוגמאות לממשקים: •
– IFormatableהמאפשר לנו להשתמש במחלקות שלנו עם string.Format
•
– IComparableהמאפשר השוואה בין שני אוביקטיים.
•
- IEnumerableהמאפשר להשתמש עם foreachעל האובייקטים שלנו.