Write ASP. NET is a common method to use Eval, as if any of the ASP. NET will describe how to bind a able to a control and use Eval to set values. However, in the current Domain Driven Design era, we often operate on Domain model objects. We can use any object that implements IEnumerable as the data source to bind the control, and obtain the field value through Eval In the bound control. As follows:
Protected void Page_Load (object sender, EventArgs e)
{
List <Comment> comments = GetComments ();
This. rptComments. DataSource = comments;
This. rptComments. DataBind ();
}
<Asp: Repeater runat = "server" ID = "rptComments">
<ItemTemplate>
Title: <% # Eval ("Title") %>
Conent: <% # Eval ("Content") %>
</ItemTemplate>
<SeparatorTemplate>
<Hr/>
</SeparatorTemplate>
</Asp: Repeater>
Here, the Eval object obtains the value of the Title and Content attributes through reflection. As a result, someone often said, "how poor is the performance of reflection? I don't need it !". Here, I still have a reserved attitude towards this kind of practice of pursuing detailed performance. Of course, in the above example, we can do the following:
<Asp: Repeater runat = "server" ID = "rptComments">
<ItemTemplate>
Title: <% # (Container. DataItem as Comment). Title %>
Conent: <% # (Container. DataItem as Comment). Content %>
</ItemTemplate>
<SeparatorTemplate>
<Hr/>
</SeparatorTemplate>
</Asp: Repeater>
We use Container. DataItem to obtain the data object in the current traversal process, convert it to Comment, and then read its Title and Content attributes. Although the expression is somewhat long, it seems to be a good solution. Performance ...... It must have been improved.
However, in the actual development process, we may not be able to use a specific type of data as a data source so easily. We often need to combine two types of objects for joint display. For example, when we display the comment list, we usually need to display the personal information of the user. Since anonymous objects are already supported in C #3.0, we can do this:
Protected void Page_Load (object sender, EventArgs e)
{
List <Comment> comments = GetComments ();
List <User> users = GetUsers ();
This. rptComments. DataSource = from c in comments
From u in users
Where c. UserID = u. UserID
Order by c. CreateTime
Select new
{
Title = c. Title,
Content = c. Content,
NickName = u. NickName
};
This. rptComments. DataBind ();
}
Through the LINQ cascade Comment and User dataset, we can easily construct an anonymous object set as a data source (does it show the beauty of LINQ ?). The above anonymous object will contain several public attributes such as Title, Content, and NickName. Therefore, Eval is still used to retrieve data on the page, not to mention.
But I am almost certain that someone is calling it again: "It's useless to use LINQ! We don't need LINQ! Poor Eval performance! We don't need Eval !". Well, I will try again with the "most practical" technology:
Private Dictionary <int, User> m_users;
Protected User GetUser (int userId)
{
Return this. m_users [userId];
}
Protected void Page_Load (object sender, EventArgs e)
{
List <Comment> comments = GetComments ();
List <User> users = GetUsers ();
This. m_users = new Dictionary <int, User> ();
Foreach (User u in users)
{
This. m_users [u. UserID] = u;
}
This. rptComments. DataSource = comments;
This. rptComments. DataBind ();
}
<Asp: Repeater runat = "server" ID = "rptComments">
<ItemTemplate>
Title: <% # (Container. DataItem as Comment). Title %>
Conent: <% # (Container. DataItem as Comment). Content %>
NickName: <% # this. GetUser (Container. DataItem as Comment). UserID). NickName %>
</ItemTemplate>
<SeparatorTemplate>
<Hr/>
</SeparatorTemplate>
</Asp: Repeater>
Poor reflection performance? That makes sense ......
Slow reflection speed? I agree that it is relatively slow.
How many CPUs does reflection occupy? I agree that he is a little more.
So shouldn't Eval be used? I don't agree-how can I bring my child down with dirty water? Can we solve the performance problem of the reflected access attribute?
The reason for poor performance is that Eval uses reflection. The traditional method to solve such problems is to use Emit. However. NET 3.5 now has a Lambda Expression. After we construct a Lambda Expression dynamically, we can use its Compile method to obtain a delegate instance. As for the various details of Emit implementation. NET Framework implementation-it's not that difficult.
Public class DynamicPropertyAccessor
{
Private Func <object, object> m_getter;
Public DynamicPropertyAccessor (Type type, string propertyName)
: This (type. GetProperty (propertyName ))
{}
Public DynamicPropertyAccessor (PropertyInfo propertyInfo)
{
// Target: (object) ({TargetType}) instance). {Property })
// Preparing parameter, object type
ParameterExpression instance = Expression. Parameter (
Typeof (object), "instance ");
// ({TargetType}) instance
Expression instanceCast = Expression. Convert (
Instance, propertyInfo. ReflectedType );
// ({TargetType}) instance). {Property}
Expression propertyAccess = Expression. Property (
InstanceCast, propertyInfo );
// (Object) ({TargetType}) instance). {Property })
UnaryExpression castPropertyValue = Expression. Convert (
PropertyAccess, typeof (object ));
// Lambda expression
Expression <Func <object, object> lambda =
Expression. Lambda <Func <object, object> (
CastPropertyValue, instance );
This. m_getter = lambda. Compile ();
}
Public object GetValue (object o)
{
Return this. m_getter (o );
}
}
In DynamicPropertyAccessor, we construct an o => object (Class) o) for a specific attribute ). property Lambda expression, which can be Compile as a Func <object, object> delegate. Finally, we can pass in a Class object for the GetValue method to obtain the value of the specified attribute.
Is this method familiar? That's right. In "direct call of methods, reflection call and ...... Similar practices are also used in the Lambda expression call article.
Test the performance?
Let's compare the directly obtained value of the attribute, and obtain the value from reflection ...... Lambda expressions can be used to obtain values in the same way.
Var t = new Temp {Value = null };
PropertyInfo propertyInfo = t. GetType (). GetProperty ("Value ");
Stopwatch wattings = new Stopwatch ();
Watch1.Start ();
For (var I = 0; I <1000000; I ++)
{
Var value = propertyInfo. GetValue (t, null );
}
Watch1.Stop ();
Console. WriteLine ("Reflection:" + watch1.Elapsed );
DynamicPropertyAccessor property = new DynamicPropertyAccessor (t. GetType (), "Value ");
Stopwatch watch2 = new Stopwatch ();
Watch2.Start ();
For (var I = 0; I <1000000; I ++)
{
Var value = property. GetValue (t );
}
Watch2.Stop ();
Console. WriteLine ("Lambda:" + watch2.Elapsed );
Stopwatch watch3 = new Stopwatch ();
Watch3.Start ();
For (var I = 0; I <1000000; I ++)
{
Var value = t. Value;
}
Watch3.Stop ();
Console. WriteLine ("Direct:" + watch3.Elapsed );
The result is as follows:
Reflection: 00:00:04. 2695397
Lambda: 00:00:00. 0445277
Direct: 00:00:00. 0175414
After using DynamicPropertyAccessor, although the performance is slightly slower than the direct call, there are already hundreds of times the gap. More importantly, DynamicPropertyAccessor also supports values of attributes of anonymous objects. This means that our Eval method can rely entirely on DynamicPropertyAccessor.
Only one step away from quick Eval
"One step away "? That's the cache. It is time-consuming to call the GetValue method of a DynamicPropertyAccessor. However, it is time-consuming to construct a DynamicPropertyAccessor object. Therefore, we need to cache the DynamicPropertyAccessor object as follows:
Public class DynamicPropertyAccessorCache
{
Private object m_mutex = new object ();
Private Dictionary <Type, Dictionary <string, DynamicPropertyAccessor> m_cache =
New Dictionary <Type, Dictionary <string, DynamicPropertyAccessor> ();
Public DynamicPropertyAccessor GetAccessor (Type type, string propertyName)
{
DynamicPropertyAccessor accessor;
Dictionary <string, DynamicPropertyAccessor> typeCache;
If (this. m_cache.TryGetValue (type, out typeCache ))
{
If (typeCache. TryGetValue (propertyName, out accessor ))
{
Return accessor;
}
}
Lock (m_mutex)
{
If (! This. m_cache.ContainsKey (type ))
{
This. m_cache [type] = new Dictionary <string, DynamicPropertyAccessor> ();
}
Accessor = new DynamicPropertyAccessor (type, propertyName );
This. m_cache [type] [propertyName] = accessor;
Return accessor;
}
}
}
After testing, it is found that the calling performance is reduced because the DynamicPropertyAccessor object is obtained from the cache every time, but it is still several hundred times faster than the reflection call.
FastEval -- Will anyone reject it?
FastEval method. In the previous. NET version, we can define it in the common base class of each page. However, since we are using. NET 3.5, we can use Extension Method to implement it without any intrusion:
Public static class FastEvalExtensions
{
Private static DynamicPropertyAccessorCache s_cache =
New DynamicPropertyAccessorCache ();
Public static object FastEval (this Control control, object o, string propertyName)
{
Return s_cache.GetAccessor (o. GetType (), propertyName). GetValue (o );
}
Public static object FastEval (this TemplateControl control, string propertyName)
{
Return control. FastEval (control. Page. GetDataItem (), propertyName );
}
}
The extension on Control ensures that a value can be obtained directly through an object and attribute name on each page. The extension on TemplateControl enables all types of controls or pages (Page, MasterPage, UserControl) to be bound to directly obtain the attribute values of the Data Object currently bound through the attribute name.
Now, what reason do you want to reject FastEval?
Others
In fact, our entire article has underestimated the role of the Eval method. The string parameter of the Eval method is "expression", that is, the expression. In fact, we can even use "." To split the string to obtain an object's in-depth attributes, such as <% # Eval ("Content. Length") %>. So can we do this with FastEval? Of course you can -- but you need to implement it yourself. :)
Finally, let's leave another question for your consideration: currently, DynamicPropertyAccessor only provides one GetValue method. Can you add a SetValue Method for it to set this attribute? I hope you will reply enthusiastically. I will provide my practices later.
Questions and answers
One thing you should know is that an attribute is actually composed of a pair of get/set methods (of course, one of them may be missing ). After obtaining the PropertyInfo object of an attribute, you can use its GetSetMethod method to obtain its setting method. The following work can not be handed over to "direct call of methods, reflection call and ...... Is DynamicMethodExecutor in Lambda expression call? Therefore, it is easy to add a SetValue Method for DynamicPropertyAccessor:
Public class DynamicPropertyAccessor
{
...
Private DynamicMethodExecutor m_dynamicSetter;
...
Public DynamicPropertyAccessor (PropertyInfo propertyInfo)
{
...
MethodInfo setMethod = propertyInfo. GetSetMethod ();
If (setMethod! = Null)
{
This. m_dynamicSetter = new DynamicMethodExecutor (setMethod );
}
}
...
Public void SetValue (object o, object value)
{
If (this. m_dynamicSetter = null)
{
Throw new NotSupportedException ("Cannot set the property .");
}
This. m_dynamicSetter.Execute (o, new object [] {value });
}
}