Browser Wars and Internet Explorer’s Compatibility View issue.

The following story is based on true events... (Hebrew).

“Crisis”: Unlike the different internal presentations, on our customer’s CEO computer, the web application we’ve developed appeared broken, causing poor user experience. For us, the problem was clear and known: the CEO was using IE8 set to Compatibility View.

Proper Development provides outsourcing and consulting services in the .NET development framework for a certain company dealing with security. Amongst other activities, we develop an extensive Web Application designated to be the front-end interface for the different product users. The application is developed in ASP.NET 4, using rich graphics from professional designers, and includes multiple ASP.NET Themes and MS ASP.NET Ajax usage for a better and faster user experience. In advanced stages of the development, towards the oncoming pre-release version, we were called to the CEO’s office, who attempted to use the web application as it was installed on the QA servers, and that’s when the problem occurred - the IE Compatibility View Issue.

In order to understand what Compatibility View is, several words are required in order to understand Web Application development and Browser Wars. Web Application development is inherently different from developing regular desktop applications, as the user is not using a local installed application, but an Internet browser. The user experience depends explicitly on the type and version of the browser used. Usually, the more updated the browser is, the better user experience. But, there had been many changes throughout the years, and the fact that Microsoft’s Internet Explorer was incorporated within Windows helped it become the worlds most dominant browser (at least on PC machines), and had Microsoft try to lead the browser market by developing their own standards, not following the common worldwide standards. This caused many web sites to break on competitor browsers, as they were developed targeting IE only, and a lot of effort had to be invested (time & money), or even postponing product deliveries, in order for the web site to be compatible to both IE and non-IE browsers.

But what seemed to be like a certain victory for Microsoft over the competitors, changed several years back with the arrivals of strong competitors browsers (Firefox, Safari, Google Chrome etc’), which provided a more reasonable solution for the customers growing needs, whereas Microsoft neglected the competition and chose not to develop more advanced versions for IE. A simple example for this would be tabbed-browsing, which wasn’t available till the end of 2006 (in IE7), whereas the competitors had this feature “built in” for years. In the past several years, Microsoft understands that neglecting the users, and standards, was wrong, and is investing a lot of effort to develop more advanced and competitive browsers, which support the world wide standards. We saw this first with the emergence of IE8, which was a major milestone towards implementing the world wide standards (but still, very far from it), and the near coming IE9, which should be a major upgrade, both as far as standards go, as well as performance.

The problem Microsoft was facing, ironically, were exactly those same web sites which were built for years targeting older IE browsers, and not according to world wide standards. Browsing to these websites using IE8 could cause them to break, just like in competitor browsers. As in other cases, Microsoft solved this problem by incorporating a “Compatibility View”, and with a click of a button, an IE8 user can view the currently displayed website as if using IE7, viewing the website correctly.

This was the case for our CEO customer. He was using IE8 which was set to Compatibility View, and actually viewed our application as if he was using IE7. The web application was developed to support IE8 and Firefox 3.5, in order not to spend precious “time to market” development time in making the application IE7-compatible, and that’s why it broke on his machine. With click of a button, we switched back his browser to IE8 view, and the problem was gone. But this didn’t solve the true problem at hand: any user could tackle this exact problem, and accidentally browse using Compatibility View. The common programmable solutions, which suppose to help IE8 understand that it’s supposed to display the website not in Compatibility View - don’t always work, for example in cases where IE8 could be configured so that intranet sites always display in Compatibility View. More over, there are users who actually browse using IE6 or IE7, and their user experience could be quite bad.

The ideal solution was allegedly simple: lets support all the browsers! But this is probably the non-recommended option. The time and effort required to support older browsers, including QA and postponing product releases - is not always worth the effort, except for uncommon scenarios. In a world where browsers constantly become more and more advanced, that leading websites decide to stop older IE support (google, YouTube and Facebook Chat), and that Microsoft recommends to upgrade to IE8 - you have to have a good reason to continue financing support for IE6 or IE7. Over 70% of Internet users don’t use these older versions, as of August 2010.

So, eventually, there were two choices to choose from, concerning older IE users:
  1. Include support for IE7 (there was no intention to support IE6 at all in the case discussed) - this would have impact on development and QA, with more chance for bugs and support later on.
  2. To detect the version of the IE browser, and act accordingly in the following manner:
    1. For IE6/7 - display a message regarding the supported browsers, plus a recommendation to upgrade to those supported browsers (this method is also used for other non-IE browsers which aren’t officially supported).
    2. For IE8, in Compatibility View - display a message explaining what Compatibility View is, including a recommendation to switch it off, including instructions on how to do so.

    In any case, the user can “accept responsibility”, and decide to continue browsing using a non-supported browser.

    It’s important to understand, that supporting IE7 is something that can be added later on, upon customer feedback. You can always decide to support older browsers later on. Also important is the understanding, that supporting certain types of browsers and giving up others, especially IE6/7 which are still common browsers, should be a strategic management-level decision, and not the development team’s call (although the development team should take part in this decision making).

    In our case, the decision was indeed not to support IE7, till possible user feedback will demand such support (if at all), and to display those messages encouraging users to upgrade to IE8 or FF 3.5, or above. If a user browses using Compatibility View, we’ll provide explanation on how to switch back to IE8.

    Read how to programmatically check if the user’s browser is set to Comptibility View.

Currently rated 3.3 by 6 people

  • Currently 3.333333/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 9/20/2010 at 1:54 AM
Tags:
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

How-to: Detecting IE8 with Compatibility View and general browser support.


If you decide to stop supporting all types of browsers, mainly older IE browsers, you’ll probably be required to detect the browser and it’s version using either client or server programming. Client browser detection can use viewed here. This blog entry focuses on an ASP.NET server side solution, which will allow not only to detect if the browser is supported, but on how to detect IE8 running in Compatibility View (optionally notifying the user on how to switch from this view to IE8).

The solution is quite simple - it relies on the user-agent string sent by the browser. Browsers send information to the server, one of them is HTTP_USER_AGENT, which describes the user’s browser. For example, IE browsers incorporate the keyword MSIE plus the browser’s version. So IE6 will show as MSIE 6, IE7 will show up as MSIE 7 and IE8 will show up as MSIE 8. But how will IE8 appear in Compatibility View (that is, IE7)? Apparently, the browser will send a string with MSIE 7. So, how can we tell whether this is really an IE7, or IE8 in Compatibility View? The answer relies also in the user-agent string: IE8 incorporates a new “Trident” token in the user-agent string, even in Compatibility View. So by combining a check for MSIE 7 with Trident, allows understanding that the user is running in Compatibility View.

In short, the following ASP.NET code will detect if the browser is running in Compatibility View:
bool isCompatibilityMode = (Request.UserAgent.IndexOf("MSIE 7") > -1 && Request.UserAgent.IndexOf("Trident") > -1);

Naturally, you may choose to refactor this code to “better looking code”, such as testing for IE first, and then testing for the major version of the browser etc’.

You can also combine this code in different scenarios: You can detect other browsers, you can block certain browsers, you can notify the users that they’re using unsupported browsers, and you can notify how to switch off Compatibility View etc’. It could also be a good practice to store the user’s decision in a Cookie, Session or some other Profile settings, in order not to process this code every time the browser makes a request.

You can read about the cause for this code in the first place in Browser Wars and Compatibility in Internet Explorer (Hebrew).

To see what your user agent is, click here.

 

Currently rated 2.4 by 7 people

  • Currently 2.428571/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 9/20/2010 at 12:29 AM
Tags:
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

מלחמת הדפדפנים ובעיית התאימות של Internet Explorer

הסיפור הבא מבוסס על מקרה אמיתי... (אנגלית).


"שוד ושבר": שלא כמו במצגות פנימיות שערכנו לחברה, במחשב המנכ"ל, אפליקציית ה-Web שפיתחנו הוצגה בצורה בעייתית, מה שגרם לחוויית משתמש מפוקפקת. מבחינתנו, הבעיה הייתה ברורה וצפה מיד: המנכ"ל גלש ב-IE8 במצב "תאימות לאחור" (Compatibility View).


חברת פרופר מספקת שירותי אאוטסורסינג וייעוץ בתחום .NET לחברה מסוימת בתחום האבטחה. בין השאר אנו מפתחים אפליקציית Web, שמטרתה להיות ממשק משתמש לבעלי התפקידים השונים. האפליקציה כתובה ב- ASP.NET 4 תוך שימוש בגרפיקה עשירה מאת מעצבים מקצועיים, מרובת Themes ו-Ajax לטובת חוויית משתמש טובה, נינוחה ומהירה. בשלבים מתקדמים בפיתוח ולקראת יציאה של גרסת התנסות ללקוחות, נקראנו למנכ"ל החברה, אשר ביקש להשתמש באפליקציה כפי שהיא נמצאת בשרתי הבדיקות, ואז צצה הבעיה המדוברת - בעיית התאימות בדפדפני IE.


על מנת להבין מהו Compatibility View, כדאי לתת מספר מילים לגבי פיתוח אפליקציות Web ומלחמות הדפדפנים. פיתוח אפליקציית Web שונה מהותית מפיתוח אפליקציות רגילה, בכך שהמשתמש לא עובד עם תוכנה ייעודית המותקנת מקומית, אלא באמצעות הדפדפן. כך שחוויית המשתמש תלויה בצורה חד-משמעית בסוג הדפדפן שהוא משתמש וכמובן בגרסת הדפדפן: בדרך כלל, ככל שהדפדפן מעודכן יותר, כך חוויית המשתמש צפויה להיות עשירה יותר ומהירה יותר. אלא שהיו הרבה תהפוכות לאורך השנים, והדומיננטיות של דפדפן Microsoft Internet Explorer, עקב הימצאותו כחלק ממערכות ההפעלה מסוג Windows, גרמו למיקרוסופט עד לפני מספר שנים לנסות להכתיב לשוק סטנדרטים משלה. התוצאה: שנים רבות של פיתוח לפי דפדפני IE הדומיננטיים, גרמו לכך שאתרים רבים פשוט לא עבדו בצורה תקינה על גבי דפדפנים מתחרים. על מנת שהאפליקציה תעבוד בצורה תקינה הן על גבי IE והן על גבי דפדפנים מתחרים, נדרשו מאמצים הכרוכים לרוב הן בהוצאה כלכלית על זמן הפיתוח וההתאמה, ולעתים אף דחייה בשחרור גרסאות של אפליקציות שונות.


אך מה שהיה נראה כמו ניצחון בטוח של מיקרוסופט על המתחרים, השתנה עד לפני מספר שנים עם יציאתם של דפדפנים חזקים מתחרים (Firefox, Safari, Google Chrome ואחרים), אשר סיפקו מענה הולם לצורכי המשתמשים, בעוד מיקרוסופט זנחה את התחרות ובחרה במשך תקופה ארוכה שלא לפתח גרסאות חדשות ומתקדמות ל-IE. דוגמא פשוטה לכך היא גלישה באמצעות טאבים, שפשוט לא הייתה זמינה עד סוף 2006 (ב-IE7), בעוד שאצל המתחרים זו הייתה תכונה מובנית מזה מספר שנים. מיקרוסופט בשנים האחרונות מבינה שהיא טעתה בהזנחת המשתמשים, וחשוב מכך, בהזנחת הסטנדרטים, ועושה מאמצים רבים לפתח דפדפנים עדכניים ותחרותיים אשר תומכים בסטנדרטים העולמיים המקובלים. לראשונה ראינו זאת ביציאת IE8, אשר היה בבחינת אבן דרך משמעותית בהליכה לקראת הסטנדרטים העולמיים (אבל עדיין מרחק רב מהרצוי), ובקרוב אף ייצא IE9 שאמור להיות קפיצת דרך משמעותית, הן בתאימות לסטנדרטים והן מבחינת ביצועים.


הבעיה שמיקרוסופט הייתה צריכה להתמודד עמה, היא אותם אתרים אשר נבנו במשך שנים לפי גרסאות קודמות של IE, שכזכור, נבנו שלא לפי הסטנדרטים העולמיים. גלישה לאתרים אלה ב-IE8 עלולה לגרום להם להיות מוצגים בצורה לא תקינה, בדיוק כמו בדפדפנים המתחרים. כמו במקרים אחרים, מיקרוסופט פתרה את הבעיה על ידי שילוב של מצב "תאימות לאחור" (Compatibility View) - ובלחיצת כפתור, משתמש IE8 יכול לצפות באתר שבו הוא נמצא, כאילו הוא גולש לשם בדפדפן IE7, ולראות את האתר בצורה תקינה.


אם כן, זה היה המצב שבו היה נתון מנכ"ל החברה - הוא גלש לאפליקציה שלנו בדפדפן IE8, שהיה מכוון למצב "תאימות לאחור", ולמעשה צפה בו כאילו הוא גלש בדפדפן IE7. האפליקציה שלנו פותחה כך שתתמוך ב-IE8 וב-Firefox 3.5, על מנת שלא להשקיע בזמן פיתוח מיותר ב-IE7, ולכן לא נצפתה אצלו במחשב בצורה תקינה. בצורה פשוטה החזרנו את הדפדפן של המנכ"ל למצב IE8 והדברים הסתדרו. אך זה לא פתר את הבעיה האמיתית: משתמש ארעי עלול למצוא את עצמו בדיוק באותה סיטואציה של המנכ"ל, ולגלוש "בטעות" בדפדפן IE8 במצב IE7. גם הפתרונות התכנותיים המקובלים לכאורה, אשר אמורים "לסייע לדפדפן IE8 להבין שהוא אמור להציג את התוכן שלא במצב IE7" - לא תמיד עובדים, ולו רק בשל העובדה ש-IE8 עלול להיות מכויל לכך שאתרים פנים-ארגוניים (Intranet websites) יוצגו תמיד במצב של תאימות לאחור. יתרה מכך, ישנם משתמשים אשר עדיין גולשים בדפדפני IE6 או IE7, וחווית המשתמש שלהם צפויה להיות מאוד לא מוצלחת.


הפתרון האידאלי לכאורה היה פשוט: יש לתמוך בכל הדפדפנים. אך בפועל, זהו כנראה הפתרון ה"פחות מוצלח". משך ההשקעה בזמן פיתוח והתאמה לדפדפנים ישנים, כולל בדיקות QA ודחיית יציאתן של גרסאות - פשוט לא תמיד שווה את ההשקעה, אלא במקרים יוצאי דופן. בעולם שבו הדפדפנים מתקדמים בקצב אדיר, שאתרים מובילים בשוק מודיעים שהם יפסיקו תמיכה ב-IE ישנים (google, YouTube ו-Facebook Chat), ושמיקרוסופט בעצמה ממליצה לשדרג ל-IE8 - צריך סיבה טובה לממן המשך תמיכה ב-IE6 או IE7. למעלה מ-70% מהגולשים השונים לא עובדים בגרסאות ישנות אלה, נכון לאוגוסט 2010.


לכן, בסיכומו של דבר היו שתי אפשרויות פעולה בעבור משתמשי IE בגרסאותיו הישנות:

  1. לכלול תמיכה ב-IE7 (לגבי IE6 "ומטה" מעולם לא היה ספק, שלא תהיה תמיכה) - מה שכאמור היה גורר מאמצים בפיתוח ובבדיקות, ופותח פתח לבאגים ותחזוקה עודפת.

  2. לאתר את סוג דפדפן ה-IE של המשתמש ולפעול בהתאם:

    1. במידה ומדובר ב-IE 6/7 - לספק למשתמש מסך הסבר עם המלצה לשדרג את הדפדפן  (פתרון זה טוב גם עבור דפדפנים אחרים שאינם נתמכים באופן רשמי).

    2. במידה ומדובר ב-IE8, במצב תאימות לאחור ל-IE7 - להציג מסך למשתמש עם הסבר לגבי מצב "תאימות לאחור", הכולל המלצה לכבות מצב זה ולעבור ל-IE8, כולל הנחיות כיצד לעשות זאת.


בכל מקרה, המשתמש יוכל להחליט "על אחריותו", להמשיך בדפדפן שאיננו נתמך.


חשוב להבין שהאפשרות לספק תמיכה ל-IE7 יכולה להגיע גם בשלב מאוחר, לאחר קבלת חיווי מהמשתמשים עצמם. תמיד אפשר לקבל תגובות מהשטח ולקבל החלטה בשלב מאוחר יותר, להמשיך ולהשקיע בפיתוח והתאמה לדפדפנים ישנים יותר. עוד חשוב לציין, שההחלטה לתמוך בסוגים מסוימים של דפדפנים אל מול ההחלטה לוותר על דפדפנים אחרים, בפרט IE6/7 שהם עדיין דפדפנים נפוצים, אמורה להיות החלטה אסטרטגית ברמה הניהולית, ולא צריכה להיות החלטה של צוות הפיתוח בלבד (אם כי בהחלט יש להתייעץ עם צוות הפיתוח לגבי המשמעויות).


במקרה המדובר הבחירה בסופו של דבר הייתה לוותר על IE7, עד אשר ואם תגיע דרישה מהמשתמשים אשר תיתפס כמחייבת, ולספק הודעות מתאימות אשר יעודדו אותם לשדרג ל-IE8 או FF 3.5 ומעלה. במקרה שבו משתמש גולש במצב תאימות ל-IE7, נספק הסברים כיצד לעבור ל-IE8.


קרא כיצד לבצע לבדוק בקוד האם דפדפן ה-IE רץ במצב Compatibility View.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 9/20/2010 at 12:13 AM
Tags:
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

הכנסת null כערך ל-textbox

נושא קטן, ישן ומעצבן ששוב בזבז שעות עבודה מיותרות. בניגוד לכל הדפדפנים האחרים, כאשר מנסים להכניס לתיבת טקסט ערך null באמצעות javascript, הדפדפן IE "מגדיל ראש" שלא לצורך ובמקום לאפס את הערך למחרוזת ריקה, הוא מכניס את המחרוזת "null". מה שמפתיע יותר הוא שגם IE8, שכבר מתחיל להתקרב יותר ויותר להיות דפדפן שעובד לפי התקנים, גם הוא ממשיך עם ההתנהגות הזו. ככל הנראה הסיבה להתנהגות זו היא ש-MS רצו בזמנו להקל על התוכניתנים ולהבין מחרוזות הכתובות ללא גרשיים כמחרוזות, ופשוט שכחו להתייחס למקרה המיוחד של null. אפשר לראות בזה באג כיוון שבכל הדפדפנים האחרים זה עובד בצורה תקינה (לפחות בבדיקה מול: chrome, safari, firefox).
 
אגב, התופעה משתחזרת גם בדפדפני IE ישנים יותר, כגון IE6 ו- IE7 (אשר עדיין בשימוש בפלחים גדולים באוכלוסיה).
 
ניתן לשחזר בצורה מאוד פשוטה באמצעות דף html. פשוט להעתיק את שתי השורות הבאות ולהריץ:

<input type='text' id='txt' />
<input type='button' onclick="document.getElementById('txt').value=null;" value='insert null' />

 
התיקון פשוט מאוד.סה"כ יש להחליף את null במחרוזת ריקה:
 

<input type='text' id='txt' />
<input type='button' onclick="document.getElementById('txt').value='';" value='insert null' />

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 11/17/2009 at 11:09 PM
Tags: , ,
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

קוד מקור לטובת דיבאג

חדשה ישנה אבל לא בהכרח מוכרת ונפוצה היא שמיקרוסופט שחררו את קוד המקור של דוטנט 3.5. המשמעות היא שניתן לבצע דיבאג בעזרת קוד המקור של מיקרוסופט ופחות להשתמש בטכניקות עקיפות כגון Reflector (המצוין). להלן הסבר קצר כיצד להפעיל אפשרות זו:

יש לפתוח את האפשרויות ב- Tools->Options->Debugging ולבטל את Just My Code ולשפעל את Enable source server support.

יש לקנפג את המקום ממנו ייטענו ה-debugging symbols ולאן יישמרו (Tools->Options->Debugging->Symbols):

הכתובת היא: http://referencesource.microsoft.com/symbols.

הערה: החלון עשוי להראות שונה בין גרסאות ה-Visual Studio השונות.

כאשר מגיעים לקטע שאותו מעוניינים לדבג (framework, לא קוד שלנו), ב-call stack בוחרים ב-Load Symbols.


  

לאחר מספר שניות צפויה להופיע אזהרת copy rights. אם ההערה חוזרת שוב ושוב, ניתן להוריד תיקון כאן. אם לא מופיעה הודעת אזהרה, כדאי לעצור את הדיבאג ואחר כך לנסות שוב.

כפי שניתן לראות, ה-Symbols נטענו עבור הקבצים המבוקשים וכעת ניתן לבצע Step Into.

עד כאן היו צעדים ראשונים בנושא. למעשה, יש פירוט נרחב למדי ונושאים מתקדמים שעליהם ניתן לקרוא כאן.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 4/11/2009 at 10:14 AM
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

Lambda Expressions

קהל יעד

תוכניתני C# 2 העובדים ב-Generics וכן מכירים מספיק טוב שימוש ב-delegates ומעוניינים לבחון את האפשרות לעבור ל-C# 3, או כאלה שכבר עובדים ב-C# 3 ואוהבים לקרוא בנושא.

כללי

Lambda Expression היא יכולת מסוימת שאם ננסה להסבירה בצורה פשוטה, נגדיר זאת כך: היכולת שלנו לכתוב Anonymous Delegates/Methods בצורה פשוטה יותר.

Anonymous Methods?

נניח שאנו קולטים ב-Console Application מספרים מהמשתמש לתוך מערך של string. לאחר שקלטנו מערך של מחרוזות (strings), אנו נרצה להמיר אותם למספרים. דרך אחת, היא באמצעות foreach:

// input in string format
string[] input = new string[] { "1", "2", "3", "4", "5", "6" };           

// convert to int typed list
List<int> list = new List<int>();
foreach (string s in input)
   list.Add(int.Parse(s));           

// to array...
int[] result = list.ToArray();

 

דרך נוספת, היא להשתמש ב- Array.ConvertAll< >:

// input in string format
string[] input = new string[] { "1", "2", "3", "4", "5", "6" };    

// to array...
int[] result = Array.ConvertAll<string, int>(input, MyConvert);
private static int MyConvert(string s)
{
   return int.Parse(s);
}

Array.ConvertAll< > סה"כ מבצע בדיוק את מה שעשינו קודם לכן: foreach וקריאה ל-MyConvert, היא הפונקציה שלנו, המתרגמת מחרוזת בודדת ל-int. קצת מסורבל. כתבנו פונקציה "שלמה" לטובת מטרה פשוטה: לבצע int.Parse.

באמצעות Anonymous Method אפשר לפשט את הדברים:

// input in string format

string[] input = new string[] { "1", "2", "3", "4", "5", "6" };

   

// to array...

int[] result = Array.ConvertAll<string, int>(input, delegate(string s)

{

   return int.Parse(s);

});

 

למעשה, שמנו את גוף הפונקציה MyConvert ישירות בתור ארגומנט ל-Array.ConvertAll< >. אם נסדר קצת את הקוד ונצמצם שורות מיותרות, נגיע לתוצאה הבאה:

int[] result = Array.ConvertAll<string, int>(input, delegate(string s) { return int.Parse(s); });

 

זה כבר יותר נחמד.  קצר, ברור למדי וכאשר מתרגלים – ברור מאוד. כדאי לשים לב שאין צורך לציין את ערך ההחזר של ה-delegate (במקרה זה: int), כיוון שהקומפיילר כבר מסיק זאת בעצמו.

Lambda Expressions

באמצעות C# 3, ניתן לכתוב Lambda Expressions אשר יפשטו עוד יותר את הדברים. לפני שרצים לקוד, כדאי להקדים ולומר, שהקומפיילר הולך ונהיה יותר ויותר "חכם". למעשה, הוא כ"כ חכם, שהוא פשוט מסיק הרבה דברים לבד. בדוגמא הקודמת, הקומפיילר הסיק בעצמו שערך ההחזר הוא מסוג int (לפי ה-Converter delegate וה-generics שצוינו). הקומפיילר נהיה עוד יותר "חכם" בגרסה 3, ולכן מתאפשרת הפשטה של הקוד. אנו נדגים זאת בשלבים:

  1. השלב הראשון דווקא לא קשור ל-lambda expression. הקומפיילר לבדו מבין שמדובר ב-string input ו-int output, שכן הוא מבחין בסוגי הפרמטרים הנדרשים. לכן, ניתן להוריד את ההגדרות המפורשות:

int[] result = Array.ConvertAll<string, int>(input, delegate(string s) { return int.Parse(s); });

 

  1. ברור לקומפיילר שמדובר ב-delegate. הוא מסיק זאת לפי הארגומנט המופיע בחתימה של Array.ConvertAll< >. לכן ניתן להשמיט את המילה המיותרת:

int[] result = Array.ConvertAll(input, delegate(string s) { return int.Parse(s); });

 

  1. בצורה דומה, אפשר להשמיט את ה-string בארגומנט של ה-delegate, כיוון שהקומפיילר מסיק זאת לפי העובדה ש-input הוא מערך של מחרוזות:

int[] result = Array.ConvertAll(input, (string s) { return int.Parse(s); });

 

  1. העובדה שמדובר ב-delegate שמחזיר ערך, ברורה לקומפיילר מהחתימה של Converter delegate (בדיוק כפי שהוא הסיק שמדובר בערך החזר מסוג int). לכן גם המילה return מיותרת:

int[] result = Array.ConvertAll(input, (s) { return int.Parse(s); });

 

  1. מה שנשאר הוא ארגומנט s וגוף הפונקציה:

int[] result = Array.ConvertAll(input, (s) { int.Parse(s); });

  1. במטרה להשמיט את הסוגריים המיותרים הן מהארגומנט והן מגוף הפונקציה, C# 3 מספק לנו אופרטור חדש. התוצאה, קרויה lambda expression:

int[] result = Array.ConvertAll(input, s => int.Parse(s));

 

 

  1. בהשוואה אל מול ה-anonymous delegate שכתבנו קודם (ללא ה-generics):

int[] result = Array.ConvertAll(input, delegate(string s) { return int.Parse(s); });

אל מול:

int[] result = Array.ConvertAll(input, s => int.Parse(s));

 

כאשר מבינים lambda expression כהפשטה של Anonymous Method, כאשר צד שמאל של האופרטור <= הוא הארגומנט, וצד ימין הוא גוף הפונקציה, הדברים ברורים למדי. מה שנשאר הוא לתרגל ולהפנים.

סיכום

Lambda Expressions היא דרך שבה אנחנו יכולים לפשט ולייעל את השימוש ב-Anonymous Methods. כתוצאה מהשימוש הזה, לומדים מהר מאוד כיצד לקצר את הקוד שלנו ועדיין להשאיר אותו קריא. בשילוב עם LINQ, אפשר להגיע לתוצאות נהדרות בקוד.

מה צריך בשביל זה?

.NET Framework 3.5 ו-Visual Studio 2008 או מאוחר יותר.

קישורים נוספים

Anonymous Methods

Lambda Expressions

C# 3.0 and LINQ - Expression Trees

Currently rated 4.6 by 7 people

  • Currently 4.571429/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 12/22/2008 at 12:36 AM
Tags: , , , , ,
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (3) | Post RSSRSS comment feed

LINQ (To Objects) Revolutions

קהל יעד

תוכניתני C# 2 המעוניינים לבחון את האפשרות לעבור ל-C# 3, או כאלה שכבר עובדים ב-C# 3 או LINQ  ואוהבים לקרוא בנושא.

כללי

לא כולם יסכימו, אבל LINQ הוא בגדר מהפכה, בפרט LINQ To Objects.  אנו בפרופר מקודדים ומשתמשים ב-LINQ כשנה והדבר שינה באופן מהותי את שיטת הקידוד שלנו. המהפכה, לאו דווקא מתבטאת בשינוי ארכיטקטורה, אלא בצורת הקידוד היום-יומי. לפני שנים, אני זוכר שחשבתי על Object Oriented כעל מהפכה של ממש בתחום הקידוד (מהפכה ותיקה מאוד) ותמיד שאלתי את עצמי איפה תהיה המהפכה הבאה. הכוונה איננה לעוד טכנולוגיה המהווה שלב טבעי בהתפתחות הקידוד (למשל WPF, WCF וכד'), ולא מתכוון לשפות קידוד פונקציונאליות וחידושים בתחום זה, אלא משהו שממש ישנה את הקידוד הבסיסי של "שפות דור שלישי". LINQ To Objects מהווה עבורנו בפרופר מהפכה מהסוג הזה.

מה זה?

LINQ הוא ראשי תיבות של Language-Integrated Query. במשפט אחד: היכולת לשלב שאילתות בסגנון SQL בקוד שלנו. מכאן ואילך, "השמיים הם הגבול". LINQ To Objects, הוא בסיס מסוים שכדאי להכיר וללמוד גם בזכות עצמו וגם כתשתית לשימושי LINQ שונים אחרים. על מנת להבין טיפה יותר טוב, אפשר להקביל את הפעולות המבוצעות ב-LINQ לפעולות המבוצעות על טבלאות. בדיוק כפי שאנחנו רגילים לתחקר טבלאות, ולמצוא שורות ונתונים, LINQ מאפשר לנו לעשות זאת בקוד, אל מול כל סוגי הרשימות האפשרויות (IEnumerable<T>). גם רשימות ישנות יותר שאינן מממשות IEnumerable<T> אלא רק IEnumerable ניתנות לתחקור בצורה קלה ע"י הסבה פשוטה. לדוגמא, הקוד שלהלן ידפיס את המספרים במערך שערכם מעל 3:

int[] myArray = new int[] { 1, 2, 3, 4, 5, 6 };

var list = from i in myArray
             where i > 3
             select i;

foreach (int i in list)
{
   Console.Write(i);
}

"תרגיל"

נתונה רשומת לקוחות (GetCustomers() מחזירה Customer[]). יש למצוא את כל הלקוחות שה-Id שלהם קטן מ-100, ואותם למיין לפי מספר עוסק המורשה. ב-.NET 2, היינו כותבים משהו כזה:

List<Customer> list = new List<Customer>();

foreach (Customer c in DataStore.GetCustomers())
{
   if (c.Id < 100)
      list.Add(c);
}

list.Sort(CompareCustomers);

private int CompareCustomers(Customer x, Customer y)
{
    if (x == y)
        return 0;

    if (x == null)
        return -1;

    if (y == null)
        return 1;

    if (x.RegisteredBusinessId < y.RegisteredBusinessId)
        return -1;

    if (x.RegisteredBusinessId > y.RegisteredBusinessId)
        return 1;

    return 0;
}

 

הקוד יעבוד היטב, אבל יש בו שתי בעיות:

  1. הוא ארוך ומסורבל למדיי.
  2. אם מעוניינים למיין לפי מאפיין אחר מעוסק מורשה, למשל שם הלקוח, Id או פריט אחר, אנחנו נצטרך להתחיל לכתוב מחלקה מתאימה שתעזור במיון, או delegate-ים שונים לביצוע הפעולה.

באמצעות LINQ ניתן לפתור את התרגיל באופן הבא:

 

var list = from c in DataStore.GetCustomers()
             where c.Id < 100
             orderby c.RegisteredBusinessId
             select c;


 

זהו. פשוט, ברור, מדויק וקל לתחזוקה ולשינוי.

var

הרבה אנשים התפלאו על השימוש ב-var. לכאורה, חזרה אחורה ל-VB טרום עידן .NET. אלא שה-var של C# 3 מזכיר את זה שיש ב-VB, אבל שונה בפונקציונאליות בשני נושאים עיקריים: 1) בזמן קומפילציה (ולא בזמן ריצה), הקומפיילר חייב להבין מהו ה-type האמיתי של המשתנה; 2) לא ניתן להחליף את ה-type של המשתנה בהמשך הקוד. כלומר, לא ניתן לבצע:

var i = 2;      // i is int type
i = "hello";    // compilation error

 

השימוש ב-var מיועד לחסוך לנו כתיבה מפורשת של ה-type-ים החוזרים משאילתות LINQ. אלה צפויים להשתנות לפי השאילתא ולא תמיד מדובר ב-type פשוט ולכן הצורך בנוחות. למשל, שאילתת Where תחזיר type שונה ממה שתחזיר GroupBy. לא חייבים להשתמש בזה, אבל אחרי מספר ניסיונות סביר להניח ש-var יהפוך להיות האופציה המועדפת.

איך LINQ עובד?

במסגרת C# 3 בוצעו מספר "שדרוגים" משמעותיים לשפה, ניתן לומר שכמעט כולם מיועדים לאפשר את LINQ. בעיקר, הכוונה היא ל- Extension Methods. Extension Methods מאפשרים להרחיב מחלקות קיימות בפונקציונאליות שלא ניתנה להן במקור, ולא באמצעות ירושה. לדוגמא, להלן קוד הבודק את הערך עבור int מסוים:

int i = 5;

if (i => 2 && i <= 10)
{
    // do some code
}

 

אפשר גם לכתוב פונקציה לטובת העניין:

int i = 5;

if (IsBetween(i, 2, 10))
{
    // do some code
}

public static bool IsBetween(int number, int min, int max)
{
    return (number >= min && number <= max);
}

 

ב- C# 3, אפשר להוסיף את this לארגומנט הראשון, ולמעשה להנחות את הקומפיילר להבין שמדובר בהרחבה, במקרה זה ל-int (שים לב למחלקה הסטטית העוטפת – חובה):

int i = 5;

if (i.IsBetween(2, 10))
{
    // do some code
}

static class Extensions
{
    public static bool IsBetween(this int number, int min, int max)
    {
        return (number >= min && number <= max);
    }
}


 

 

רגע, אבל מה הקשר ל-LINQ? אם ניקח את הדוגמא הראשונה ונסתכל ב-Reflector על תוצאת הקומפילציה, נראה את הדבר הבא:


int[] myArray = new int[] { 1, 2, 3, 4, 5, 6 };

var list = from i in myArray
             where i > 3
             select i;

foreach (int i in list)
{
   Console.Write(i);
}


 

כלומר, הקוד המקורי ב-LINQ למעשה תורגם ע"י הקומפיילר ל-Extension Method בשם Where. המתודה Where התווספה במקרה הזה למערך מסוג int.

אם כך, ניתן גם לכתוב את אותה שאילתא בצורה הבאה:

int[] myArray = new int[] { 1, 2, 3, 4, 5, 6 };

var list = myArray.Where(i => i > 3); // lambda expression

foreach (int i in list)
{
    Console.Write(i);
}

 

כפי שניתן לראות, אפשר לפנות ל-Where ישירות (למשל באמצעות lambda expression). אם נעשה Go To Definition על הפונקציה Where, נוכל לראות שני דברים:

  • החתימה של Where היא כדלקמן:

public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

כלומר, כל IEnumerable<T> על כל "יורשיו", "מקבל במתנה" את הפונקציה הזו, ולא רק מערך של int.

  • עשרות Extension Methods נוספות שקיבלנו לכל ה-IEnumerable<T>. בהן נמצא גם את: Sum, Count, GroupBy ואחרות.

Extension Methods כיכולת אשר התווספה ל- C# 3, והניצול שלהן ב-LINQ, הוסיפו עשרות פונקציות אשר מאפשרות לנו לתחקר כל אובייקט מסוג רשימה (IEnumerable<T>) שלא היו זמינות לנו קודם לכן.

דוגמאות נפוצות אחרות

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

סינון (שימוש ב-Where):

IEnumerable<Customer> customers = DataStore.GetCustomers().Where(c => c.Id > 100);

האם קיים”? ("exists"):

bool exists = DataStore.GetCustomers().Any(c => c.Id == 10);

מציאת פריט ספציפי וייחודי:

Customer customer = DataStore.GetCustomers().Single(c => c.Id == 10);

מציאת פריט ספציפי וייחודי או null אם איננו קיים:

Customer customer = DataStore.GetCustomers().SingleOrDefault(c => c.Id == 1000);

if (customer == null)
    this.Response.Write("not found");
else
    this.Response.Write("found");

 

קיבוץ לקבוצות (לפי האות הראשונה בשם הלקוח) ומיון:

var group = from c in DataStore.GetCustomers()
            group c by c.CustomerName[0] into g
            orderby g.Key
            select g;

foreach (var current in group)
{
    Console.WriteLine(current.Key);
    foreach (var customer in current)
    {
        Console.WriteLine(customer.CustomerName);
    }
}

כיוונים נוספים

ייאמר לזכות מיקרוסופט שהם לא נעצרו כאן, אלא למעשה מפתחים ומקדמים את LINQ בכיוונים ואפשרויות שונות. מיקרוסופט שחררו את LINQ עם תמיכה מורחבת לנושאים שונים: LINQ To XML, LINQ To DataSets, LINQ To SQL ולאחרונה גם LINQ To Entities. כל אחד מהם הוא נושא בפני עצמו, אבל כולם מושתתים על הרעיון הבסיסי של היכולת לכתוב שאילתות בקוד, שתוצאת הקומפילציה שלהן היא קריאה ל-Extension Methods. אפילו אם לא מעוניינים לעבוד באחת מהטכנולוגיות האלה, רק השימוש ב-LINQ To Objects צפוי לחסוך הרבה כאב ראש.

סיכום

LINQ מהווה מהפכה של ממש בצורה שבה מקודדים, אם מחליטים לתרגלו ולאמצו. היכולת לתחקר, לסנן, לקבץ ולמיין "מעולם לא הייתה פשוטה יותר". בהדגמה שערכנו באחד מהמקומות להם אנו מסייעים להתקדם ל-LINQ, שם גם לא ידעו להשתמש ב-List<T> ו-List<T>.Sort( ), את קטע הקוד של סינון ומיון הלקוחות אשר הודגם קודם לכן, ללא LINQ, לקח לראשונים לסיים לאחר כחצי שעה. כאשר התבקשו לכתוב אותו ב-LINQ To Objects, הראשונים סיימו תוך כשלוש דקות.

יש עוד ל-LINQ. מה שהוסבר והודגם הוא רק "על קצה המזלג". אנחנו מאוד ממליצים.

מה צריך בשביל זה?

.NET Framework 3.5 ו-Visual Studio 2008 או מאוחר יותר.

קישורים נוספים

The LINQ Project (MS official site)

101 LINQ Samples

C# 3.0 and LINQ - Expression Trees

LINQ to JavaScript  

Currently rated 4.8 by 4 people

  • Currently 4.75/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by: eladv
Posted on: 12/16/2008 at 1:53 AM
Tags: , , , , ,
Actions: E-mail | Kick it! | DZone it! | del.icio.us
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed