Skip to content

Commit f4b3181

Browse files
committed
Optimising string-to-enum mapping
1 parent 79981e7 commit f4b3181

File tree

4 files changed

+96
-43
lines changed

4 files changed

+96
-43
lines changed

AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,26 @@ public void ShouldMapANonMatchingStringToANullableEnum()
141141
result.Value.ShouldBeNull();
142142
}
143143

144+
[Fact]
145+
public void ShouldMapANullStringOverANullableEnum()
146+
{
147+
var source = new PublicField<string> { Value = default(string) };
148+
var target = new PublicProperty<Title?> { Value = Dr };
149+
150+
Mapper.Map(source).Over(target);
151+
152+
target.Value.ShouldBeDefault();
153+
}
154+
155+
[Fact]
156+
public void ShouldMapAnEmptyStringOnToAnEnum()
157+
{
158+
var source = new PublicField<string> { Value = string.Empty };
159+
var result = Mapper.Map(source).OnTo(new PublicProperty<Title>());
160+
161+
result.Value.ShouldBeDefault();
162+
}
163+
144164
[Fact]
145165
public void ShouldMapAnEnumToAnEnum()
146166
{

AgileMapper/DataSources/DataSourceBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ protected DataSourceBase(IQualifiedMember sourceMember, Expression value)
1414
{
1515
}
1616

17-
protected DataSourceBase(IDataSource wrappedDataSource, Expression value = null)
17+
protected DataSourceBase(IDataSource wrappedDataSource, Expression value)
1818
: this(
1919
wrappedDataSource.SourceMember,
2020
wrappedDataSource.Variables,
21-
value ?? wrappedDataSource.Value,
21+
value,
2222
wrappedDataSource.Condition)
2323
{
2424
}

AgileMapper/ObjectPopulation/ObjectMapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public ObjectMapper(
4646
}
4747
}
4848

49+
#region Setup
50+
4951
private ICache<ObjectMapperKeyBase, IRecursionMapperFunc> CreateRecursionMapperFuncs()
5052
{
5153
var cache = MapperData.MapperContext.Cache.CreateNew<ObjectMapperKeyBase, IRecursionMapperFunc>();
@@ -80,8 +82,6 @@ private ICache<ObjectMapperKeyBase, IRecursionMapperFunc> CreateRecursionMapperF
8082
return cache;
8183
}
8284

83-
#region Setup
84-
8585
#endregion
8686

8787
public LambdaExpression MappingLambda { get; }

AgileMapper/TypeConversion/ToEnumConverter.cs

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ public override Expression GetConversion(Expression sourceValue, Type targetEnum
7070
return GetStringValueConversion(
7171
sourceValue,
7272
fallbackValue,
73-
nonNullableSourceType,
7473
nonNullableTargetEnumType);
7574
}
7675

@@ -85,7 +84,7 @@ private Expression GetFlagsEnumConversion(
8584

8685
var enumValueVariable = Expression.Variable(underlyingEnumType, enumTypeName + "Value");
8786
var underlyingTypeDefault = underlyingEnumType.ToDefaultExpression();
88-
var assignEnumValue = Expression.Assign(enumValueVariable, underlyingTypeDefault);
87+
var assignEnumValue = enumValueVariable.AssignTo(underlyingTypeDefault);
8988

9089
if (nonNullableSourceType.IsNumeric())
9190
{
@@ -118,13 +117,14 @@ private Expression GetFlagsEnumConversion(
118117
var enumeratorCurrent = Expression.Property(sourceValuesVariable, "Current");
119118
var stringTrimMethod = typeof(string).GetPublicInstanceMethod("Trim", parameterCount: 0);
120119
var currentTrimmed = Expression.Call(enumeratorCurrent, stringTrimMethod);
121-
var assignLocalVariable = Expression.Assign(localSourceValueVariable, currentTrimmed);
120+
var assignLocalVariable = localSourceValueVariable.AssignTo(currentTrimmed);
122121

123-
var numericTryParseCall = GetNumericTryParseCall(
124-
enumTypeName,
125-
underlyingEnumType,
126-
localSourceValueVariable,
127-
out var sourceNumericValueVariable);
122+
var isNumericTest = GetIsNumericTest(localSourceValueVariable);
123+
124+
var sourceNumericValueVariableName = enumTypeName + underlyingEnumType.Name + "Value";
125+
var sourceNumericValueVariable = Expression.Variable(underlyingEnumType, sourceNumericValueVariableName);
126+
var parsedString = GetStringParseCall(localSourceValueVariable, underlyingEnumType);
127+
var assignNumericVariable = sourceNumericValueVariable.AssignTo(parsedString);
128128

129129
var numericValuePopulationLoop = GetNumericToFlagsEnumPopulationLoop(
130130
nonNullableTargetEnumType,
@@ -136,6 +136,7 @@ private Expression GetFlagsEnumConversion(
136136

137137
var numericValuePopulationBlock = Expression.Block(
138138
new[] { enumValuesVariable },
139+
assignNumericVariable,
139140
assignEnumValues,
140141
numericValuePopulationLoop);
141142

@@ -147,7 +148,7 @@ private Expression GetFlagsEnumConversion(
147148
var assignParsedEnumValue = Expression.OrAssign(enumValueVariable, stringValueConversion);
148149

149150
var assignValidValuesIfPossible = Expression.IfThenElse(
150-
numericTryParseCall,
151+
isNumericTest,
151152
numericValuePopulationBlock,
152153
assignParsedEnumValue);
153154

@@ -187,7 +188,7 @@ private static Expression GetNumericToFlagsEnumConversion(
187188
sourceValue = Expression.Convert(sourceValue, underlyingEnumType);
188189
}
189190

190-
var assignSourceVariable = Expression.Assign(sourceValueVariable, sourceValue);
191+
var assignSourceVariable = sourceValueVariable.AssignTo(sourceValue);
191192

192193
var populationLoop = GetNumericToFlagsEnumPopulationLoop(
193194
nonNullableTargetEnumType,
@@ -227,7 +228,7 @@ private static Expression GetNumericToFlagsEnumPopulationLoop(
227228

228229
var localEnumValueVariable = Expression.Variable(underlyingEnumType, enumTypeName);
229230
var enumeratorCurrent = Expression.Property(enumValuesVariable, "Current");
230-
var assignLocalVariable = Expression.Assign(localEnumValueVariable, enumeratorCurrent);
231+
var assignLocalVariable = localEnumValueVariable.AssignTo(enumeratorCurrent);
231232

232233
var localVariableAndSourceValue = Expression.And(localEnumValueVariable, sourceValueVariable);
233234
var andResultEqualsEnumValue = Expression.Equal(localVariableAndSourceValue, localEnumValueVariable);
@@ -298,21 +299,19 @@ private static Expression GetLoopExitCheck(
298299
return Expression.IfThen(Expression.Not(enumeratorMoveNext), Expression.Break(loopBreakTarget));
299300
}
300301

301-
private static Expression GetNumericTryParseCall(
302-
string enumTypeName,
303-
Type underlyingEnumType,
304-
Expression stringValue,
305-
out ParameterExpression numericValueVariable)
302+
private static Expression GetIsNumericTest(Expression stringValue)
306303
{
307-
var tryParseMethod = underlyingEnumType
308-
.GetPublicStaticMethod("TryParse", parameterCount: 2);
309-
310-
var sourceNumericValueVariableName = enumTypeName + underlyingEnumType.Name + "Value";
311-
numericValueVariable = Expression.Parameter(underlyingEnumType, sourceNumericValueVariableName);
312-
313-
var tryParseCall = Expression.Call(tryParseMethod, stringValue, numericValueVariable);
304+
return Expression.Call(
305+
typeof(char).GetPublicStaticMethod("IsDigit", typeof(string), typeof(int)),
306+
stringValue,
307+
0.ToConstantExpression());
308+
}
314309

315-
return tryParseCall;
310+
public static Expression GetStringParseCall(Expression sourceValue, Type underlyingEnumType)
311+
{
312+
return Expression.Call(
313+
underlyingEnumType.GetPublicStaticMethod("Parse", typeof(string)),
314+
sourceValue);
316315
}
317316

318317
private static Expression GetStringToEnumConversion(
@@ -449,9 +448,16 @@ private static Expression GetNumericToEnumConversion(
449448
return nonNullValidValueOrFallback;
450449
}
451450

452-
private static Expression GetIsValidEnumValueCheck(Type enumType, Expression value)
451+
private static Expression GetIsValidEnumValueCheck(
452+
Type enumType,
453+
Expression value,
454+
Expression validEnumValues = null)
453455
{
454-
var validEnumValues = GetEnumValuesConstant(enumType, value.Type);
456+
if (validEnumValues == null)
457+
{
458+
validEnumValues = GetEnumValuesConstant(enumType, value.Type);
459+
}
460+
455461
var containsMethod = validEnumValues.Type.GetPublicInstanceMethod("Contains");
456462
var containsCall = Expression.Call(validEnumValues, containsMethod, value);
457463

@@ -461,7 +467,6 @@ private static Expression GetIsValidEnumValueCheck(Type enumType, Expression val
461467
private Expression GetStringValueConversion(
462468
Expression sourceValue,
463469
Expression fallbackValue,
464-
Type nonNullableSourceType,
465470
Type nonNullableTargetEnumType)
466471
{
467472
if (sourceValue.Type != typeof(string))
@@ -471,29 +476,57 @@ private Expression GetStringValueConversion(
471476

472477
var underlyingEnumType = Enum.GetUnderlyingType(nonNullableTargetEnumType);
473478

474-
var nameMatchingConversion = GetStringToEnumConversion(
479+
var isNumericTest = GetIsNumericTest(sourceValue);
480+
481+
var numericConversion = GetNumericStringToEnumConversion(
475482
sourceValue,
476483
fallbackValue,
477-
nonNullableTargetEnumType);
484+
nonNullableTargetEnumType,
485+
underlyingEnumType);
478486

479-
var tryParseCall = GetNumericTryParseCall(
480-
GetVariableNameFor(nonNullableTargetEnumType),
481-
underlyingEnumType,
487+
var nameMatchingConversion = GetStringToEnumConversion(
482488
sourceValue,
483-
out var parseResultVariable);
484-
485-
var numericConversion = GetNumericToEnumConversion(
486-
parseResultVariable,
487489
fallbackValue,
488-
nonNullableSourceType,
489490
nonNullableTargetEnumType);
490491

491492
var numericOrNameConversion = Expression.Condition(
492-
tryParseCall,
493+
isNumericTest,
493494
numericConversion,
494495
nameMatchingConversion);
495496

496-
return Expression.Block(new[] { parseResultVariable }, numericOrNameConversion);
497+
var valueIsNullOrEmpty = Expression.Call(
498+
typeof(string).GetPublicStaticMethod("IsNullOrWhiteSpace"),
499+
sourceValue);
500+
501+
var convertedValueOrDefault = Expression.Condition(
502+
valueIsNullOrEmpty,
503+
fallbackValue,
504+
numericOrNameConversion);
505+
506+
return convertedValueOrDefault;
507+
}
508+
509+
private static Expression GetNumericStringToEnumConversion(
510+
Expression sourceValue,
511+
Expression fallbackValue,
512+
Type nonNullableTargetEnumType,
513+
Type underlyingEnumType)
514+
{
515+
var validEnumValues = Enum
516+
.GetValues(nonNullableTargetEnumType)
517+
.Cast<object>()
518+
.Select(v => Convert.ChangeType(v, underlyingEnumType).ToString())
519+
.ToArray()
520+
.ToConstantExpression(typeof(ICollection<string>));
521+
522+
var parsedString = GetStringParseCall(sourceValue, underlyingEnumType);
523+
524+
var validValueOrFallback = Expression.Condition(
525+
GetIsValidEnumValueCheck(nonNullableTargetEnumType, sourceValue, validEnumValues),
526+
parsedString.GetConversionTo(fallbackValue.Type),
527+
fallbackValue);
528+
529+
return validValueOrFallback;
497530
}
498531
}
499532
}

0 commit comments

Comments
 (0)