390. Implementing Eval using Mono CSharp

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

למי שלא מכיר, Mono זה מימוש Open Sourceי של NET Framework.

בפרויקט הזה הם מימשו את כל הרכיבים של הFramework – את הCompiler, את הCLR ואת כל הBCL.

אחד הפרויקטים שהם כתבו זה פרויקט בשם CSharp Compiler (mcs) – זהו קומפיילר שכתבו בC# שבעזרתו בין השאר מקמפלים את Mono (בערך 4 מיליון שורות קוד)

הקומפיילר הוא קומפיילר המכיל באופן מלא את כל התכולות של C# 1.0-4.0. המימוש שלו משתמש באופן כבד בReflection ובReflection.Emit

החל מאיזשהי גרסה של Mono, הם מאפשרים להשתמש בCompiler כService וכך ניתן להפעילו גם בזמן ריצה.

נוכל להשתמש בו לביצוע Eval. הנה דוגמה פופולרית לשימוש כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Evaluator evaluator =
new Evaluator(
new CompilerSettings()
{
AssemblyReferences = new List<string>()
{
"System.Core"
}
},
new Report(new ConsoleReportPrinter()));
evaluator.Run("using System;");
evaluator.Run("using System.Linq;");
while (true)
{
string line = Console.ReadLine();
object result;
bool resultSet;
evaluator.Evaluate(line, out result, out resultSet);
if (resultSet)
{
Console.WriteLine(result);
}
}

הדבר הזה מקבל קלטים מהמשתמש ומריץ אותם. ברגע שרצה פעולה שמחזירה תוצאה, תודפס התוצאה למסך (זה הבוליאני של resultSet)

לדוגמה אם נכתוב int x = 1 * 2 *3;

ולאחר מכן x;

נקבל 6.

נוכל לנצל את זה למימוש Evaluator ככה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class CodeEvaluator
{
private readonly Evaluator mEvaluator;
public CodeEvaluator()
{
StringBuilder log = new StringBuilder();
mEvaluator =
new Evaluator(
new CompilerSettings()
{
AssemblyReferences = new List<string>()
{
"System.Core"
}
},
new Report(new StreamReportPrinter(new StringWriter(log))));
mEvaluator.Run("using System;");
mEvaluator.Run("using System.Linq;");
}
public T Evaluate<T>(string expression)
{
string expressionToRun =
string.Format("{0} result = {1};",
FormatType(typeof (T)),
expression);
mEvaluator.Run(expressionToRun);
return (T) mEvaluator.Evaluate("result;");
}
public static string FormatType(Type type)
{
if (!type.IsGenericType && !type.IsArray)
{
return type.FullName;
}
else if (type.IsArray)
{
return string.Format("{0}[{1}]", FormatType(type.GetElementType()),
new string(',', type.GetArrayRank() - 1));
}
else if (type.IsGenericType)
{
return string.Format("{0}<{1}>", type.FullName.Substring(0, type.FullName.IndexOf('`')),
string.Join(", ", type.GetGenericArguments().Select(x => FormatType(x))));
}
return null;
}
}

הפעם צרפתי את המתודה FormatType שכתבתי עליה בטיפ מספר 388.

כעת נוכל להשתמש במחלקה כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CodeEvaluator evaluator = new CodeEvaluator();
double three =
evaluator.Evaluate<double>("3");
Func<double, double> sine =
evaluator.Evaluate<Func<double, double>>
("x => Math.Sin(x)");
Func<IEnumerable<string>, IEnumerable<string>> linq =
evaluator.Evaluate<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 =
evaluator.Evaluate<Expression<Func<double, double>>>
("x => Math.Cos(x)");

מגניב ממש!


היתרון על הדברים שראינו קודם:

  • לעומת CodeDom – הכל כאן Managed!
  • לעומת Roslyn – כל הFeatureים של Eval כבר ממומשים

החסרונות:

  • נראה שנוצר Assembly עבור כל שורה. ייתכן וניתן לשלוט על זה בCompilerSettings, אבל לא חקרתי את זה לעומק.
  • כאשר Roslyn יהיה גמור, הוא יהיה יותר בשל מהסיפור הזה

בינתיים זו אופציה יחסית לא רעה לעומת שאר האופציות. כמובן, כאשר יצא Roslyn, יהיה עדיף להשתמש בו.

יום מוערך טוב.

שתף