// 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
}
}
}