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<string> { Value = Title.Miss.ToString().ToLowerInvariant() }; + var source = new PublicField<string> { Value = Miss.ToString().ToLowerInvariant() }; var result = Mapper.Map(source).OnTo(new PublicProperty<Title>()); - result.Value.ShouldBe(Title.Miss); + result.Value.ShouldBe(Miss); } [Fact] public void ShouldMapAMatchingNumericStringOverAnEnum() { - var source = new PublicField<string> { Value = ((int)Title.Dr).ToString() }; + var source = new PublicField<string> { Value = ((int)Dr).ToString() }; var result = Mapper.Map(source).Over(new PublicProperty<Title>()); - result.Value.ShouldBe(Title.Dr); + result.Value.ShouldBe(Dr); } [Fact] @@ -146,13 +147,13 @@ public void ShouldMapAnEnumToAnEnum() var source = new PublicProperty<TitleShortlist> { Value = TitleShortlist.Mrs }; var result = Mapper.Map(source).ToANew<PublicProperty<Title>>(); - result.Value.ShouldBe(Title.Mrs); + result.Value.ShouldBe(Mrs); } [Fact] public void ShouldMapANonMatchingEnumToANullableEnum() { - var source = new PublicProperty<Title> { Value = Title.Lord }; + var source = new PublicProperty<Title> { Value = Lord }; var result = Mapper.Map(source).ToANew<PublicProperty<TitleShortlist?>>(); result.Value.ShouldBeNull(); @@ -161,10 +162,10 @@ public void ShouldMapANonMatchingEnumToANullableEnum() [Fact] public void ShouldMapANullableEnumToAnEnum() { - var source = new PublicProperty<Title?> { Value = Title.Dr }; + var source = new PublicProperty<Title?> { Value = Dr }; var result = Mapper.Map(source).ToANew<PublicProperty<Title>>(); - 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<object> { Value = (Title?)Title.Mr }; + var source = new PublicProperty<object> { Value = (Title?)Mr }; var result = Mapper.Map(source).ToANew<PublicProperty<TitleShortlist>>(); 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<Title?, Title> { Value1 = Title.Dr, Value2 = Title.Count }; + var nonNullSource = new PublicTwoFields<Title?, Title> { Value1 = Dr, Value2 = Count }; var nonNullResult = mapper.Map(nonNullSource).ToANew<PublicField<TitleShortlist>>(); nonNullResult.Value.ShouldBe(TitleShortlist.Dr); - var nullSource = new PublicTwoFields<Title?, Title> { Value1 = null, Value2 = Title.Mrs }; + var nullSource = new PublicTwoFields<Title?, Title> { Value1 = null, Value2 = Mrs }; var nullResult = mapper.Map(nullSource).ToANew<PublicField<TitleShortlist>>(); nullResult.Value.ShouldBe(TitleShortlist.Mrs); - var dukeSource = new PublicTwoFields<Title?, Title> { Value1 = Title.Duke }; + var dukeSource = new PublicTwoFields<Title?, Title> { Value1 = Duke }; var dukeResult = mapper.Map(dukeSource).ToANew<PublicField<TitleShortlist>>(); 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<byte> { Value = (byte)InProgress }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + result.Value.ShouldBe(InProgress); + } + + [Fact] + public void ShouldMapAMultiValueShortToAFlagsEnum() + { + var source = new PublicField<short> { Value = (short)(InProgress | Assigned) }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + 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<int?> { Value = (int)(New | Completed | Cancelled) }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + result.Value.ShouldBe(New | Completed | Cancelled); + } + + [Fact] + public void ShouldMapANullNullableIntToANullableFlagsEnum() + { + var source = new PublicProperty<int?> { Value = default(int?) }; + var result = Mapper.Map(source).ToANew<PublicField<Status?>>(); + + result.Value.ShouldBeNull(); + } + + [Fact] + public void ShouldMapASingleValueLongToANullableFlagsEnum() + { + var source = new PublicProperty<long> { Value = (long)Removed }; + var result = Mapper.Map(source).ToANew<PublicField<Status?>>(); + + result.Value.ShouldBe(Removed); + } + + [Fact] + public void ShouldMapAMultiValueNumericCharacterToAFlagsEnum() + { + var source = new PublicProperty<char> { Value = '9' }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + result.Value.ShouldBe(New | Completed); + } + + [Fact] + public void ShouldMapASingleValueNumericStringToAFlagsEnum() + { + var source = new PublicProperty<string> { Value = "4" }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + result.Value.ShouldBe((Status)4); + } + + [Fact] + public void ShouldMapAMultiValueMixedStringToAFlagsEnum() + { + var source = new PublicProperty<string> { Value = "9, InProgress, 4" }; + var result = Mapper.Map(source).ToANew<PublicField<Status>>(); + + 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<FlagsAttribute>()) + { + 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