389. Eval Roslyn project implementation

בהמשך לפעמים הקודמות, נראה כעת עוד דרך לממש Eval בC#.

הדרך היא באמצעות ספריה שנכון לרגעים אלה עדיין בפיתוח במיקרוסופט. לספריה קוראים Roslyn והרעיון שלה הוא לאפשר פתרון “Compiler as service”.

הכוונה ב”Compiler as service” הוא הכוונה הבאה:

קומפילציה היא תהליך שבו מתקבלים כקלט קבצי קוד ויוצא כפלט Assembly (המייצג קוד שפת מכונה/שפת Virtual Machine).

במהלך התהליך קורים הרבה דברים, אבל בגדול אפשר לחלק את זה ל4 חלקים עיקריים:

  1. פרסור קוד המקור – תהליך שלוקח את קוד המקור שלנו והופך אותו לאיזשהו אובייקט המייצג אותו (בד”כ עץ)
  2. פענוח הגדרות וSymbolים (הן מהקוד שלנו, והן מקוד חיצוני) – תהליך שרץ על האובייקט הקודם, ומחפש בו הגדרות של Symbolים, כגון מחלקות, מתודות וכו’
  3. Binding – תהליך שיודע לקשר בין מתודות/מחלקות, להפניות למקומות מתאימים בזכרון
  4. Emitting – הפיכת המידע הנ”ל לAssembly

אפשר לחשוב שאולי מספיק לנו להתייחס באמת לקומפיילר כקופסה שחורה שהופכת איכשהו את הקוד שלנו לAssembly, אך היום הIDEים שלנו יודעים לעשות דברים מדהימים כגון:

  • למצוא Referenceים של Symbol מסוים (משתנה/מחלקה/מתודה וכו’)
  • לעשות Refactor
  • לצבוע לנו באדום כאשר שורה לא מתקמפלת
  • ועוד הרבה ניסים ונפלאות!

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

מה שקורה עד עכשיו זה שPluginים כמו ReSharper שרצו לבצע חלק מהמשימות הנ”ל, היו צריכים לממש כמעט קומפיילר C# שלם בעצמם!

Roslyn היא בעצם מימוש Managed של הקומפיילר בC#, המאפשרת לנו גישה לכל המידע של החלקים הנ”ל של בתהליך הקומפילציה.

לכל אחד מהחלקים שכתבתי למעלה, יש API מקביל בRoslyn המאפשר לנו לעשות ניסים ונפלאות.

Roslyn מפותחת במיקרוסופט כבר כמה שנים, ובסוף ב2011 יצא הCTP הראשון שלה, ולפני כמה חודשים, יצא CTP נוסף.


מה הקשר לEval?

אחד הדברים שRoslyn מאפשרת Out of the box זה לעשות Eval. זה נעשה כך:

יוצרים ScriptEngine - זהו אובייקט המייצג מעין Interpreter של C#.

1
2
3
ScriptEngine scriptEngine =
new ScriptEngine(new[] { typeof(Enumerable).Assembly },
new[] { "System", "System.Linq" });

בConstructor אני ציינתי שני פרמטרים: לאיזה Assembly להוסיף Reference ולאיזה Namespaceים לעשות using בקוד.

כעת ניתן לבקש ממנו לעשות Eval לאובייקטים כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
double three =
scriptEngine.Execute<double>("3");
Func<double, double> sine =
scriptEngine.Execute<Func<double, double>>
("x => Math.Sin(x)");
Func<IEnumerable<string>, IEnumerable<string>> linq =
scriptEngine.Execute<Func<IEnumerable<string>, IEnumerable<string>>>
("collection => from x in collection " +
"where x.Length >= 3 " +
"select x ");
IEnumerable<string> test =
linq(new string[] { "a", "ab", "abc", "abcd", "abcde" });
Expression<Func<double, double>> cosineExpression =
scriptEngine.Execute<Expression<Func<double, double>>>
("x => Math.Cos(x)");

למעשה הכל עובד חוץ מהדוגמה האחרונה שזורקת כרגע Exception בו כתוב שRoslyn לא מימשו עדיין Expressionים.

ממש מגניב 😃


יש כמה יתרונות על מה שראינו בפעם שעברה:

  1. הקוד שאנחנו קוראים לו הוא Managed! (לא קוראים יותר לcsc.exe)
  2. לא נוצר Assembly על כל Eval שאנחנו מפעילים – זאת בזכות המימוש של Roslyn שנעשה באמצעות Reflection.Emit:

נוצר Assembly דינאמי יחיד:

Program.exe’ (Managed (v4.0.30319)): Loaded ‘Anonymously Hosted DynamicMethods Assembly’

החסרון הגדול הוא שכרגע Roslyn עדיין רק CTP, וכפי שראינו עדיין לא מומשו כל התכולות.

נקווה שבהמשך תמומשנה כלל התכולות, ונוכל להשתמש בספריה זו לניסים ונפלאות. אני מניח שגם החבר’ה בJetBrains ישנו את הקוד שלהם שישתמש בRoslyn וכו’.


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

יש למשל עם הCTP איזשהו Demo מגניב שבו ממירים קוד מC# לVB ולהפך.

סופ"ש מוערך לטובה.

שתף