tools/AutoMapper/TypeDetails.cs (187 lines of code) (raw):

using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using AutoMapper.Configuration; namespace AutoMapper { /// <summary> /// Contains cached reflection information for easy retrieval /// </summary> [DebuggerDisplay("{Type}")] public class TypeDetails { public TypeDetails(Type type, ProfileMap config) { Type = type; var membersToMap = MembersToMap(config.ShouldMapProperty, config.ShouldMapField); var publicReadableMembers = GetAllPublicReadableMembers(membersToMap); var publicWritableMembers = GetAllPublicWritableMembers(membersToMap); PublicReadAccessors = BuildPublicReadAccessors(publicReadableMembers); PublicWriteAccessors = BuildPublicAccessors(publicWritableMembers); PublicNoArgMethods = BuildPublicNoArgMethods(); Constructors = type.GetDeclaredConstructors().Where(ci => !ci.IsStatic).ToArray(); PublicNoArgExtensionMethods = BuildPublicNoArgExtensionMethods(config.SourceExtensionMethods); AllMembers = PublicReadAccessors.Concat(PublicNoArgMethods).Concat(PublicNoArgExtensionMethods).ToList(); DestinationMemberNames = AllMembers.Select(mi => new DestinationMemberName { Member = mi, Possibles = PossibleNames(mi.Name, config.Prefixes, config.Postfixes).ToArray() }); } private IEnumerable<string> PossibleNames(string memberName, IEnumerable<string> prefixes, IEnumerable<string> postfixes) { yield return memberName; if (!postfixes.Any()) { foreach (var withoutPrefix in prefixes.Where(prefix => memberName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).Select(prefix => memberName.Substring(prefix.Length))) { yield return withoutPrefix; } yield break; } foreach (var withoutPrefix in prefixes.Where(prefix => memberName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).Select(prefix => memberName.Substring(prefix.Length))) { yield return withoutPrefix; foreach (var s in PostFixes(postfixes, withoutPrefix)) yield return s; } foreach (var s in PostFixes(postfixes, memberName)) yield return s; } private static IEnumerable<string> PostFixes(IEnumerable<string> postfixes, string name) { return postfixes.Where(postfix => name.EndsWith(postfix, StringComparison.OrdinalIgnoreCase)) .Select(postfix => name.Remove(name.Length - postfix.Length)); } private static Func<MemberInfo, bool> MembersToMap(Func<PropertyInfo, bool> shouldMapProperty, Func<FieldInfo, bool> shouldMapField) { return m => { switch (m) { case PropertyInfo property: return !property.IsStatic() && shouldMapProperty(property); case FieldInfo field: return !field.IsStatic && shouldMapField(field); default: throw new ArgumentException("Should be a field or property."); } }; } public struct DestinationMemberName { public MemberInfo Member { get; set; } public string[] Possibles { get; set; } } public Type Type { get; } public IEnumerable<ConstructorInfo> Constructors { get; } public IEnumerable<MemberInfo> PublicReadAccessors { get; } public IEnumerable<MemberInfo> PublicWriteAccessors { get; } public IEnumerable<MethodInfo> PublicNoArgMethods { get; } public IEnumerable<MethodInfo> PublicNoArgExtensionMethods { get; } public IEnumerable<MemberInfo> AllMembers { get; } public IEnumerable<DestinationMemberName> DestinationMemberNames { get; set; } private IEnumerable<MethodInfo> BuildPublicNoArgExtensionMethods(IEnumerable<MethodInfo> sourceExtensionMethodSearch) { var explicitExtensionMethods = sourceExtensionMethodSearch.Where(method => method.GetParameters()[0].ParameterType == Type); var genericInterfaces = Type.GetTypeInfo().ImplementedInterfaces.Where(t => t.IsGenericType()); if (Type.IsInterface() && Type.IsGenericType()) { genericInterfaces = genericInterfaces.Union(new[] { Type }); } return explicitExtensionMethods.Union ( from genericInterface in genericInterfaces let genericInterfaceArguments = genericInterface.GetTypeInfo().GenericTypeArguments let matchedMethods = ( from extensionMethod in sourceExtensionMethodSearch where !extensionMethod.IsGenericMethodDefinition select extensionMethod ).Concat( from extensionMethod in sourceExtensionMethodSearch where extensionMethod.IsGenericMethodDefinition && extensionMethod.GetGenericArguments().Length == genericInterfaceArguments.Length let constructedGeneric = MakeGenericMethod(extensionMethod, genericInterfaceArguments) where constructedGeneric != null select constructedGeneric ) from methodMatch in matchedMethods where methodMatch.GetParameters()[0].ParameterType.GetTypeInfo().IsAssignableFrom(genericInterface.GetTypeInfo()) select methodMatch ).ToArray(); } // Use method.MakeGenericMethod(genericArguments) wrapped in a try/catch(ArgumentException) // in order to catch exceptions resulting from the generic arguments not being compatible // with any constraints that may be on the generic method's generic parameters. static MethodInfo MakeGenericMethod(MethodInfo genericMethod, Type[] genericArguments) { try { return genericMethod.MakeGenericMethod(genericArguments); } catch (ArgumentException) { return null; } } private static MemberInfo[] BuildPublicReadAccessors(IEnumerable<MemberInfo> allMembers) { // Multiple types may define the same property (e.g. the class and multiple interfaces) - filter this to one of those properties var filteredMembers = allMembers .OfType<PropertyInfo>() .GroupBy(x => x.Name) // group properties of the same name together .Select(x => x.First()) .Concat(allMembers.Where(x => x is FieldInfo)); // add FieldInfo objects back return filteredMembers.ToArray(); } private static MemberInfo[] BuildPublicAccessors(IEnumerable<MemberInfo> allMembers) { // Multiple types may define the same property (e.g. the class and multiple interfaces) - filter this to one of those properties var filteredMembers = allMembers .OfType<PropertyInfo>() .GroupBy(x => x.Name) // group properties of the same name together .Select(x => x.Any(y => y.CanWrite && y.CanRead) ? // favor the first property that can both read & write - otherwise pick the first one x.First(y => y.CanWrite && y.CanRead) : x.First()) .Where(pi => pi.CanWrite || pi.PropertyType.IsListOrDictionaryType()) //.OfType<MemberInfo>() // cast back to MemberInfo so we can add back FieldInfo objects .Concat(allMembers.Where(x => x is FieldInfo)); // add FieldInfo objects back return filteredMembers.ToArray(); } private IEnumerable<MemberInfo> GetAllPublicReadableMembers(Func<MemberInfo, bool> membersToMap) => GetAllPublicMembers(PropertyReadable, FieldReadable, membersToMap); private IEnumerable<MemberInfo> GetAllPublicWritableMembers(Func<MemberInfo, bool> membersToMap) => GetAllPublicMembers(PropertyWritable, FieldWritable, membersToMap); private static bool PropertyReadable(PropertyInfo propertyInfo) => propertyInfo.CanRead; private static bool FieldReadable(FieldInfo fieldInfo) => true; private static bool PropertyWritable(PropertyInfo propertyInfo) { var propertyIsEnumerable = (typeof(string) != propertyInfo.PropertyType) && typeof(IEnumerable).GetTypeInfo().IsAssignableFrom(propertyInfo.PropertyType.GetTypeInfo()); return propertyInfo.CanWrite || propertyIsEnumerable; } private static bool FieldWritable(FieldInfo fieldInfo) => !fieldInfo.IsInitOnly; private IEnumerable<MemberInfo> GetAllPublicMembers( Func<PropertyInfo, bool> propertyAvailableFor, Func<FieldInfo, bool> fieldAvailableFor, Func<MemberInfo, bool> memberAvailableFor) { var typesToScan = new List<Type>(); for (var t = Type; t != null; t = t.BaseType()) typesToScan.Add(t); if (Type.IsInterface()) typesToScan.AddRange(Type.GetTypeInfo().ImplementedInterfaces); // Scan all types for public properties and fields return typesToScan .Where(x => x != null) // filter out null types (e.g. type.BaseType == null) .SelectMany(x => x.GetDeclaredMembers() .Where(mi => mi.DeclaringType != null && mi.DeclaringType == x) .Where( m => m is FieldInfo && fieldAvailableFor((FieldInfo)m) || m is PropertyInfo && propertyAvailableFor((PropertyInfo)m) && !((PropertyInfo)m).GetIndexParameters().Any()) .Where(memberAvailableFor) ); } private MethodInfo[] BuildPublicNoArgMethods() { return Type.GetAllMethods() .Where(mi => mi.IsPublic && !mi.IsStatic && mi.DeclaringType != typeof(object)) .Where(m => (m.ReturnType != typeof(void)) && (m.GetParameters().Length == 0)) .ToArray(); } } }