Skip to content

Instantly share code, notes, and snippets.

@doekman
Created February 20, 2015 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save doekman/b5fbe3944bfe0283670e to your computer and use it in GitHub Desktop.
Save doekman/b5fbe3944bfe0283670e to your computer and use it in GitHub Desktop.
Changed ObjectToDictionary implementation, for handling arrays. The change is in the file TypeHelper2.cs; The file PropertyHelper2.cs is an internal class, so it was necessary to include it here too.
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
namespace System.Web.WebPages
{
/// <summary>
/// Ripped from: http://aspnetwebstack.codeplex.com/SourceControl/latest#src/Common/PropertyHelper.cs
/// </summary>
internal class PropertyHelper2
{
private static ConcurrentDictionary<Type, PropertyHelper2[]> _reflectionCache = new ConcurrentDictionary<Type, PropertyHelper2[]>();
private Func<object, object> _valueGetter;
/// <summary>
/// Initializes a fast property helper. This constructor does not cache the helper.
/// </summary>
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "This is intended the Name is auto set differently per type and the type is internal")]
public PropertyHelper2(PropertyInfo property)
{
Contract.Assert(property != null);
Name = property.Name;
_valueGetter = MakeFastPropertyGetter(property);
}
/// <summary>
/// Creates a single fast property setter. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
/// <returns>a fast setter.</returns>
/// <remarks>This method is more memory efficient than a dynamically compiled lambda, and about the same speed.</remarks>
public static Action<TDeclaringType, object> MakeFastPropertySetter<TDeclaringType>(PropertyInfo propertyInfo)
where TDeclaringType : class
{
Contract.Assert(propertyInfo != null);
MethodInfo setMethod = propertyInfo.GetSetMethod();
Contract.Assert(setMethod != null);
Contract.Assert(!setMethod.IsStatic);
Contract.Assert(setMethod.GetParameters().Length == 1);
Contract.Assert(!propertyInfo.ReflectedType.IsValueType);
// Instance methods in the CLR can be turned into static methods where the first parameter
// is open over "this". This parameter is always passed by reference, so we have a code
// path for value types and a code path for reference types.
Type typeInput = propertyInfo.ReflectedType;
Type typeValue = setMethod.GetParameters()[0].ParameterType;
Delegate callPropertySetterDelegate;
// Create a delegate TValue -> "TDeclaringType.Property"
var propertySetterAsAction = setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, typeValue));
var callPropertySetterClosedGenericMethod = _callPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, typeValue);
callPropertySetterDelegate = Delegate.CreateDelegate(typeof(Action<TDeclaringType, object>), propertySetterAsAction, callPropertySetterClosedGenericMethod);
return (Action<TDeclaringType, object>)callPropertySetterDelegate;
}
public virtual string Name { get; protected set; }
public object GetValue(object instance)
{
Contract.Assert(_valueGetter != null, "Must call Initialize before using this object");
return _valueGetter(instance);
}
/// <summary>
/// Creates and caches fast property helpers that expose getters for every public get property on the underlying type.
/// </summary>
/// <param name="instance">the instance to extract property accessors for.</param>
/// <returns>a cached array of all public property getters from the underlying type of this instance.</returns>
public static PropertyHelper2[] GetProperties(object instance)
{
return GetProperties(instance, CreateInstance, _reflectionCache);
}
/// <summary>
/// Creates a single fast property getter. The result is not cached.
/// </summary>
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
/// <returns>a fast getter.</returns>
/// <remarks>This method is more memory efficient than a dynamically compiled lambda, and about the same speed.</remarks>
public static Func<object, object> MakeFastPropertyGetter(PropertyInfo propertyInfo)
{
Contract.Assert(propertyInfo != null);
MethodInfo getMethod = propertyInfo.GetGetMethod();
Contract.Assert(getMethod != null);
Contract.Assert(!getMethod.IsStatic);
Contract.Assert(getMethod.GetParameters().Length == 0);
// Instance methods in the CLR can be turned into static methods where the first parameter
// is open over "this". This parameter is always passed by reference, so we have a code
// path for value types and a code path for reference types.
Type typeInput = getMethod.ReflectedType;
Type typeOutput = getMethod.ReturnType;
Delegate callPropertyGetterDelegate;
if (typeInput.IsValueType)
{
// Create a delegate (ref TDeclaringType) -> TValue
Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(ByRefFunc<,>).MakeGenericType(typeInput, typeOutput));
MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterByReferenceOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput);
callPropertyGetterDelegate = Delegate.CreateDelegate(typeof(Func<object, object>), propertyGetterAsFunc, callPropertyGetterClosedGenericMethod);
}
else
{
// Create a delegate TDeclaringType -> TValue
Delegate propertyGetterAsFunc = getMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(typeInput, typeOutput));
MethodInfo callPropertyGetterClosedGenericMethod = _callPropertyGetterOpenGenericMethod.MakeGenericMethod(typeInput, typeOutput);
callPropertyGetterDelegate = Delegate.CreateDelegate(typeof(Func<object, object>), propertyGetterAsFunc, callPropertyGetterClosedGenericMethod);
}
return (Func<object, object>)callPropertyGetterDelegate;
}
private static PropertyHelper2 CreateInstance(PropertyInfo property)
{
return new PropertyHelper2(property);
}
// Implementation of the fast getter.
private delegate TValue ByRefFunc<TDeclaringType, TValue>(ref TDeclaringType arg);
private static readonly MethodInfo _callPropertyGetterOpenGenericMethod = typeof(PropertyHelper2).GetMethod("CallPropertyGetter", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo _callPropertyGetterByReferenceOpenGenericMethod = typeof(PropertyHelper2).GetMethod("CallPropertyGetterByReference", BindingFlags.NonPublic | BindingFlags.Static);
private static object CallPropertyGetter<TDeclaringType, TValue>(Func<TDeclaringType, TValue> getter, object @this)
{
return getter((TDeclaringType)@this);
}
private static object CallPropertyGetterByReference<TDeclaringType, TValue>(ByRefFunc<TDeclaringType, TValue> getter, object @this)
{
TDeclaringType unboxed = (TDeclaringType)@this;
return getter(ref unboxed);
}
// Implementation of the fast setter.
private static readonly MethodInfo _callPropertySetterOpenGenericMethod = typeof(PropertyHelper2).GetMethod("CallPropertySetter", BindingFlags.NonPublic | BindingFlags.Static);
private static void CallPropertySetter<TDeclaringType, TValue>(Action<TDeclaringType, TValue> setter, object @this, object value)
{
setter((TDeclaringType)@this, (TValue)value);
}
protected static PropertyHelper2[] GetProperties(object instance,
Func<PropertyInfo, PropertyHelper2> createPropertyHelper,
ConcurrentDictionary<Type, PropertyHelper2[]> cache)
{
// Using an array rather than IEnumerable, as this will be called on the hot path numerous times.
PropertyHelper2[] helpers;
Type type = instance.GetType();
if (!cache.TryGetValue(type, out helpers))
{
// We avoid loading indexed properties using the where statement.
// Indexed properties are not useful (or valid) for grabbing properties off an anonymous object.
IEnumerable<PropertyInfo> properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(prop => prop.GetIndexParameters().Length == 0 &&
prop.GetMethod != null);
var newHelpers = new List<PropertyHelper2>();
foreach (PropertyInfo property in properties)
{
PropertyHelper2 propertyHelper = createPropertyHelper(property);
newHelpers.Add(propertyHelper);
}
helpers = newHelpers.ToArray();
cache.TryAdd(type, helpers);
}
return helpers;
}
}
}
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Web.Routing;
namespace System.Web.WebPages
{
/// <summary>
/// Ripped from: http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.WebPages/Utils/TypeHelper.cs
/// </summary>
public static class TypeHelper2
{
/// <summary>
/// Given an object of anonymous type, add each property as a key and associated with its value to a dictionary.
///
/// Different from the MVC-implementation: when an item implements IEnumerable, for each item an entry is
/// created in the returned dictionary with as key "Name[index]" (index is 0 based).
///
/// Also, null-values are default excluded, to keep the querystring short.
///
/// This helper will cache accessors and types, and is intended when the anonymous object is accessed multiple
/// times throughout the lifetime of the web application.
/// </summary>
public static RouteValueDictionary ObjectToDictionary(object value, bool includeNullValues = false)
{
RouteValueDictionary dictionary = new RouteValueDictionary();
if (value != null)
{
foreach (PropertyHelper2 helper in PropertyHelper2.GetProperties(value))
{
var value2 = helper.GetValue(value);
var value2enum = value2 as System.Collections.IEnumerable;
if (value2enum != null)
{
int index = 0;
foreach (var value2item in value2enum)
{
if (value2item != null || includeNullValues)
{
var name = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", helper.Name, index++);
dictionary.Add(name, value2item);
}
}
}
else
{
if (value2 != null || includeNullValues)
{
dictionary.Add(helper.Name, value2);
}
}
}
}
return dictionary;
}
/// <summary>
/// Given an object of anonymous type, add each property as a key and associated with its value to a dictionary.
///
/// This helper will not cache accessors and types, and is intended when the anonymous object is accessed once
/// or very few times throughout the lifetime of the web application.
/// </summary>
public static RouteValueDictionary ObjectToDictionaryUncached(object value)
{
RouteValueDictionary dictionary = new RouteValueDictionary();
if (value != null)
{
foreach (PropertyHelper2 helper in PropertyHelper2.GetProperties(value))
{
dictionary.Add(helper.Name, helper.GetValue(value));
}
}
return dictionary;
}
/// <summary>
/// Given an object of anonymous type, add each property as a key and associated with its value to the given dictionary.
/// </summary>
public static void AddAnonymousObjectToDictionary(IDictionary<string, object> dictionary, object value)
{
var values = ObjectToDictionary(value);
foreach (var item in values)
{
dictionary.Add(item);
}
}
/// <remarks>This code is copied from http://www.liensberger.it/web/blog/?p=191 </remarks>
public static bool IsAnonymousType(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
// TODO: The only way to detect anonymous types right now.
return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
&& type.IsGenericType && type.Name.Contains("AnonymousType")
&& (type.Name.StartsWith("<>", StringComparison.OrdinalIgnoreCase) || type.Name.StartsWith("VB$", StringComparison.OrdinalIgnoreCase))
&& (type.Attributes & TypeAttributes.NotPublic) == TypeAttributes.NotPublic;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment