// Copyright 2009-2022 Josh Close // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; namespace CsvHelper { /// /// Efficiently creates instances of object types. /// public class ObjectCreator { private readonly Dictionary> cache = new Dictionary>(); /// /// Creates an instance of type T using the given arguments. /// /// The type to create an instance of. /// The constrcutor arguments. public T CreateInstance(params object[] args) { return (T)CreateInstance(typeof(T), args); } /// /// Creates an instance of the given type using the given arguments. /// /// The type to create an instance of. /// The constructor arguments. public object CreateInstance(Type type, params object[] args) { var func = GetFunc(type, args); return func(args); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Func GetFunc(Type type, object[] args) { var argTypes = GetArgTypes(args); var key = GetConstructorCacheKey(type, argTypes); if (!cache.TryGetValue(key, out var func)) { cache[key] = func = CreateInstanceFunc(type, argTypes); } return func; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type[] GetArgTypes(object[] args) { var argTypes = new Type[args.Length]; for (var i = 0; i < args.Length; i++) { argTypes[i] = args[i]?.GetType() ?? typeof(object); } return argTypes; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetConstructorCacheKey(Type type, Type[] args) { #if !NET45 var hashCode = new HashCode(); hashCode.Add(type.GetHashCode()); for (var i = 0; i < args.Length; i++) { hashCode.Add(args[i].GetHashCode()); } return hashCode.ToHashCode(); #else unchecked { var hash = 17; hash = hash * 31 + type.GetHashCode(); for (var i = 0; i < args.Length; i++) { hash = hash * 31 + (args[i].GetHashCode()); } return hash; } #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Func CreateInstanceFunc(Type type, Type[] argTypes) { var parameterExpression = Expression.Parameter(typeof(object[]), "args"); Expression body; if (type.IsValueType) { if (argTypes.Length > 0) { throw GetConstructorNotFoundException(type, argTypes); } body = Expression.Convert(Expression.Default(type), typeof(object)); } else { var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var constructor = GetConstructor(constructors, type, argTypes); var parameters = constructor.GetParameters(); var parameterTypes = new Type[parameters.Length]; for (var i = 0; i < parameters.Length; i++) { parameterTypes[i] = parameters[i].ParameterType; } var arguments = new List(); for (var i = 0; i < parameterTypes.Length; i++) { var parameterType = parameterTypes[i]; var arrayIndexExpression = Expression.ArrayIndex(parameterExpression, Expression.Constant(i)); var convertExpression = Expression.Convert(arrayIndexExpression, parameterType); arguments.Add(convertExpression); } body = Expression.New(constructor, arguments); } var lambda = Expression.Lambda>(body, new[] { parameterExpression }); var func = lambda.Compile(); return func; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ConstructorInfo GetConstructor(ConstructorInfo[] constructors, Type type, Type[] argTypes) { var matchType = MatchType.Exact; var fuzzyMatches = new List(); for (var i = 0; i < constructors.Length; i++) { var constructor = constructors[i]; var parameters = constructors[i].GetParameters(); if (parameters.Length != argTypes.Length) { continue; } for (var j = 0; j < parameters.Length && j < argTypes.Length; j++) { var parameterType = parameters[j].ParameterType; var argType = argTypes[j]; if (argType == parameterType) { matchType = MatchType.Exact; continue; } if (!parameterType.IsValueType && (parameterType.IsAssignableFrom(argType) || argType == typeof(object))) { matchType = MatchType.Fuzzy; continue; } matchType = MatchType.None; break; } if (matchType == MatchType.Exact) { // Only possible to have one exact match. return constructor; } if (matchType == MatchType.Fuzzy) { fuzzyMatches.Add(constructor); } } if (fuzzyMatches.Count == 1) { return fuzzyMatches[0]; } if (fuzzyMatches.Count > 1) { throw new AmbiguousMatchException(); } throw GetConstructorNotFoundException(type, argTypes); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static MissingMethodException GetConstructorNotFoundException(Type type, Type[] argTypes) { var signature = $"{type.FullName}({string.Join(", ", argTypes.Select(a => a.FullName))})"; throw new MissingMethodException($"Constructor '{signature}' was not found."); } private enum MatchType { None = 0, Exact = 1, Fuzzy = 2 } } }