diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj
index c8106b6e2..32f08d2a8 100644
--- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj
+++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj
@@ -143,6 +143,7 @@
+
@@ -207,6 +208,7 @@
+
diff --git a/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs
index babaa6d3a..0cbb26415 100644
--- a/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs
+++ b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToEnums.cs
@@ -2,40 +2,41 @@
{
using TestClasses;
using Xunit;
+ using static TestClasses.Title;
public class WhenConvertingToEnums
{
[Fact]
public void ShouldMapAByteToAnEnum()
{
- var source = new PublicField { Value = (byte)Title.Dr };
+ var source = new PublicField { Value = (byte)Dr };
var result = Mapper.Map(source).ToANew>();
- result.Value.ShouldBe(Title.Dr);
+ result.Value.ShouldBe(Dr);
}
[Fact]
public void ShouldMapAShortToAnEnum()
{
- var source = new PublicField { Value = (short)Title.Miss };
+ var source = new PublicField { Value = (short)Miss };
var result = Mapper.Map(source).ToANew>();
- result.Value.ShouldBe(Title.Miss);
+ result.Value.ShouldBe(Miss);
}
[Fact]
public void ShouldMapANullableIntToAnEnum()
{
- var source = new PublicProperty { Value = (int)Title.Lady };
+ var source = new PublicProperty { Value = (int)Lady };
var result = Mapper.Map(source).ToANew>();
- result.Value.ShouldBe(Title.Lady);
+ result.Value.ShouldBe(Lady);
}
[Fact]
public void ShouldMapAnIntToAnEnum()
{
- var source = new PublicProperty { Value = (int)Title.Dr };
+ var source = new PublicProperty { Value = (int)Dr };
var result = Mapper.Map(source).ToANew>();
result.Value.ShouldBe((Title)source.Value);
@@ -53,7 +54,7 @@ public void ShouldMapANullNullableIntToAnEnum()
[Fact]
public void ShouldMapALongToAnEnum()
{
- var source = new PublicProperty { Value = (long)Title.Miss };
+ var source = new PublicProperty { Value = (long)Miss };
var result = Mapper.Map(source).ToANew>();
result.Value.ShouldBe((Title)source.Value);
@@ -62,7 +63,7 @@ public void ShouldMapALongToAnEnum()
[Fact]
public void ShouldMapANonMatchingNullableLongToANullableEnum()
{
- var source = new PublicProperty { Value = (long)Title.Earl };
+ var source = new PublicProperty { Value = (long)Earl };
var result = Mapper.Map(source).ToANew>();
result.Value.ShouldBeNull();
@@ -98,28 +99,28 @@ public void ShouldMapAMatchingNullableCharacterOnToANullableEnum()
[Fact]
public void ShouldMapAMatchingStringOnToAnEnum()
{
- var source = new PublicField { Value = Title.Mrs.ToString() };
+ var source = new PublicField { Value = Mrs.ToString() };
var result = Mapper.Map(source).OnTo(new PublicProperty());
- result.Value.ShouldBe(Title.Mrs);
+ result.Value.ShouldBe(Mrs);
}
[Fact]
public void ShouldMapAMatchingStringOnToAnEnumCaseInsensitively()
{
- var source = new PublicField { Value = Title.Miss.ToString().ToLowerInvariant() };
+ var source = new PublicField { Value = Miss.ToString().ToLowerInvariant() };
var result = Mapper.Map(source).OnTo(new PublicProperty());
- result.Value.ShouldBe(Title.Miss);
+ result.Value.ShouldBe(Miss);
}
[Fact]
public void ShouldMapAMatchingNumericStringOverAnEnum()
{
- var source = new PublicField { Value = ((int)Title.Dr).ToString() };
+ var source = new PublicField { Value = ((int)Dr).ToString() };
var result = Mapper.Map(source).Over(new PublicProperty());
- result.Value.ShouldBe(Title.Dr);
+ result.Value.ShouldBe(Dr);
}
[Fact]
@@ -146,13 +147,13 @@ public void ShouldMapAnEnumToAnEnum()
var source = new PublicProperty { Value = TitleShortlist.Mrs };
var result = Mapper.Map(source).ToANew>();
- result.Value.ShouldBe(Title.Mrs);
+ result.Value.ShouldBe(Mrs);
}
[Fact]
public void ShouldMapANonMatchingEnumToANullableEnum()
{
- var source = new PublicProperty { Value = Title.Lord };
+ var source = new PublicProperty { Value = Lord };
var result = Mapper.Map(source).ToANew>();
result.Value.ShouldBeNull();
@@ -161,10 +162,10 @@ public void ShouldMapANonMatchingEnumToANullableEnum()
[Fact]
public void ShouldMapANullableEnumToAnEnum()
{
- var source = new PublicProperty { Value = Title.Dr };
+ var source = new PublicProperty { Value = Dr };
var result = Mapper.Map(source).ToANew>();
- result.Value.ShouldBe(Title.Dr);
+ result.Value.ShouldBe(Dr);
}
[Fact]
@@ -206,7 +207,7 @@ public void ShouldMapAnObjectEnumMemberValueToAnNullableEnum()
[Fact]
public void ShouldMapAnObjectNullableEnumMemberValueToAnNullableEnum()
{
- var source = new PublicProperty { Value = (Title?)Title.Mr };
+ var source = new PublicProperty { Value = (Title?)Mr };
var result = Mapper.Map(source).ToANew>();
result.Value.ShouldBe(TitleShortlist.Mr);
@@ -225,20 +226,20 @@ public void ShouldMapEnumsConditionally()
.If((ptf, pf) => ptf.Value1 == null)
.Map((ptf, pf) => ptf.Value2).To(pf => pf.Value)
.And
- .If((ptf, pf) => Title.Duke == ptf.Value1)
+ .If((ptf, pf) => Duke == ptf.Value1)
.Map(TitleShortlist.Other).To(pf => pf.Value);
- var nonNullSource = new PublicTwoFields { Value1 = Title.Dr, Value2 = Title.Count };
+ var nonNullSource = new PublicTwoFields { Value1 = Dr, Value2 = Count };
var nonNullResult = mapper.Map(nonNullSource).ToANew>();
nonNullResult.Value.ShouldBe(TitleShortlist.Dr);
- var nullSource = new PublicTwoFields { Value1 = null, Value2 = Title.Mrs };
+ var nullSource = new PublicTwoFields { Value1 = null, Value2 = Mrs };
var nullResult = mapper.Map(nullSource).ToANew>();
nullResult.Value.ShouldBe(TitleShortlist.Mrs);
- var dukeSource = new PublicTwoFields { Value1 = Title.Duke };
+ var dukeSource = new PublicTwoFields { Value1 = Duke };
var dukeResult = mapper.Map(dukeSource).ToANew>();
dukeResult.Value.ShouldBe(TitleShortlist.Other);
diff --git a/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToFlagsEnums.cs b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToFlagsEnums.cs
new file mode 100644
index 000000000..65f2e7582
--- /dev/null
+++ b/AgileMapper.UnitTests/SimpleTypeConversion/WhenConvertingToFlagsEnums.cs
@@ -0,0 +1,84 @@
+namespace AgileObjects.AgileMapper.UnitTests.SimpleTypeConversion
+{
+ using TestClasses;
+ using Xunit;
+ using static TestClasses.Status;
+
+ public class WhenConvertingToFlagsEnums
+ {
+ [Fact]
+ public void ShouldMapASingleValueByteToAFlagsEnum()
+ {
+ var source = new PublicField { Value = (byte)InProgress };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe(InProgress);
+ }
+
+ [Fact]
+ public void ShouldMapAMultiValueShortToAFlagsEnum()
+ {
+ var source = new PublicField { Value = (short)(InProgress | Assigned) };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.HasFlag(InProgress).ShouldBeTrue();
+ result.Value.HasFlag(Assigned).ShouldBeTrue();
+ result.Value.HasFlag(Cancelled).ShouldBeFalse();
+ result.Value.ShouldBe(InProgress | Assigned);
+ }
+
+ [Fact]
+ public void ShouldMapAMultiValueNullableIntToAFlagsEnum()
+ {
+ var source = new PublicProperty { Value = (int)(New | Completed | Cancelled) };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe(New | Completed | Cancelled);
+ }
+
+ [Fact]
+ public void ShouldMapANullNullableIntToANullableFlagsEnum()
+ {
+ var source = new PublicProperty { Value = default(int?) };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBeNull();
+ }
+
+ [Fact]
+ public void ShouldMapASingleValueLongToANullableFlagsEnum()
+ {
+ var source = new PublicProperty { Value = (long)Removed };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe(Removed);
+ }
+
+ [Fact]
+ public void ShouldMapAMultiValueNumericCharacterToAFlagsEnum()
+ {
+ var source = new PublicProperty { Value = '9' };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe(New | Completed);
+ }
+
+ [Fact]
+ public void ShouldMapASingleValueNumericStringToAFlagsEnum()
+ {
+ var source = new PublicProperty { Value = "4" };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe((Status)4);
+ }
+
+ [Fact]
+ public void ShouldMapAMultiValueMixedStringToAFlagsEnum()
+ {
+ var source = new PublicProperty { Value = "9, InProgress, 4" };
+ var result = Mapper.Map(source).ToANew>();
+
+ result.Value.ShouldBe(New | InProgress | Completed);
+ }
+ }
+}
diff --git a/AgileMapper.UnitTests/TestClasses/Status.cs b/AgileMapper.UnitTests/TestClasses/Status.cs
new file mode 100644
index 000000000..069b778aa
--- /dev/null
+++ b/AgileMapper.UnitTests/TestClasses/Status.cs
@@ -0,0 +1,15 @@
+namespace AgileObjects.AgileMapper.UnitTests.TestClasses
+{
+ using System;
+
+ [Flags]
+ public enum Status
+ {
+ New = 1,
+ Assigned = 2,
+ InProgress = 4,
+ Completed = 8,
+ Cancelled = 16,
+ Removed = 32
+ }
+}
\ No newline at end of file
diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs
index 138f719f6..2c933b3ed 100644
--- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs
+++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableSourcePopulationLoopData.cs
@@ -10,9 +10,6 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables
internal class EnumerableSourcePopulationLoopData : IPopulationLoopData
{
- private static readonly MethodInfo _enumeratorMoveNextMethod = typeof(IEnumerator).GetPublicInstanceMethod("MoveNext");
- private static readonly MethodInfo _disposeMethod = typeof(IDisposable).GetPublicInstanceMethod("Dispose");
-
private readonly Expression _enumerableSubject;
private readonly MethodInfo _getEnumeratorMethod;
private readonly ParameterExpression _enumerator;
@@ -34,7 +31,7 @@ public EnumerableSourcePopulationLoopData(
_enumerator = Expression.Variable(_getEnumeratorMethod.ReturnType, "enumerator");
ContinueLoopTarget = Expression.Label(typeof(void), "Continue");
- LoopExitCheck = Expression.Not(Expression.Call(_enumerator, _enumeratorMoveNextMethod));
+ LoopExitCheck = Expression.Not(Expression.Call(_enumerator, typeof(IEnumerator).GetPublicInstanceMethod("MoveNext")));
SourceElement = Expression.Property(_enumerator, "Current");
}
@@ -69,7 +66,9 @@ public BlockExpression GetLoopBlock(
var enumeratorAssignment = _enumerator.AssignTo(enumeratorValue);
- Expression finallyClause = Expression.Call(_enumerator, _disposeMethod);
+ Expression finallyClause = Expression.Call(
+ _enumerator,
+ typeof(IDisposable).GetPublicInstanceMethod("Dispose"));
if (finallyClauseFactory != null)
{
diff --git a/AgileMapper/TypeConversion/ToEnumConverter.cs b/AgileMapper/TypeConversion/ToEnumConverter.cs
index 2976fc397..4b0c68c20 100644
--- a/AgileMapper/TypeConversion/ToEnumConverter.cs
+++ b/AgileMapper/TypeConversion/ToEnumConverter.cs
@@ -1,6 +1,7 @@
namespace AgileObjects.AgileMapper.TypeConversion
{
using System;
+ using System.Collections;
using System.Linq.Expressions;
using Extensions.Internal;
using NetStandardPolyfills;
@@ -22,19 +23,97 @@ public override bool CanConvert(Type nonNullableSourceType, Type nonNullableTarg
}
return nonNullableSourceType.IsEnum() ||
- (nonNullableSourceType == typeof(string)) ||
- (nonNullableSourceType == typeof(object)) ||
- (nonNullableSourceType == typeof(char)) ||
- nonNullableSourceType.IsNumeric();
+ (nonNullableSourceType == typeof(string)) ||
+ (nonNullableSourceType == typeof(object)) ||
+ (nonNullableSourceType == typeof(char)) ||
+ nonNullableSourceType.IsNumeric();
}
- public override Expression GetConversion(Expression sourceValue, Type targetType)
+ public override Expression GetConversion(Expression sourceValue, Type targetEnumType)
+ {
+ var fallbackValue = targetEnumType.ToDefaultExpression();
+ var nonNullableSourceType = sourceValue.Type.GetNonNullableType();
+ var nonNullableTargetEnumType = targetEnumType.GetNonNullableType();
+
+ if (nonNullableTargetEnumType.HasAttribute())
+ {
+ return GetFlagsEnumConversion(
+ sourceValue,
+ fallbackValue,
+ nonNullableSourceType,
+ nonNullableTargetEnumType);
+ }
+
+ if (nonNullableSourceType.IsNumeric())
+ {
+ return GetNumericToEnumConversion(
+ sourceValue,
+ fallbackValue,
+ nonNullableSourceType,
+ nonNullableTargetEnumType);
+ }
+
+ return GetTryParseConversion(
+ sourceValue,
+ fallbackValue,
+ nonNullableSourceType,
+ nonNullableTargetEnumType);
+ }
+
+ private static Expression GetNumericToEnumConversion(
+ Expression sourceValue,
+ Expression fallbackValue,
+ Type nonNullableSourceType,
+ Type nonNullableTargetEnumType)
+ {
+ Expression convertedNumericValue = Expression.Convert(sourceValue, nonNullableTargetEnumType);
+
+ if (nonNullableTargetEnumType != fallbackValue.Type)
+ {
+ convertedNumericValue = convertedNumericValue.GetConversionTo(fallbackValue.Type);
+ }
+
+ var definedValueOrFallback = Expression.Condition(
+ GetEnumIsDefinedCall(nonNullableTargetEnumType, sourceValue),
+ convertedNumericValue,
+ fallbackValue);
+
+ if (sourceValue.Type == nonNullableSourceType)
+ {
+ return definedValueOrFallback;
+ }
+
+ var nonNullDefinedValueOrFallback = Expression.Condition(
+ sourceValue.GetIsNotDefaultComparison(),
+ definedValueOrFallback,
+ fallbackValue);
+
+ return nonNullDefinedValueOrFallback;
+ }
+
+ private static Expression GetEnumIsDefinedCall(Type enumType, Expression value)
+ {
+ var convertedValue = value
+ .GetConversionTo(Enum.GetUnderlyingType(enumType))
+ .GetConversionTo(typeof(object));
+
+ return Expression.Call(
+ typeof(Enum).GetPublicStaticMethod("IsDefined"),
+ enumType.ToConstantExpression(),
+ convertedValue);
+ }
+
+ private Expression GetTryParseConversion(
+ Expression sourceValue,
+ Expression fallbackValue,
+ Type nonNullableSourceType,
+ Type nonNullableTargetEnumType)
{
bool sourceIsAnEnum;
if (sourceValue.Type != typeof(string))
{
- sourceIsAnEnum = sourceValue.Type.GetNonNullableType().IsEnum();
+ sourceIsAnEnum = nonNullableSourceType.IsEnum();
sourceValue = _toStringConverter.GetConversion(sourceValue);
}
else
@@ -42,27 +121,33 @@ public override Expression GetConversion(Expression sourceValue, Type targetType
sourceIsAnEnum = false;
}
- var nonNullableEnumType = targetType.GetNonNullableType();
+ var tryParseCall = GetTryParseCall(sourceValue, nonNullableTargetEnumType, out var valueVariable);
+ var parseSuccessBranch = GetParseSuccessBranch(sourceIsAnEnum, valueVariable, fallbackValue);
+
+ var parsedValueOrDefault = Expression.Condition(tryParseCall, parseSuccessBranch, fallbackValue);
+ var tryParseBlock = Expression.Block(new[] { valueVariable }, parsedValueOrDefault);
+
+ return tryParseBlock;
+ }
+ private static Expression GetTryParseCall(
+ Expression sourceValue,
+ Type nonNullableTargetEnumType,
+ out ParameterExpression valueVariable)
+ {
var tryParseMethod = typeof(Enum)
.GetPublicStaticMethod("TryParse", parameterCount: 3)
- .MakeGenericMethod(nonNullableEnumType);
+ .MakeGenericMethod(nonNullableTargetEnumType);
- var valueVariable = Expression.Variable(nonNullableEnumType, nonNullableEnumType.GetShortVariableName());
+ valueVariable = Expression.Variable(
+ nonNullableTargetEnumType,
+ nonNullableTargetEnumType.GetShortVariableName());
- var tryParseCall = Expression.Call(
+ return Expression.Call(
tryParseMethod,
sourceValue,
true.ToConstantExpression(), // <- IgnoreCase
valueVariable);
-
- var defaultValue = targetType.ToDefaultExpression();
- var parseSuccessBranch = GetParseSuccessBranch(sourceIsAnEnum, valueVariable, defaultValue);
-
- var parsedValueOrDefault = Expression.Condition(tryParseCall, parseSuccessBranch, defaultValue);
- var tryParseBlock = Expression.Block(new[] { valueVariable }, parsedValueOrDefault);
-
- return tryParseBlock;
}
private static Expression GetParseSuccessBranch(
@@ -79,15 +164,213 @@ private static Expression GetParseSuccessBranch(
return successfulParseReturnValue;
}
- var isDefinedCall = Expression.Call(
- null,
- typeof(Enum).GetPublicStaticMethod("IsDefined"),
- valueVariable.Type.ToConstantExpression(),
- valueVariable.GetConversionTo(typeof(object)));
-
- var definedValueOrDefault = Expression.Condition(isDefinedCall, successfulParseReturnValue, defaultValue);
+ var definedValueOrDefault = Expression.Condition(
+ GetEnumIsDefinedCall(valueVariable.Type, valueVariable),
+ successfulParseReturnValue,
+ defaultValue);
return definedValueOrDefault;
}
+
+ private Expression GetFlagsEnumConversion(
+ Expression sourceValue,
+ Expression fallbackValue,
+ Type nonNullableSourceType,
+ Type nonNullableTargetEnumType)
+ {
+ var enumTypeName = nonNullableTargetEnumType.GetVariableNameInCamelCase();
+ var underlyingEnumType = Enum.GetUnderlyingType(nonNullableTargetEnumType);
+
+ var enumValueVariable = Expression.Variable(underlyingEnumType, enumTypeName + "Value");
+ var assignEnumValue = Expression.Assign(enumValueVariable, underlyingEnumType.ToDefaultExpression());
+
+ if (nonNullableSourceType.IsNumeric())
+ {
+ return GetNumericToFlagsEnumConversion(
+ sourceValue,
+ fallbackValue,
+ nonNullableTargetEnumType,
+ enumTypeName,
+ enumValueVariable,
+ assignEnumValue);
+ }
+
+ if (sourceValue.Type != typeof(string))
+ {
+ sourceValue = _toStringConverter.GetConversion(sourceValue);
+ }
+
+ var sourceValuesVariable = GetEnumValuesVariable(enumTypeName);
+
+ var splitSourceValueCall = Expression.Call(
+ sourceValue,
+ typeof(string).GetPublicInstanceMethod("Split", parameterCount: 1),
+ Expression.NewArrayInit(typeof(char), ','.ToConstantExpression()));
+
+ var assignSourceValues = GetValuesEnumeratorAssignment(sourceValuesVariable, splitSourceValueCall);
+
+ var ifNotMoveNextBreak = GetLoopExitCheck(sourceValuesVariable, out var loopBreakTarget);
+
+ var localSourceValueVariable = Expression.Variable(typeof(string), enumTypeName);
+ var enumeratorCurrent = Expression.Property(sourceValuesVariable, "Current");
+ var currentToString = Expression.Call(enumeratorCurrent, typeof(object).GetPublicInstanceMethod("ToString"));
+ var stringTrimMethod = typeof(string).GetPublicInstanceMethod("Trim", parameterCount: 0);
+ var currentTrimmed = Expression.Call(currentToString, stringTrimMethod);
+ var assignLocalVariable = Expression.Assign(localSourceValueVariable, currentTrimmed);
+
+ var sourceNumericValueVariableName = enumTypeName + underlyingEnumType.Name + "Value";
+ var sourceNumericValueVariable = Expression.Parameter(underlyingEnumType, sourceNumericValueVariableName);
+
+ var numericTryParseCall = Expression.Call(
+ underlyingEnumType.GetPublicStaticMethod("TryParse", parameterCount: 2),
+ localSourceValueVariable,
+ sourceNumericValueVariable);
+
+ var numericValuePopulationLoop = GetNumericToFlagsEnumPopulationLoop(
+ nonNullableTargetEnumType,
+ enumTypeName,
+ enumValueVariable,
+ sourceNumericValueVariable,
+ out var enumValuesVariable,
+ out var assignEnumValues);
+
+ var numericValuePopulationBlock = Expression.Block(
+ new[] { enumValuesVariable },
+ assignEnumValues,
+ numericValuePopulationLoop);
+
+ var stringHasValidValueCheck = GetTryParseCall(
+ localSourceValueVariable,
+ nonNullableTargetEnumType,
+ out var sourceEnumValueVariable);
+
+ var convertedEnumValue = sourceEnumValueVariable.GetConversionTo(enumValueVariable.Type);
+ var assignParsedEnumValue = Expression.OrAssign(enumValueVariable, convertedEnumValue);
+
+ var assignValidValuesIfPossible = Expression.IfThenElse(
+ numericTryParseCall,
+ numericValuePopulationBlock,
+ Expression.IfThen(stringHasValidValueCheck, assignParsedEnumValue));
+
+ var loopBody = Expression.Block(
+ new[] { localSourceValueVariable, sourceNumericValueVariable, sourceEnumValueVariable },
+ ifNotMoveNextBreak,
+ assignLocalVariable,
+ assignValidValuesIfPossible);
+
+ var populationBlock = Expression.Block(
+ new[] { sourceValuesVariable, enumValueVariable },
+ assignEnumValue,
+ assignSourceValues,
+ Expression.Loop(loopBody, loopBreakTarget),
+ enumValueVariable.GetConversionTo(fallbackValue.Type));
+
+ return populationBlock;
+ }
+
+ private static Expression GetNumericToFlagsEnumConversion(
+ Expression sourceValue,
+ Expression fallbackValue,
+ Type nonNullableTargetEnumType,
+ string enumTypeName,
+ ParameterExpression enumValueVariable,
+ Expression assignEnumValue)
+ {
+ var underlyingEnumType = enumValueVariable.Type;
+
+ var sourceValueVariable = Expression.Variable(underlyingEnumType, enumTypeName + "Source");
+
+ if (sourceValue.Type != underlyingEnumType)
+ {
+ sourceValue = Expression.Convert(sourceValue, underlyingEnumType);
+ }
+
+ var assignSourceVariable = Expression.Assign(sourceValueVariable, sourceValue);
+
+ var populationLoop = GetNumericToFlagsEnumPopulationLoop(
+ nonNullableTargetEnumType,
+ enumTypeName,
+ enumValueVariable,
+ sourceValueVariable,
+ out var enumValuesVariable,
+ out var assignEnumValues);
+
+ var populationBlock = Expression.Block(
+ new[] { sourceValueVariable, enumValueVariable, enumValuesVariable },
+ assignSourceVariable,
+ assignEnumValue,
+ assignEnumValues,
+ populationLoop,
+ enumValueVariable.GetConversionTo(fallbackValue.Type));
+
+ return populationBlock;
+ }
+
+ private static Expression GetNumericToFlagsEnumPopulationLoop(
+ Type nonNullableTargetEnumType,
+ string enumTypeName,
+ Expression enumValueVariable,
+ Expression sourceValueVariable,
+ out ParameterExpression enumValuesVariable,
+ out Expression assignEnumValues)
+ {
+ var underlyingEnumType = enumValueVariable.Type;
+ enumValuesVariable = GetEnumValuesVariable(enumTypeName);
+
+ var enumGetValuesCall = Expression.Call(
+ typeof(Enum).GetPublicStaticMethod("GetValues"),
+ nonNullableTargetEnumType.ToConstantExpression());
+
+ assignEnumValues = GetValuesEnumeratorAssignment(enumValuesVariable, enumGetValuesCall);
+
+ var ifNotMoveNextBreak = GetLoopExitCheck(enumValuesVariable, out var loopBreakTarget);
+
+ var localEnumValueVariable = Expression.Variable(underlyingEnumType, enumTypeName);
+ var enumeratorCurrent = Expression.Property(enumValuesVariable, "Current");
+ var currentAsEnumType = Expression.Convert(enumeratorCurrent, underlyingEnumType);
+ var assignLocalVariable = Expression.Assign(localEnumValueVariable, currentAsEnumType);
+
+ var localVariableAndSourceValue = Expression.And(localEnumValueVariable, sourceValueVariable);
+ var andResultEqualsEnumValue = Expression.Equal(localVariableAndSourceValue, localEnumValueVariable);
+
+ var ifAndResultMatchesAssign = Expression.IfThen(
+ andResultEqualsEnumValue,
+ Expression.OrAssign(enumValueVariable, localEnumValueVariable));
+
+ var loopBody = Expression.Block(
+ new[] { localEnumValueVariable },
+ ifNotMoveNextBreak,
+ assignLocalVariable,
+ ifAndResultMatchesAssign);
+
+ return Expression.Loop(loopBody, loopBreakTarget);
+ }
+
+ private static ParameterExpression GetEnumValuesVariable(string enumTypeName)
+ => Expression.Variable(typeof(IEnumerator), enumTypeName + "Values");
+
+ private static Expression GetValuesEnumeratorAssignment(
+ Expression enumValuesVariable,
+ Expression enumeratedValues)
+ {
+ var getValuesEnumeratorCall = Expression.Call(
+ enumeratedValues,
+ enumeratedValues.Type.GetPublicInstanceMethod("GetEnumerator"));
+
+ return enumValuesVariable.AssignTo(getValuesEnumeratorCall);
+ }
+
+ private static Expression GetLoopExitCheck(
+ Expression valuesEnumerator,
+ out LabelTarget loopBreakTarget)
+ {
+ var enumeratorMoveNext = Expression.Call(
+ valuesEnumerator,
+ valuesEnumerator.Type.GetPublicInstanceMethod("MoveNext"));
+
+ loopBreakTarget = Expression.Label();
+
+ return Expression.IfThen(Expression.Not(enumeratorMoveNext), Expression.Break(loopBreakTarget));
+ }
}
}
\ No newline at end of file