1 //------------------------------------------------------------------------------
2 // <copyright file="FastPropertyAccessor.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Reflection;
9 using System.Reflection.Emit;
10 using System.Threading;
11 using System.Collections;
12 using System.Security;
13 using System.Security.Permissions;
15 namespace System.Web.Util {
17 * Property Accessor Generator class
19 * The purpose of this class is to generate some IL code on the fly that can efficiently
20 * access properties (and fields) of objects. This is an alternative to using
21 * very slow reflection.
24 internal class FastPropertyAccessor {
26 private static object s_lockObject = new object();
27 private static FastPropertyAccessor s_accessorGenerator;
28 private static Hashtable s_accessorCache;
29 private static MethodInfo _getPropertyMethod;
30 private static MethodInfo _setPropertyMethod;
31 private static Type[] _getPropertyParameterList = new Type[] { typeof(object) };
32 private static Type[] _setPropertyParameterList = new Type[] { typeof(object), typeof(object) };
33 private static Type[] _interfacesToImplement;
35 private static int _uniqueId; // Used to generate unique type ID's.
37 // Property getter/setter must be public for codegen to access it.
38 // Static properties are ignored, since this class only works on instances of objects.
39 // Need to use DeclaredOnly to avoid AmbiguousMatchException if a property with
40 // a different return type is hidden.
41 private const BindingFlags _declaredFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
43 private ModuleBuilder _dynamicModule = null;
45 static FastPropertyAccessor() {
47 // Get the SetProperty method, and make sure it has
48 // the correct signature.
50 _getPropertyMethod = typeof(IWebPropertyAccessor).GetMethod("GetProperty");
51 _setPropertyMethod = typeof(IWebPropertyAccessor).GetMethod("SetProperty");
53 // This will be needed later, when building the dynamic class.
54 _interfacesToImplement = new Type[1];
55 _interfacesToImplement[0] = typeof(IWebPropertyAccessor);
58 private static String GetUniqueCompilationName() {
59 return Guid.NewGuid().ToString().Replace('-', '_');
62 Type GetPropertyAccessorTypeWithAssert(Type type, string propertyName,
63 PropertyInfo propInfo, FieldInfo fieldInfo) {
64 // Create the dynamic assembly if needed.
67 MethodInfo getterMethodInfo = null;
68 MethodInfo setterMethodInfo = null;
71 if (propInfo != null) {
73 getterMethodInfo = propInfo.GetGetMethod();
74 setterMethodInfo = propInfo.GetSetMethod();
76 propertyType = propInfo.PropertyType;
79 // If not, it must be a field
80 propertyType = fieldInfo.FieldType;
83 if (_dynamicModule == null) {
85 if (_dynamicModule == null) {
87 // Use a unique name for each assembly.
88 String name = GetUniqueCompilationName();
90 AssemblyName assemblyName = new AssemblyName();
91 assemblyName.Name = "A_" + name;
93 // Create a new assembly.
94 AssemblyBuilder newAssembly =
95 Thread.GetDomain().DefineDynamicAssembly(assemblyName,
96 AssemblyBuilderAccess.Run,
97 null, //directory to persist assembly
98 true, //isSynchronized
99 null //assembly attributes
102 // Create a single module in the assembly.
103 _dynamicModule = newAssembly.DefineDynamicModule("M_" + name);
108 // Give the factory a unique name.
110 String typeName = System.Web.UI.Util.MakeValidTypeNameFromString(type.Name) +
111 "_" + propertyName + "_" + (_uniqueId++);
113 TypeBuilder accessorTypeBuilder = _dynamicModule.DefineType("T_" + typeName,
114 TypeAttributes.Public,
116 _interfacesToImplement);
119 // Define the GetProperty method. It must be virtual to be an interface implementation.
122 MethodBuilder method = accessorTypeBuilder.DefineMethod("GetProperty",
123 MethodAttributes.Public |
124 MethodAttributes.Virtual,
126 _getPropertyParameterList);
128 // Generate IL. The generated IL corresponds to:
129 // "return ((TargetType) target).Blah;"
131 ILGenerator il = method.GetILGenerator();
132 if (getterMethodInfo != null) {
133 il.Emit(OpCodes.Ldarg_1);
134 il.Emit(OpCodes.Castclass, type);
136 // Generate the getter call based on whether it's a Property or Field
137 if (propInfo != null)
138 il.EmitCall(OpCodes.Callvirt, getterMethodInfo, null);
140 il.Emit(OpCodes.Ldfld, fieldInfo);
142 il.Emit(OpCodes.Box, propertyType);
143 il.Emit(OpCodes.Ret);
145 // Specify that this method implements GetProperty from the inherited interface.
146 accessorTypeBuilder.DefineMethodOverride(method, _getPropertyMethod);
149 // Generate IL. The generated IL corresponds to "throw new InvalidOperationException"
150 ConstructorInfo cons = typeof(InvalidOperationException).GetConstructor(Type.EmptyTypes);
151 il.Emit(OpCodes.Newobj, cons);
152 il.Emit(OpCodes.Throw);
157 // Define the SetProperty method. It must be virtual to be an interface implementation.
160 method = accessorTypeBuilder.DefineMethod("SetProperty",
161 MethodAttributes.Public |
162 MethodAttributes.Virtual,
164 _setPropertyParameterList);
166 il = method.GetILGenerator();
168 // Don't generate any code in the setter if it's a readonly property.
169 // We still need to have an implementation of SetProperty, but it does nothing.
170 if (fieldInfo != null || setterMethodInfo != null) {
172 // Generate IL. The generated IL corresponds to:
173 // "((TargetType) target).Blah = (PropType) val;"
175 il.Emit(OpCodes.Ldarg_1);
176 il.Emit(OpCodes.Castclass, type);
177 il.Emit(OpCodes.Ldarg_2);
179 if (propertyType.IsPrimitive) {
180 // Primitive type: deal with boxing
181 il.Emit(OpCodes.Unbox, propertyType);
183 // Emit the proper instruction for the type
184 if (propertyType == typeof(sbyte)) {
185 il.Emit(OpCodes.Ldind_I1);
187 else if (propertyType == typeof(byte)) {
188 il.Emit(OpCodes.Ldind_U1);
190 else if (propertyType == typeof(short)) {
191 il.Emit(OpCodes.Ldind_I2);
193 else if (propertyType == typeof(ushort)) {
194 il.Emit(OpCodes.Ldind_U2);
196 else if (propertyType == typeof(uint)) {
197 il.Emit(OpCodes.Ldind_U4);
199 else if (propertyType == typeof(int)) {
200 il.Emit(OpCodes.Ldind_I4);
202 else if (propertyType == typeof(long)) {
203 il.Emit(OpCodes.Ldind_I8);
205 else if (propertyType == typeof(ulong)) {
206 il.Emit(OpCodes.Ldind_I8); // Somehow, there is no Ldind_u8
208 else if (propertyType == typeof(bool)) {
209 il.Emit(OpCodes.Ldind_I1);
211 else if (propertyType == typeof(char)) {
212 il.Emit(OpCodes.Ldind_U2);
214 else if (propertyType == typeof(decimal)) {
215 il.Emit(OpCodes.Ldobj, propertyType);
217 else if (propertyType == typeof(float)) {
218 il.Emit(OpCodes.Ldind_R4);
220 else if (propertyType == typeof(double)) {
221 il.Emit(OpCodes.Ldind_R8);
224 il.Emit(OpCodes.Ldobj, propertyType);
227 else if (propertyType.IsValueType) {
228 // Value type: deal with boxing
229 il.Emit(OpCodes.Unbox, propertyType);
230 il.Emit(OpCodes.Ldobj, propertyType);
233 // No boxing involved: just generate a standard cast
234 il.Emit(OpCodes.Castclass, propertyType);
237 // Generate the assignment based on whether it's a Property or Field
238 if (propInfo != null)
239 il.EmitCall(OpCodes.Callvirt, setterMethodInfo, null);
241 il.Emit(OpCodes.Stfld, fieldInfo);
244 il.Emit(OpCodes.Ret);
246 // Specify that this method implements SetProperty from the inherited interface.
247 accessorTypeBuilder.DefineMethodOverride(method, _setPropertyMethod);
250 accessorType = accessorTypeBuilder.CreateType();
255 private static void GetPropertyInfo(Type type, string propertyName, out PropertyInfo propInfo, out FieldInfo fieldInfo, out Type declaringType) {
257 // First, try to find a property with that name. Type.GetProperty() without BindingFlags.Declared
258 // will throw AmbiguousMatchException if there is a hidden property with the same name and a
259 // different type (VSWhidbey 237437). This method finds the property with the specified name
260 // on the most specific type.
261 propInfo = GetPropertyMostSpecific(type, propertyName);
264 if (propInfo != null) {
265 // Get the most base Type where the property is first declared
266 MethodInfo baseCheckMethodInfo = propInfo.GetGetMethod();
267 if (baseCheckMethodInfo == null) {
268 baseCheckMethodInfo = propInfo.GetSetMethod();
270 declaringType = baseCheckMethodInfo.GetBaseDefinition().DeclaringType;
273 // Ignore the declaring type if it's generic
274 if (declaringType.IsGenericType)
275 declaringType = type;
277 // If they're different, get a new PropertyInfo
278 if (declaringType != type) {
279 // We want the propertyInfo for the property specifically declared on the declaringType.
280 // So pass in the correct BindingFlags to avoid an AmbiguousMatchException, which would
281 // be thrown if the declaringType hides a property with the same name and a different type.
283 propInfo = declaringType.GetProperty(propertyName, _declaredFlags);
287 // We couldn't find a property, so try a field
288 // Type.GetField can not throw AmbiguousMatchException like Type.GetProperty above.
289 fieldInfo = type.GetField(propertyName);
291 // If we couldn't find a field either, give up
292 if (fieldInfo == null)
293 throw new ArgumentException();
295 declaringType = fieldInfo.DeclaringType;
299 private static IWebPropertyAccessor GetPropertyAccessor(Type type, string propertyName) {
301 if (s_accessorGenerator == null || s_accessorCache == null) {
302 lock (s_lockObject) {
303 if (s_accessorGenerator == null || s_accessorCache == null) {
304 s_accessorGenerator = new FastPropertyAccessor();
305 s_accessorCache = new Hashtable();
310 // First, check if we have it cached
312 // Get a hash key based on the Type and the property name
313 int cacheKey = HashCodeCombiner.CombineHashCodes(
314 type.GetHashCode(), propertyName.GetHashCode());
316 IWebPropertyAccessor accessor = (IWebPropertyAccessor)s_accessorCache[cacheKey];
318 // It was cached, so just return it
319 if (accessor != null)
322 FieldInfo fieldInfo = null;
323 PropertyInfo propInfo = null;
326 GetPropertyInfo(type, propertyName, out propInfo, out fieldInfo, out declaringType);
328 // If the Type where the property/field is declared is not the same as the current
329 // Type, check if the declaring Type already has a cached accessor. This limits
330 // the number of different accessors we need to create. e.g. Every control has
331 // an ID property, but we'll end up only create one accessor for all of them.
332 int declaringTypeCacheKey = 0;
333 if (declaringType != type) {
334 // Get a hash key based on the declaring Type and the property name
335 declaringTypeCacheKey = HashCodeCombiner.CombineHashCodes(
336 declaringType.GetHashCode(), propertyName.GetHashCode());
338 accessor = (IWebPropertyAccessor) s_accessorCache[declaringTypeCacheKey];
340 // We have a cached accessor for the declaring type, so use it
341 if (accessor != null) {
343 // Cache the declaring type's accessor as ourselves
344 lock (s_accessorCache.SyncRoot) {
345 s_accessorCache[cacheKey] = accessor;
352 if (accessor == null) {
353 Type propertyAccessorType;
355 lock (s_accessorGenerator) {
356 propertyAccessorType = s_accessorGenerator.GetPropertyAccessorTypeWithAssert(
357 declaringType, propertyName, propInfo, fieldInfo);
360 // Create the type. This is the only place where Activator.CreateInstance is used,
361 // reducing the calls to it from 1 per instance to 1 per type.
362 accessor = (IWebPropertyAccessor) HttpRuntime.CreateNonPublicInstance(propertyAccessorType);
365 // Cache the accessor
366 lock (s_accessorCache.SyncRoot) {
367 s_accessorCache[cacheKey] = accessor;
369 if (declaringTypeCacheKey != 0)
370 s_accessorCache[declaringTypeCacheKey] = accessor;
376 internal static object GetProperty(object target, string propName, bool inDesigner) {
378 IWebPropertyAccessor accessor = GetPropertyAccessor(target.GetType(), propName);
379 return accessor.GetProperty(target);
382 // Dev10 bug 491386 - avoid CLR code path that causes an exception when designer uses two
383 // assemblies of the same name at different locations
384 FieldInfo fieldInfo = null;
385 PropertyInfo propInfo = null;
387 GetPropertyInfo(target.GetType(), propName, out propInfo, out fieldInfo, out declaringType);
388 if (propInfo != null) {
389 return propInfo.GetValue(target, null);
391 else if (fieldInfo != null) {
392 return fieldInfo.GetValue(target);
394 throw new ArgumentException();
398 // Finds the property with the specified name on the most specific type.
399 private static PropertyInfo GetPropertyMostSpecific(Type type, string name) {
400 PropertyInfo propInfo;
401 Type currentType = type;
403 while (currentType != null) {
404 propInfo = currentType.GetProperty(name, _declaredFlags);
405 if (propInfo != null) {
409 currentType = currentType.BaseType;
416 internal static void SetProperty(object target, string propName, object val, bool inDesigner) {
418 IWebPropertyAccessor accessor = GetPropertyAccessor(target.GetType(), propName);
419 accessor.SetProperty(target, val);
422 // Dev10 bug 491386 - avoid CLR code path that causes an exception when designer uses two
423 // assemblies of the same name at different locations
424 FieldInfo fieldInfo = null;
425 PropertyInfo propInfo = null;
426 Type declaringType = null;
427 GetPropertyInfo(target.GetType(), propName, out propInfo, out fieldInfo, out declaringType);
428 if (propInfo != null) {
429 propInfo.SetValue(target, val, null);
431 else if (fieldInfo != null) {
432 fieldInfo.SetValue(target, val);
435 throw new ArgumentException();
441 public interface IWebPropertyAccessor {
442 object GetProperty(object target);
443 void SetProperty(object target, object value);