181. Fast property access

נניח שיש לנו Property שאנחנו ניגשים אליו הרבה פעמים בReflection.

הפעולה הזו די יקרה ויכולה להאט משמעותית את התכנית שלנו.

אחת הדרכים שנראה שאפשר לעשות כדי לפתור בעיה זו היא להשתמש בDelegate.CreateDelegate, אלא שיש פה בעיה:

אם הפרמטרים שלנו הם לא מהטיפוס המתאים, לא נוכל ליצור Delegate כזה.

לדוגמה, נניח שיש לנו את הProperty הבא:

1
2
3
4
5
6
7
8
public class Triangle : Shape
{
public Circle BoundedCircle
{
get;
set;
}
}

אז הקריאה הבאה תעבוד:

1
2
3
4
5
6
7
8
9
10
11
12
13
PropertyInfo boundedCircleProperty =
typeof(Triangle).GetProperty("BoundedCircle");
// We get this somehow through reflection
Func<Triangle, Circle> getBoundedCircle =
(Func<Triangle, Circle>)
Delegate.CreateDelegate(typeof(Func<Triangle, Circle>),
boundedCircleProperty.GetGetMethod());
Triangle myTriangle = new Triangle();
object boundedCircle =
getBoundedCircle(myTriangle);

אבל מה הבעיה? הבעיה היא שאנחנו לא מכירים את הטיפוס Triangle. זהו טיפוס שהתקבל בReflection. אם היינו מכירים אותו, היינו לעשות הסבה ולגשת לProperty ששמו BoundedCircle בעצמנו במהירות סבירה.

אם ננסה לעשות משהו כזה, נחטוף כמובן Exception:

1
2
3
4
Func<Shape, Circle> getBoundedCircle =
(Func<Shape, Circle>)
Delegate.CreateDelegate(typeof(Func<Shape, Circle>),
boundedCircleProperty.GetGetMethod());

כי הפונקציה מצפה לקבל Triangle ולא Shape.

מה שנוכל לעשות זה ליצור מתודה דינאמית בזמן ריצה שתעשה את זה:

הגישה הזאת תראה ככה:

1
2
Expression<Func<Shape, Circle>> boundedCircleExpression =
x => ((Triangle) x).BoundedCircle;

מה שמתמקפל למשהו כזה:

1
2
3
4
5
6
7
8
9
10
11
ParameterExpression parameter =
Expression.Parameter(typeof(Shape), "x");
PropertyInfo boundedCircleProperty =
typeof(Triangle).GetProperty("BoundedCircle");
Expression<Func<Shape, Circle>> boundedCircleExpression =
Expression.Lambda<Func<Shape, Circle>>
(Expression.Property
(Expression.Convert(parameterX, typeof(Triangle)), boundedCircleProperty),
new ParameterExpression[] { parameterX });

נוכל ליצור פונקציה שיוצרת ככה Delegate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static Func<TSource, TResult> GetProperty<TSource, TResult>(PropertyInfo property)
{
ParameterExpression parameterX = Expression.Parameter(typeof(TSource), "x");
Expression<Func<TSource, TResult>> propertyAccessExpression =
Expression.Lambda<Func<TSource, TResult>>
(Expression.Property
(Expression.Convert(parameterX, property.ReflectedType), property),
new ParameterExpression[] { parameterX });
Func<TSource, TResult> propertyAccess = propertyAccessExpression.Compile();
return propertyAccess;
}

ולהשתמש בו:

1
2
3
4
5
6
7
8
9
PropertyInfo boundedCircleProperty =
typeof(Triangle).GetProperty("BoundedCircle");
Shape myTriangle = new Triangle();
Func<Shape, Shape> boundedCircleAccess =
GetProperty<Shape, Shape>(boundedCircleProperty);
Shape boundedCircle = boundedCircleAccess(myTriangle);

במהירות סבירה.

נוכל לעשות גם הסבה נוספת בפונקציה במקרה הצורך (למשל במקרה של boxing לobject) בצורה הזאת:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static Func<TSource, TResult> GetProperty<TSource, TResult>(PropertyInfo property)
{
ParameterExpression parameterX = Expression.Parameter(typeof(TSource), "x");
Expression<Func<TSource, TResult>> propertyAccessExpression =
Expression.Lambda<Func<TSource, TResult>>
(Expression.Convert
(Expression.Property
(Expression.Convert(parameterX, property.ReflectedType),
property),
typeof (TResult)),
new ParameterExpression[] {parameterX});
Func<TSource, TResult> propertyAccess = propertyAccessExpression.Compile();
return propertyAccess;
}

סופ"ש דינאמי מצוין

שתף