173. Expression Trees

אחד הFeatureים היותר חזקים בC# 3.0 שלמרבה הצער, גם פחות מוכרים הוא Expression Trees.

מה זה בעצם?

הכרנו בעבר את הSyntax של Lambda Expressions – ראו גם טיפ מספר 45.

בזכות Lambda Expressions, אנחנו מסוגלים לכתוב Anonymous Delegates בצורה פשוטה, למשל:

1
2
Func<int, int> squareFunction = x => x*x;
int nine = squareFunction(3); // 9

או למשל

1
2
Func<int, int, int> sumOfSquares = (x, y) => x*x + y*y;
int fiveSquared = sumOfSquares(3, 4); // 25

ראינו כל מיני דוגמאות שבהן Lambda Expressions הם שימושיים, למשל בכל Linq.

מסתבר שהקומפיילר מאפשר לנו לקמפל Lambda Expression בשתי דרכים.

הדרך הראשונה, שבה נתקלנו כבר היא לAnonymous Delegate. במקרה זה, מה שקורה זה שנוצרת לנו מחלקה/מתודה מאחורי הקלעים שהDelegate מפנה אליה. (ראו גם טיפים 41, 44)

הדרך השנייה, היא לקמפל את הLambda Expression לExpression. מה זה? זו בעצם מחלקה שמייצגת לנו את הביטוי שכתבנו. אנחנו יכולים לנתח אותו כך.

למשל,

1
Expression<Func<int, int, int>> sumOfSquares = (x, y) => x*x + y*y;

הקוד יתקמפל למשהו כזה: (תודה לReflector)

1
2
3
4
5
6
7
8
9
ParameterExpression parameter = Expression.Parameter(typeof(int), "x");
ParameterExpression parameter = Expression.Parameter(typeof(int), "y");
Expression<Func<int, int, int>> sumOfSquares =
Expression.Lambda<Func<int, int, int>>(
Expression.Add(
Expression.Multiply(parameterX, parameterX),
Expression.Multiply(parameterY, parameterY)),
new ParameterExpression[] { parameterX, parameterY });

מה כתוב כאן? ובכן, הקוד הזה בעצם מתאר את הביטוי. אם נסתכל מבפנים, נראה קודם כל את הExpression.Multiplyים שהם בסה"כ מייצגים את הביטויים x*x וy*y בהתאמה.

לאחר מכן, מופיע פה Expression.Add בין הExpression.Multiplyים שמייצג את ה+ בביטוי המקורי.

בסוף הExpression.Lambda מצוין שמדובר בגוף פונקציה עם חתימה מסוימת, ומצוינים הפרמטרים שלה.

זה נחמד.


בשביל מה צריך את הביטויים האלה?

באופן כללי הביטויים האלה הם חלק מהNamespace של System.Linq.Expressions. הם הומצאו בשביל מטרה מאוד פשוטה – הכרנו את Linq עלEnumerableים.

מה אם אנחנו רוצים לעשות שאילתות LINQ על מקור מרוחק, למשל Database?

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

דרך אחת לעשות זאת היא לשלוף את כל הטבלה לאפליקציה ולהריץ עליה את הLINQ:

1
2
3
4
5
6
IEnumerable<Worker> workers = GetAllWorkersFromDb(); // This is heavy!
IEnumerable<Worker> interestingWorkers =
from worker in workers
where worker.Salary > 4000
select worker;

אלא שזה כבד! אנחנו שולפים את כל העובדים בעולם לאפליקציה – מה שגם מכביד על הDB, גם על התקשורת וגם מפוצץ לאפליקציה את הזכרון.

במקום זאת, החליטו לאפשר דרך שנייה:

מהי הדרך? כידוע (טיפים 95-99), שאילתות LINQ מתקמפלות לשרשור של הרכבות של Extension Methods עם Lambda Expressions מתאימים. במקום זאת, ידאגו לקמפל את הLambda Expressions לExpression Tress ותתבצע בצד הלקוח המרה של הExpression Tree לשאילתא שתרוץ מול הDB וכך תחזיר את התוצאות הנכונות במהירות הטובה ביותר.

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

אנחנו עוד נראה שימושים יפים לזה.

המשך יום טוב

שתף