Browse by Tags
All Tags »
C# (
RSS)
אחד השיעורים הראשונים שכל מפתח דוט-נט לומד לשנן, הוא ש"בכל מקרה בו רוצים לחבר הרבה מחרוזות אחת לשניה, אסור בתכלית האיסור להשתמש באופרטור +, ובמקום זה להשתמש במחלקה StringBuilder". אז אחרי הדקלום המושלם הזה, אתה שואל "אוקיי, אבל למה בעצם?", והתשובה היא בדרך כלל "בגלל שמחרוזות הן Immutable, זה אומר ששימוש באופרטור+ יביא להקצאות מיותרות של מחרוזות שיהווה כל מיני 'שלבי ביניים' עד שנגיע למחרוזת הסופית". כפי שאני רואה את זה, הדקלום הזה יותר מטעה מאשר מה שהוא תורם....
אם יש משהו אחד בסיסי שכולם יודעים על פיתוח תוכנה, הוא שלא ניתן ליצור תלות מעגלית בין פרוייקטים (DLL'ים). אם למשל יש לנו פרוייקט A, שפונה לפרוייקט B, אז לא יהיה ניתן שאותו פרוייקט B יפנה בחזרה לפרוייקט A בתור רפרנס. אם היינו עושים דבר כזה, היתה נוצרת לנו תלות מעגלית בין שני הפרוייקטים. וזה רע, בגלל שכשהקומפיילר ירצה לקמפל את A, הוא יראה שהוא תלוי ב-B, אז הוא יגש ל-B וינסה לקמפל אותו. אבל אז הוא יגלה ש-B למעשה תלוי ב-A, ואז הוא יחזור חלילה עד אין קץ.. האמנם? אם יצא לכם לפשפש מספיק במבנה הספריות...
אולי לא הייתם מיודעים לכך, אבל בכל פעם שאתם פונים ל-DateTime.Now, אתם גורמים בעקיפין הקצאת זכרון דינאמית על ידי Boxing של Int32. הסיבה לכך טמונה עמוק בתוך המימוש של Now, שאם נסתכל מקרוב, נוכל לראות שהוא למעשה עוטף קריאה ל-UtcNow והמרתו לזמן מקומי על ידי קריאה לפונקציה ToLocalTime. עם הקריאה ל-UtcNow אין בעיה, מאחר ובסך הכל נעשית קריאה פנימית ל- GetSystemTimeAsFileTime , שמחזיר את הזמן הנוכחי בפורמט UTC. הבעיה האמיתית טמונה במימוש של ToLocalTime, או למעשה במחלקה CurrentSystemTimeZone בה היא נעזרת...
אם יש דבר אחד שאני לא אוהב לראות בקוד, זה שימוש מוגזם ב- Region 'ים. הטיעון העיקרי של התומכים בשימוש באותם Region'ים הוא שאפשר להגיע בעזרתם לקוד הרבה יותר "נקי", "מסודר", או חס וחלילה, "קל לתחזוקה". תלוי ביום, ומצב הרוח שלי באותו רגע, אני אוטומטית משיב: הפוך גוטה, הפוך . הדבר היחיד ש-Region'ים יודעים לעשות זה להחביא קוד . מה שלעצמו מרגיש די אבסורדי מאחר ורובנו בדרך כלל נמצאים במירוץ לא נגמר אחרי מסך גדול יותר, רזולוציה מטורפת יותר, פונט קטן יותר - העיקר להכניס...
כשזה מגיע לתכנות מקבילי, משימה נפוצה למדי היא להשתמש בת'רד נפרד בתוכנית שיטפל בכל מיני קלטים/בקשות שהתוכנית שלנו מקבלת. מה שקורה בדרך כלל הוא שבזמן היצירה של הת'רד, מכניסים אותו לפונקציה עם לולאה אינסופית, ובתוך הלולאה מחכים לקבל Signal על כך ש"יש עבודה" לעשות. בצורה גסה, התבנית הבסיסית נראית כך: AutoResetEvent m_event = new AutoResetEvent ( false ); private void WorkCycles() { while ( true ) { // wait for a signal m_event.WaitOne(); // do work.. } } שימוש ב- AutoResetEvent מאוד...
שימוש ב-Assert יכול להיות פתרון נוח ושימושי כשרוצים לוודא שה-State שאנחנו נמצאים בו כרגע, באמת מתאים למה שאנחנו מצפים. אנחנו יכוללים להשתמש בו בשביל לבדוק כל מיני מקרי קצה ביזארים, כל מיני מצבים ש"אין מצב" שיקרו (על פי הלוגיקה הקיימת בקוד). כל מיני מצבים שהמשך הקוד שלנו מסתמך על זה שהם לא אפשריים, ולא יקרו בזמן הוא רץ. בדרך כלל נקרא ל-Assert דרך המחלקה הסטאטית Debug, שעושה שימוש ב- ConditionalAttribute עם הערך "DEBUG", כך שאנחנו יודעים שכל הבדיקות האלו לא יעלו לנו כלום כשנצא לגרסת...
בדוט-נט קיים מנגנון סנכרון אובייקטים מובנה. ובין אם אנחנו מודעים לזה או לא, אנחנו משתמשים בו בכל פעם שאנחנו באים לסנכרן קטעי קוד באפליקציה שלנו. המשמעות היא שאנחנו לא צריכים לגשת לאובייקטי סנכרון מובנים של מערכת ההפעלה, בכל פעם שאנחנו רוצים לעשות lock קטן בקוד. מה שמקנה לנו יתרון בביצועים בהשוואה לשימוש באובייקטי סנכרון כגון Mutex או Semaphore (במקרה שלנו, אובייקט הקרנל היחיד בו משתמשים הוא Event). עבור כל אובייקט שקיים אצלנו באפליקציה, קיים גם SyncBlock שמשוייך אליו. אותו אובייקט SyncBlock אחראי...
אובייקטים בדוט-נט אינם בנויים בתור יחידות בודדת ושלמות שנמצאות על ה-GC Heap, שמכילות את ה-Data Member'ים שהגדרנו להן. בפועל, מעבר למידע "הנראה לעין" שאנחנו מגדירים כשאנחנו כותבים מחלקה חדשה, מתווספים גם מספר שדות "בלתי נראים" לאובייקט שלנו שנמצאים באזורים שונים בזכרון. השדה הראשון והבסיסי הוא המצביע ל-MethodTable השייך לטיפוס שלנו. אני מדלג על ההסבר על ה-MethodTable והמידע שהיא מכילה (שימו לב שלא מדובר ב-VTable עצמו, אלא בטיפוס שבין היתר מכיל מצביע אליה), ובמקום זה אתמקד בנקודה...
דבר אחד שימושי בדוט-נט הוא התמיכה הטבעית הקיימת ל-Event'ים. כדי ליצור אירוע למחלקה שלנו, כל מה שעלינו לעשות הוא להגדיר Event חדש בעזרת ה-Delegate המתאים, וזהו זה. שימו לב שאפילו לא היינו צריכים לכתוב Accessors כלשהם, מאחר וכברירת מחדל, אלא אם כן לא הגדרנו אחרת, הקומפיילר יחולל כבר Accessor'ים משלו שידאגו לנהל עבורנו את הרישומים עבור האירוע שהגדרנו. זוהי נקודה שהרבה פעמים לוקחים כמובן מאליו, ולא מפנים אליה תשומת לב מיוחדת, ולמרות זאת, מסתתרת מאחוריה משמעות קריטית כש-Multithreading נכנס לתמונה...
לפני כמה פוסטים הבנו מה המשמעות של אופרטור ההשוואה (==) כשנעשה בו שימוש בקונטקסט של Reference Types. למעשה הגענו למסקנה שבמימוש הבסיס הוא למעשה עונה לנו על השאלה "האם 2 הרפרנסים שיש לי מצביעים לאותו אובייקט?", כלומר יש לנו כאן השוואה של כתובות בזכרון. אם הן זהות, קיבלנו true; אחרת, false. אבל רק רגע, בנקודה הזאת אנחנו נזכרים לרגע בפונקציה ReferenceEquals שנמצאת תחת מחלקת הבסיס Object. כמו שמשתמע מהשם, גם התפקיד שלה הוא לענות בדיוק על אותה השאלה. אז אם 2 הדרכים הללו למעשה "עושות אותו...
רובנו כבר יודעים לשנן את האימרה שאומרת שכשאנחנו כותבים קוד, אנו כותבים אותו עבור בני אדם, ולאו דווקא למחשב, ולכן יותר חשוב שהקוד שאנו כותבים יהיה יותר קריא מיעיל. למרות זאת, פעמים רבות אנו שוכחים פרטים קטנים שלאו דווקא פוגעים בקריאות הקוד, כאלו שלא נראים לנו חשובים בעת כתיבת הקוד. למעשה, אני מדבר על כתיבת קוד שיהיה נוח לדבג. כי אם החוק אומר שנשקיע יותר זמן בקריאת קוד מאשר בכתיבת קוד, אז הכרחי שהזמן שנעביר בלדבג את הקוד יעלה כנראה על הזמן שנדרש לכתוב או לקרוא את הקוד גם יחדיו. לכן, כדי לעשות את ה"אקסטרה"...
כל מי שאי פעם ניסה לדבג קוד שרץ תחת כמה ת'רדים בו זמנית, נתקל כנראה בתופעה המציקה שברגע שמבצעים Step Inside וכו', בדיוק כשאתם באמצע הדיבאגינג .. הדיבאגר לוקח אתכם בחזרה 200 שורות קוד לאחור, בפרוייקט אחר בכלל, חותך את חוט המחשבה שלכם בדרך, ובעצם מחזיר אתכם בחזרה ל breakpoint המקורי שהגדרתם. למה? כי פתאום ת'רד אחר בכלל הגיע לאותו ה breakpoint. הצורה בה רוב האנשים מתמודדים עם הבעיה, היא דרך כל מיני "משחקים" עם הדיבאגר. למשל לבטל את ה breakpoint ברגע שנעצרנו בו בפעם הראשונה .. לזכור...
ב-#C יש 2 דרכים לביצוע cast בין טיפוסים. הראשונה היא הדרך הסטנדרטית והמוכרת: class Person { } class Program { static void Main() { object foo = new Person (); Person bar = ( Person )foo; } } ולעומתה, השימוש במילת המפתח "as": object foo = new Person (); Person bar = foo as Person ; ההבדלים ניסיון לבצע קאסט "סטנדרטי" על טיפוסים שאין ביניהם קשר של הורשה/מימוש (למשל ניסיון לבצע קאסט בין מחלקת Car לבין Apple), יוביל לזריקת שגיאה מסוג InvalidCastException. לעומת זאת, ניסיון לבצע קאסט...
שאלה: האם לתוכניות שרצות בדוט-נט יש בדיקה אוטומטית עבור Arithmetic Overflows? קחו לדוגמה את התוכנית הבאה, מה לדעתכם יהיה הפלט? או שבכלל תזרק שגיאה? class Program { static void Main( string [] args) { int foo = 1; int bar = int .MaxValue + foo; Console .WriteLine(foo); } } אם הניחוש שלכם היה 2147483648- (שווה ערך ל int.MinValue), צדקתם. זאת טעות נפוצה לחשוב שכברירת מחדל הסביבה תבדוק כל הזמן האם קיימת אי התאמה בין גודל הערך שאותו אנחנו מנסים להכניס למשתנה. רוב המפתחים מצפים שעל פעולה כמו בדוגמה למעלה...
אחת הבעיות שיכולות להתעורר במהלך בדיקות UnitTests היא שינוי מצב ה-State בין הבדיקות. למה הכוונה? יתכנו מצבים בהם הרצה של Test אחד יכולה להשפיע על מהלך הבדיקה של Test אחר. אם במידה וקיים משתנה סטטי כלשהו באפליקציה שלכם, וב-Test הראשון איתחלתם את ערכו ל-10 למשל, אזי ברגע שתפעיל את ה-Test השני, הערך של אותו משתנה סטאטי עדיין יהיה 10. ובמידה והבדיקה תערב בצורה כלשהיא את אותו משתנה סטאטי, יתכן ולוגיקת הבדיקה תפגע. איך ניתן להמנע ממצב כזה? אם ניקח לדוגמה את nUnit, אנחנו יכולים להשתמש ב-Attribute'ים...
More Posts
Next page »