Asp.net MVC DefaultModelBinder分析
今天看见一同事写了一段代码很是奇怪,大致结构如下:
public ActionResult Demo(string name, dynamic obj)
{
if (obj != null)
{
return Content("obj is not null");
}
return Content("obj is null");
}
在调用action时obj参数不传,如@{Html.RenderAction("Demo",new
{name="majiang"});}。
可是在实际运行中obj永远不等于null。这是为什么了? 其实一切答案都在DefaultModelBinder
首先看看BindModel方法
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (bindingContext == null) { throw new ArgumentNullException("bindingContext"); } bool performedFallback = false; if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back // to the empty prefix. if (bindingContext.FallbackToEmptyPrefix) { bindingContext = new ModelBindingContext() { ModelMetadata = bindingContext.ModelMetadata, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; performedFallback = true; } else { return null; } } // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string)) // or by seeing if a value in the request exactly matches the name of the model we're binding. // Complex type = everything else. if (!performedFallback) { bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext); ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation); if (vpResult != null) { return BindSimpleModel(controllerContext, bindingContext, vpResult); } } if (!bindingContext.ModelMetadata.IsComplexType) { return null; } return BindComplexModel(controllerContext, bindingContext); }
很显然object是复杂类型 应该关注方法BindComplexModel
internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object model = bindingContext.Model; Type modelType = bindingContext.ModelType; // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created if (model == null && modelType.IsArray) { Type elementType = modelType.GetElementType(); Type listType = typeof(List<>).MakeGenericType(elementType); object collection = CreateModel(controllerContext, bindingContext, listType); ModelBindingContext arrayBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType); if (list == null) { return null; } Array array = Array.CreateInstance(elementType, list.Count); list.CopyTo(array, 0); return array; } if (model == null) { model = CreateModel(controllerContext, bindingContext, modelType); } // special-case IDictionary<,> and ICollection<> Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>)); if (dictionaryType != null) { Type[] genericArguments = dictionaryType.GetGenericArguments(); Type keyType = genericArguments[0]; Type valueType = genericArguments[1]; ModelBindingContext dictionaryBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType); return dictionary; } Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (enumerableType != null) { Type elementType = enumerableType.GetGenericArguments()[0]; Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(model)) { ModelBindingContext collectionBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType); return collection; } } // otherwise, just update the properties on the complex type BindComplexElementalModel(controllerContext, bindingContext, model); return model; }
在它里面有关键的一句
if (model == null) {
model = CreateModel(controllerContext, bindingContext, modelType);
}
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { Type typeToCreate = modelType; // we can understand some collection interfaces, e.g. IList<>, IDictionary<,> if (modelType.IsGenericType) { Type genericTypeDefinition = modelType.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(IDictionary<,>)) { typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments()); } else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) { typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments()); } } // fallback to the type's default constructor return Activator.CreateInstance(typeToCreate); }
很明显 在绑定的时候发现复杂类型实例为null,这时DefaultModelBinder会创建一个默认实例。这就是为什么action参数为object的时候,再绑定后都不为null。然而string类型的数据他们走的是BindSimpleModel。
为了便于大家调试代码,证实以上结论,大家可以创建一个类继承与DefaultModelBinder,如CustBinder
然后再Application_Start() 中添加一句 ModelBinders.Binders.DefaultBinder = new CustBinder();
CustBinder代码:
public class CustBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //return base.BindModel(controllerContext, bindingContext); if (bindingContext == null) { throw new ArgumentNullException("bindingContext"); } bool performedFallback = false; if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back // to the empty prefix. if (bindingContext.FallbackToEmptyPrefix) { bindingContext = new ModelBindingContext() { ModelMetadata = bindingContext.ModelMetadata, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; performedFallback = true; } else { return null; } } // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string)) // or by seeing if a value in the request exactly matches the name of the model we're binding. // Complex type = everything else. if (!performedFallback) { bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext); //ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation); ValueProviderResult vpResult = (bindingContext.ValueProvider as IUnvalidatedValueProvider).GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation); if (vpResult != null) { return BindSimpleModel(controllerContext, bindingContext, vpResult); } } if (!bindingContext.ModelMetadata.IsComplexType) { return null; } return BindComplexModel(controllerContext, bindingContext); } protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { Type typeToCreate = modelType; // we can understand some collection interfaces, e.g. IList<>, IDictionary<,> if (modelType.IsGenericType) { Type genericTypeDefinition = modelType.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(IDictionary<,>)) { typeToCreate = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments()); } else if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(ICollection<>) || genericTypeDefinition == typeof(IList<>)) { typeToCreate = typeof(List<>).MakeGenericType(modelType.GetGenericArguments()); } } // fallback to the type's default constructor return Activator.CreateInstance(typeToCreate); } internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) { bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); // if the value provider returns an instance of the requested data type, we can just short-circuit // the evaluation and return that instance if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) { return valueProviderResult.RawValue; } // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following if (bindingContext.ModelType != typeof(string)) { // conversion results in 3 cases, as below if (bindingContext.ModelType.IsArray) { // case 1: user asked for an array // ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); return modelArray; } Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>)); if (enumerableType != null) { // case 2: user asked for a collection rather than an array // need to call ConvertTo() on the array type, then copy the array to the collection object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType); Type elementType = enumerableType.GetGenericArguments()[0]; Type arrayType = elementType.MakeArrayType(); object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType); Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(modelCollection)) { CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray); } return modelCollection; } } // case 3: user asked for an individual element object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); return model; } internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object model = bindingContext.Model; Type modelType = bindingContext.ModelType; // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created if (model == null && modelType.IsArray) { Type elementType = modelType.GetElementType(); Type listType = typeof(List<>).MakeGenericType(elementType); object collection = CreateModel(controllerContext, bindingContext, listType); ModelBindingContext arrayBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType); if (list == null) { return null; } Array array = Array.CreateInstance(elementType, list.Count); list.CopyTo(array, 0); return array; } if (model == null) { model = CreateModel(controllerContext, bindingContext, modelType); } // special-case IDictionary<,> and ICollection<> Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>)); if (dictionaryType != null) { Type[] genericArguments = dictionaryType.GetGenericArguments(); Type keyType = genericArguments[0]; Type valueType = genericArguments[1]; ModelBindingContext dictionaryBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType); return dictionary; } Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (enumerableType != null) { Type elementType = enumerableType.GetGenericArguments()[0]; Type collectionType = typeof(ICollection<>).MakeGenericType(elementType); if (collectionType.IsInstanceOfType(model)) { ModelBindingContext collectionBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType); return collection; } } // otherwise, just update the properties on the complex type BindComplexElementalModel(controllerContext, bindingContext, model); return model; } internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { // need to replace the property filter + model object and create an inner binding context ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model); // validation if (OnModelUpdating(controllerContext, newBindingContext)) { BindProperties(controllerContext, newBindingContext); OnModelUpdated(controllerContext, newBindingContext); } } internal ModelBindingContext CreateComplexElementalModelBindingContext(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { BindAttribute bindAttr = (BindAttribute)GetTypeDescriptor(controllerContext, bindingContext).GetAttributes()[typeof(BindAttribute)]; Predicate<string> newPropertyFilter = (bindAttr != null) ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName) : bindingContext.PropertyFilter; ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType), ModelName = bindingContext.ModelName, ModelState = bindingContext.ModelState, PropertyFilter = newPropertyFilter, ValueProvider = bindingContext.ValueProvider }; return newBindingContext; } internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) { bool stopOnIndexNotFound; IEnumerable<string> indexes; GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes); IModelBinder elementBinder = Binders.GetBinder(elementType); // build up a list of items from the request List<object> modelList = new List<object>(); foreach (string currentIndex in indexes) { string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex); if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey)) { if (stopOnIndexNotFound) { // we ran out of elements to pull break; } else { continue; } } ModelBindingContext innerContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, elementType), ModelName = subIndexKey, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object thisElement = elementBinder.BindModel(controllerContext, innerContext); // we need to merge model errors up AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement); modelList.Add(thisElement); } // if there weren't any elements at all in the request, just return if (modelList.Count == 0) { return null; } // replace the original collection object collection = bindingContext.Model; CollectionHelpers.ReplaceCollection(elementType, collection, modelList); return collection; } internal object UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType) { bool stopOnIndexNotFound; IEnumerable<string> indexes; GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes); IModelBinder keyBinder = Binders.GetBinder(keyType); IModelBinder valueBinder = Binders.GetBinder(valueType); // build up a list of items from the request List<KeyValuePair<object, object>> modelList = new List<KeyValuePair<object, object>>(); foreach (string currentIndex in indexes) { string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex); string keyFieldKey = CreateSubPropertyName(subIndexKey, "key"); string valueFieldKey = CreateSubPropertyName(subIndexKey, "value"); if (!(bindingContext.ValueProvider.ContainsPrefix(keyFieldKey) && bindingContext.ValueProvider.ContainsPrefix(valueFieldKey))) { if (stopOnIndexNotFound) { // we ran out of elements to pull break; } else { continue; } } // bind the key ModelBindingContext keyBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, keyType), ModelName = keyFieldKey, ModelState = bindingContext.ModelState, ValueProvider = bindingContext.ValueProvider }; object thisKey = keyBinder.BindModel(controllerContext, keyBindingContext); // we need to merge model errors up AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, keyFieldKey, keyType, thisKey); if (!keyType.IsInstanceOfType(thisKey)) { // we can't add an invalid key, so just move on continue; } // bind the value ModelBindingContext valueBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, valueType), ModelName = valueFieldKey, ModelState = bindingContext.ModelState, PropertyFilter = bindingContext.PropertyFilter, ValueProvider = bindingContext.ValueProvider }; object thisValue = valueBinder.BindModel(controllerContext, valueBindingContext); // we need to merge model errors up AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, valueFieldKey, valueType, thisValue); KeyValuePair<object, object> kvp = new KeyValuePair<object, object>(thisKey, thisValue); modelList.Add(kvp); } // if there weren't any elements at all in the request, just return if (modelList.Count == 0) { return null; } // replace the original collection object dictionary = bindingContext.Model; CollectionHelpers.ReplaceDictionary(keyType, valueType, dictionary, modelList); return dictionary; } private static bool ShouldPerformRequestValidation(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (controllerContext == null || controllerContext.Controller == null || bindingContext == null || bindingContext.ModelMetadata == null) { // To make unit testing easier, if the caller hasn't specified enough contextual information we just default // to always pulling the data from a collection that goes through request validation. return true; } // We should perform request validation only if both the controller and the model ask for it. This is the // default behavior for both. If either the controller (via [ValidateInput(false)]) or the model (via [AllowHtml]) // opts out, we don't validate. return (controllerContext.Controller.ValidateRequest && bindingContext.ModelMetadata.RequestValidationEnabled); } private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) { IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext); foreach (PropertyDescriptor property in properties) { BindProperty(controllerContext, bindingContext, property); } } private static void GetIndexes(ModelBindingContext bindingContext, out bool stopOnIndexNotFound, out IEnumerable<string> indexes) { string indexKey = CreateSubPropertyName(bindingContext.ModelName, "index"); ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(indexKey); if (vpResult != null) { string[] indexesArray = vpResult.ConvertTo(typeof(string[])) as string[]; if (indexesArray != null) { stopOnIndexNotFound = false; indexes = indexesArray; return; } } // just use a simple zero-based system stopOnIndexNotFound = true; indexes = GetZeroBasedIndexes(); } private static IEnumerable<string> GetZeroBasedIndexes() { for (int i = 0; ; i++) { yield return i.ToString(CultureInfo.InvariantCulture); } } private static void AddValueRequiredMessageToModelState(ControllerContext controllerContext, ModelStateDictionary modelState, string modelStateKey, Type elementType, object value) { if (value == null && !TypeHelpers.TypeAllowsNullValue(elementType) && modelState.IsValidField(modelStateKey)) { modelState.AddModelError(modelStateKey, GetValueRequiredResource(controllerContext)); } } private static string GetValueRequiredResource(ControllerContext controllerContext) { return "PropertyValueRequired"; } private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) { try { object convertedValue = valueProviderResult.ConvertTo(destinationType); return convertedValue; } catch (Exception ex) { modelState.AddModelError(modelStateKey, ex); return null; } } private static class CollectionHelpers { private static readonly MethodInfo _replaceCollectionMethod = typeof(CollectionHelpers).GetMethod("ReplaceCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic); private static readonly MethodInfo _replaceDictionaryMethod = typeof(CollectionHelpers).GetMethod("ReplaceDictionaryImpl", BindingFlags.Static | BindingFlags.NonPublic); [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void ReplaceCollection(Type collectionType, object collection, object newContents) { MethodInfo targetMethod = _replaceCollectionMethod.MakeGenericMethod(collectionType); targetMethod.Invoke(null, new object[] { collection, newContents }); } private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents) { collection.Clear(); if (newContents != null) { foreach (object item in newContents) { // if the item was not a T, some conversion failed. the error message will be propagated, // but in the meanwhile we need to make a placeholder element in the array. T castItem = (item is T) ? (T)item : default(T); collection.Add(castItem); } } } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void ReplaceDictionary(Type keyType, Type valueType, object dictionary, object newContents) { MethodInfo targetMethod = _replaceDictionaryMethod.MakeGenericMethod(keyType, valueType); targetMethod.Invoke(null, new object[] { dictionary, newContents }); } private static void ReplaceDictionaryImpl<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<object, object>> newContents) { dictionary.Clear(); foreach (KeyValuePair<object, object> item in newContents) { // if the item was not a T, some conversion failed. the error message will be propagated, // but in the meanwhile we need to make a placeholder element in the dictionary. TKey castKey = (TKey)item.Key; // this cast shouldn't fail TValue castValue = (item.Value is TValue) ? (TValue)item.Value : default(TValue); dictionary[castKey] = castValue; } } } } internal delegate bool TryGetValueDelegate(object dictionary, string key, out object value); internal static class TypeHelpers { private static readonly Dictionary<Type, TryGetValueDelegate> _tryGetValueDelegateCache = new Dictionary<Type, TryGetValueDelegate>(); private static readonly ReaderWriterLockSlim _tryGetValueDelegateCacheLock = new ReaderWriterLockSlim(); private static readonly MethodInfo _strongTryGetValueImplInfo = typeof(TypeHelpers).GetMethod("StrongTryGetValueImpl", BindingFlags.NonPublic | BindingFlags.Static); public static readonly Assembly MsCorLibAssembly = typeof(string).Assembly; public static readonly Assembly MvcAssembly = typeof(Controller).Assembly; public static readonly Assembly SystemWebAssembly = typeof(HttpContext).Assembly; // method is used primarily for lighting up new .NET Framework features even if MVC targets the previous version // thisParameter is the 'this' parameter if target method is instance method, should be null for static method public static TDelegate CreateDelegate<TDelegate>(Assembly assembly, string typeName, string methodName, object thisParameter) where TDelegate : class { // ensure target type exists Type targetType = assembly.GetType(typeName, false /* throwOnError */); if (targetType == null) { return null; } return CreateDelegate<TDelegate>(targetType, methodName, thisParameter); } public static TDelegate CreateDelegate<TDelegate>(Type targetType, string methodName, object thisParameter) where TDelegate : class { // ensure target method exists ParameterInfo[] delegateParameters = typeof(TDelegate).GetMethod("Invoke").GetParameters(); Type[] argumentTypes = Array.ConvertAll(delegateParameters, pInfo => pInfo.ParameterType); MethodInfo targetMethod = targetType.GetMethod(methodName, argumentTypes); if (targetMethod == null) { return null; } TDelegate d = Delegate.CreateDelegate(typeof(TDelegate), thisParameter, targetMethod, false /* throwOnBindFailure */) as TDelegate; return d; } public static TryGetValueDelegate CreateTryGetValueDelegate(Type targetType) { TryGetValueDelegate result; _tryGetValueDelegateCacheLock.EnterReadLock(); try { if (_tryGetValueDelegateCache.TryGetValue(targetType, out result)) { return result; } } finally { _tryGetValueDelegateCacheLock.ExitReadLock(); } Type dictionaryType = ExtractGenericInterface(targetType, typeof(IDictionary<,>)); // just wrap a call to the underlying IDictionary<TKey, TValue>.TryGetValue() where string can be cast to TKey if (dictionaryType != null) { Type[] typeArguments = dictionaryType.GetGenericArguments(); Type keyType = typeArguments[0]; Type returnType = typeArguments[1]; if (keyType.IsAssignableFrom(typeof(string))) { MethodInfo strongImplInfo = _strongTryGetValueImplInfo.MakeGenericMethod(keyType, returnType); result = (TryGetValueDelegate)Delegate.CreateDelegate(typeof(TryGetValueDelegate), strongImplInfo); } } // wrap a call to the underlying IDictionary.Item() if (result == null && typeof(IDictionary).IsAssignableFrom(targetType)) { result = TryGetValueFromNonGenericDictionary; } _tryGetValueDelegateCacheLock.EnterWriteLock(); try { _tryGetValueDelegateCache[targetType] = result; } finally { _tryGetValueDelegateCacheLock.ExitWriteLock(); } return result; } public static Type ExtractGenericInterface(Type queryType, Type interfaceType) { Func<Type, bool> matchesInterface = t => t.IsGenericType && t.GetGenericTypeDefinition() == interfaceType; return (matchesInterface(queryType)) ? queryType : queryType.GetInterfaces().FirstOrDefault(matchesInterface); } public static object GetDefaultValue(Type type) { return (TypeAllowsNullValue(type)) ? null : Activator.CreateInstance(type); } public static bool IsCompatibleObject<T>(object value) { return (value is T || (value == null && TypeAllowsNullValue(typeof(T)))); } public static bool IsNullableValueType(Type type) { return Nullable.GetUnderlyingType(type) != null; } private static bool StrongTryGetValueImpl<TKey, TValue>(object dictionary, string key, out object value) { IDictionary<TKey, TValue> strongDict = (IDictionary<TKey, TValue>)dictionary; TValue strongValue; bool retVal = strongDict.TryGetValue((TKey)(object)key, out strongValue); value = strongValue; return retVal; } private static bool TryGetValueFromNonGenericDictionary(object dictionary, string key, out object value) { IDictionary weakDict = (IDictionary)dictionary; bool containsKey = weakDict.Contains(key); value = (containsKey) ? weakDict[key] : null; return containsKey; } public static bool TypeAllowsNullValue(Type type) { return (!type.IsValueType || IsNullableValueType(type)); } }