/// <summary> /// Maps a constructor parameter to a CSV field. /// </summary> /// <param name="name">The name of the constructor parameter.</param> public virtual ParameterMap Parameter(string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } var args = new GetConstructorArgs(ClassType); return(Parameter(() => ConfigurationFunctions.GetConstructor(args), name)); }
/// <summary> /// Creates the member assignments for the given <see cref="ClassMap"/>. /// </summary> /// <param name="mapping">The mapping to create the assignments for.</param> /// <param name="assignments">The assignments that will be added to from the mapping.</param> public virtual void CreateMemberAssignmentsForMapping(ClassMap mapping, List <MemberAssignment> assignments) { foreach (var memberMap in mapping.MemberMaps) { var fieldExpression = CreateGetFieldExpression(memberMap); if (fieldExpression == null) { continue; } assignments.Add(Expression.Bind(memberMap.Data.Member, fieldExpression)); } foreach (var referenceMap in mapping.ReferenceMaps) { if (!reader.CanRead(referenceMap)) { continue; } Expression referenceBody; if (referenceMap.Data.Mapping.ParameterMaps.Count > 0) { var arguments = new List <Expression>(); CreateConstructorArgumentExpressionsForMapping(referenceMap.Data.Mapping, arguments); var args = new GetConstructorArgs { ClassType = referenceMap.Data.Mapping.ClassType, }; referenceBody = Expression.New(reader.Configuration.GetConstructor(args), arguments); } else { var referenceAssignments = new List <MemberAssignment>(); CreateMemberAssignmentsForMapping(referenceMap.Data.Mapping, referenceAssignments); referenceBody = CreateInstanceAndAssignMembers(referenceMap.Data.Member.MemberType(), referenceAssignments); } assignments.Add(Expression.Bind(referenceMap.Data.Member, referenceBody)); } }
/// <summary> /// Creates a <see cref="Delegate"/> of type <see cref="Func{T}"/> /// that will create a record of the given type using the current /// reader row. /// </summary> /// <param name="recordType">The record type.</param> protected override Delegate CreateCreateRecordDelegate(Type recordType) { if (Reader.Context.Maps[recordType] == null) { Reader.Context.Maps.Add(Reader.Context.AutoMap(recordType)); } var map = Reader.Context.Maps[recordType]; Expression body; if (map.ParameterMaps.Count > 0) { // This is a constructor parameter type. var arguments = new List <Expression>(); ExpressionManager.CreateConstructorArgumentExpressionsForMapping(map, arguments); var args = new GetConstructorArgs { ClassType = map.ClassType }; body = Expression.New(Reader.Configuration.GetConstructor(args), arguments); } else { var assignments = new List <MemberAssignment>(); ExpressionManager.CreateMemberAssignmentsForMapping(map, assignments); if (assignments.Count == 0) { throw new ReaderException(Reader.Context, $"No members are mapped for type '{recordType.FullName}'."); } body = ExpressionManager.CreateInstanceAndAssignMembers(recordType, assignments); } var funcType = typeof(Func <>).MakeGenericType(recordType); return(Expression.Lambda(funcType, body).Compile()); }
/// <summary> /// Returns the type's constructor with the most parameters. /// If two constructors have the same number of parameters, then /// there is no guarantee which one will be returned. If you have /// that situation, you should probably implement this function yourself. /// </summary> public static ConstructorInfo GetConstructor(GetConstructorArgs args) { return(args.ClassType.GetConstructorWithMostParameters()); }
/// <summary> /// Creates the constructor arguments used to create a type. /// </summary> /// <param name="map">The mapping to create the arguments for.</param> /// <param name="argumentExpressions">The arguments that will be added to the mapping.</param> public virtual void CreateConstructorArgumentExpressionsForMapping(ClassMap map, List <Expression> argumentExpressions) { foreach (var parameterMap in map.ParameterMaps) { if (parameterMap.Data.IsConstantSet) { var constantExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Constant), parameterMap.Data.Parameter.ParameterType); argumentExpressions.Add(constantExpression); continue; } if (parameterMap.Data.Ignore) { Expression defaultExpression; if (parameterMap.Data.IsDefaultSet) { defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Default), parameterMap.Data.Parameter.ParameterType); } else if (parameterMap.Data.Parameter.HasDefaultValue) { defaultExpression = Expression.Convert(Expression.Constant(parameterMap.Data.Parameter.DefaultValue), parameterMap.Data.Parameter.ParameterType); } else { defaultExpression = Expression.Default(parameterMap.Data.Parameter.ParameterType); } argumentExpressions.Add(defaultExpression); continue; } if (parameterMap.ConstructorTypeMap != null) { // Constructor parameter type. var arguments = new List <Expression>(); CreateConstructorArgumentExpressionsForMapping(parameterMap.ConstructorTypeMap, arguments); var args = new GetConstructorArgs { ClassType = parameterMap.ConstructorTypeMap.ClassType, }; var constructorExpression = Expression.New(reader.Configuration.GetConstructor(args), arguments); argumentExpressions.Add(constructorExpression); } else if (parameterMap.ReferenceMap != null) { // Reference type. var referenceAssignments = new List <MemberAssignment>(); CreateMemberAssignmentsForMapping(parameterMap.ReferenceMap.Data.Mapping, referenceAssignments); var referenceBody = CreateInstanceAndAssignMembers(parameterMap.ReferenceMap.Data.Parameter.ParameterType, referenceAssignments); argumentExpressions.Add(referenceBody); } else { // Value type. int index; if (parameterMap.Data.IsNameSet || reader.Configuration.HasHeaderRecord && !parameterMap.Data.IsIndexSet) { // Use name. index = reader.GetFieldIndex(parameterMap.Data.Names.ToArray(), parameterMap.Data.NameIndex, parameterMap.Data.IsOptional); if (index == -1) { if (parameterMap.Data.IsDefaultSet || parameterMap.Data.IsOptional) { var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); argumentExpressions.Add(defaultExpression); continue; } // Skip if the index was not found. continue; } } else if (!parameterMap.Data.IsIndexSet && parameterMap.Data.IsOptional) { // If there wasn't an index explicitly, use a default value since constructors need all // arguments to be created. var defaultExpression = CreateDefaultExpression(parameterMap, Expression.Constant(string.Empty)); argumentExpressions.Add(defaultExpression); continue; } else { // Use index. index = parameterMap.Data.Index; } // Get the field using the field index. var method = typeof(IReaderRow).GetProperty("Item", typeof(string), new[] { typeof(int) }).GetGetMethod(); Expression fieldExpression = Expression.Call(Expression.Constant(reader), method, Expression.Constant(index, typeof(int))); if (parameterMap.Data.IsDefaultSet) { fieldExpression = CreateDefaultExpression(parameterMap, fieldExpression); } else { fieldExpression = CreateTypeConverterExpression(parameterMap, fieldExpression); } argumentExpressions.Add(fieldExpression); } } }
/// <summary> /// Auto maps the given map using constructor parameters. /// </summary> /// <param name="map">The map.</param> /// <param name="context">The context.</param> /// <param name="mapParents">The list of parents for the map.</param> /// <param name="indexStart">The index starting point.</param> protected virtual void AutoMapConstructorParameters(ClassMap map, CsvContext context, LinkedList <Type> mapParents, int indexStart = 0) { var type = map.GetGenericType(); var args = new GetConstructorArgs(map.ClassType); var constructor = context.Configuration.GetConstructor(args); var parameters = constructor.GetParameters(); foreach (var parameter in parameters) { var parameterMap = new ParameterMap(parameter); if (parameter.GetCustomAttributes <IgnoreAttribute>(true).Any() || parameter.GetCustomAttributes <ConstantAttribute>(true).Any()) { // If there is an IgnoreAttribute or ConstantAttribute, we still need to add a map because a constructor requires // all parameters to be present. A default value will be used later on. ApplyAttributes(parameterMap); map.ParameterMaps.Add(parameterMap); continue; } var typeConverterType = context.TypeConverterCache.GetConverter(parameter.ParameterType).GetType(); var memberTypeInfo = parameter.ParameterType.GetTypeInfo(); var isDefaultConverter = typeConverterType == typeof(DefaultTypeConverter); if (isDefaultConverter && (memberTypeInfo.HasParameterlessConstructor() || memberTypeInfo.IsUserDefinedStruct())) { // If the type is not one covered by our type converters // and it has a parameterless constructor, create a // reference map for it. if (context.Configuration.IgnoreReferences) { throw new InvalidOperationException($"Configuration '{nameof(CsvConfiguration.IgnoreReferences)}' can't be true " + "when using types without a default constructor. Constructor parameters " + "are used and all members including references must be used."); } if (CheckForCircularReference(parameter.ParameterType, mapParents)) { throw new InvalidOperationException($"A circular reference was detected in constructor paramter '{parameter.Name}'." + "Since all parameters must be supplied for a constructor, this parameter can't be skipped."); } mapParents.AddLast(type); var refMapType = typeof(DefaultClassMap <>).MakeGenericType(parameter.ParameterType); var refMap = (ClassMap)ObjectResolver.Current.Resolve(refMapType); AutoMapMembers(refMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); var referenceMap = new ParameterReferenceMap(parameter, refMap); if (context.Configuration.ReferenceHeaderPrefix != null) { var referenceHeaderPrefix = new ReferenceHeaderPrefixArgs(memberTypeInfo.MemberType(), memberTypeInfo.Name); referenceMap.Data.Prefix = context.Configuration.ReferenceHeaderPrefix(referenceHeaderPrefix); } ApplyAttributes(referenceMap); parameterMap.ReferenceMap = referenceMap; } else if (context.Configuration.ShouldUseConstructorParameters(new ShouldUseConstructorParametersArgs(parameter.ParameterType))) { mapParents.AddLast(type); var constructorMapType = typeof(DefaultClassMap <>).MakeGenericType(parameter.ParameterType); var constructorMap = (ClassMap)ObjectResolver.Current.Resolve(constructorMapType); // Need to use Max here for nested types. AutoMapConstructorParameters(constructorMap, context, mapParents, Math.Max(map.GetMaxIndex(isParameter: true) + 1, indexStart)); mapParents.Drop(mapParents.Find(type)); parameterMap.ConstructorTypeMap = constructorMap; } else { parameterMap.Data.TypeConverterOptions = TypeConverterOptions.Merge(new TypeConverterOptions(), context.TypeConverterOptionsCache.GetOptions(parameter.ParameterType), parameterMap.Data.TypeConverterOptions); parameterMap.Data.Index = map.GetMaxIndex(isParameter: true) + 1; ApplyAttributes(parameterMap); } map.ParameterMaps.Add(parameterMap); } map.ReIndex(indexStart); }