using System; using System.Collections.Concurrent; using System.Linq; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; namespace Xamarin.PropertyEditing.Reflection { public class ReflectionEnumPropertyInfo : ReflectionPropertyInfo, IHavePredefinedValues { public ReflectionEnumPropertyInfo (PropertyInfo propertyInfo) : base (propertyInfo) { string[] names = Enum.GetNames (propertyInfo.PropertyType); Array values = Enum.GetValues (propertyInfo.PropertyType); var predefinedValues = new Dictionary (names.Length); for (int i = 0; i < names.Length; i++) { predefinedValues.Add (names[i], (T)values.GetValue (i)); } PredefinedValues = predefinedValues; FlagsAttribute flags = PropertyInfo.PropertyType.GetCustomAttribute (); if (IsValueCombinable = flags != null) { DynamicBuilder.RequestOrOperator (); DynamicBuilder.RequestHasFlagMethod (); DynamicBuilder.RequestCaster> (); } } public bool IsConstrainedToPredefined => true; public bool IsValueCombinable { get; } public IReadOnlyDictionary PredefinedValues { get; } #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public override async Task SetValueAsync (object target, TValue value) { IReadOnlyList values = value as IReadOnlyList; if (values != null) { if (!IsValueCombinable) throw new ArgumentException ("Can not set a combined value on a non-combinable type", nameof(value)); Func or = DynamicBuilder.GetOrOperator (); T realValue = values.Count > 0 ? values[0] : default(T); for (int i = 1; i < values.Count; i++) { realValue = or (realValue, values[i]); } PropertyInfo.SetValue (target, realValue); } else { object convertedValue = Enum.ToObject (PropertyInfo.PropertyType, value); PropertyInfo.SetValue (target, convertedValue); } } public override async Task GetValueAsync (object target) { if (typeof(TValue) == typeof(IReadOnlyList)) { T realValue = (T)PropertyInfo.GetValue (target); Func hasFlag = DynamicBuilder.GetHasFlagMethod (); List values = new List (); foreach (T value in PredefinedValues.Values) { if (hasFlag (realValue, value)) values.Add (value); } Func caster = DynamicBuilder.GetCaster (); return caster (values); } return (TValue) PropertyInfo.GetValue (target); } } #pragma warning restore CS1998 internal static class DynamicBuilder { public static void RequestCaster () { GetOrAddCaster (); } public static void RequestOrOperator () { GetOrAddOrOperator (); } public static void RequestHasFlagMethod () { GetOrAddHasFlagMethod (); } public static Func GetOrOperator () { Task result = GetOrAddOrOperator (); return (Func) result.Result; } public static Func GetHasFlagMethod () { Task result = GetOrAddHasFlagMethod (); return (Func) result.Result; } public static Func GetCaster () { Task result = GetOrAddCaster (); return (Func) result.Result; } private static readonly ConcurrentDictionary> Casters = new ConcurrentDictionary> (); private static readonly ConcurrentDictionary> OrOperators = new ConcurrentDictionary> (); private static readonly ConcurrentDictionary> HasFlagsMethods = new ConcurrentDictionary> (); private static Task GetOrAddCaster () { return Casters.GetOrAdd (typeof(T), t => { return Task.Run (() => { var arg = Expression.Parameter (typeof(object)); return Expression.Lambda (Expression.Convert (arg, typeof(T)), arg).Compile (); }); }); } private static Task GetOrAddHasFlagMethod () { return HasFlagsMethods.GetOrAdd (typeof(T), t => { return Task.Run (() => { var left = Expression.Parameter (typeof(T)); var right = Expression.Parameter (typeof(T)); var hasFlag = Expression.Equal (Expression.And (left, right), right); return Expression.Lambda (hasFlag, left, right).Compile(); }); }); } private static Task GetOrAddOrOperator () { return OrOperators.GetOrAdd (typeof(T), t => { return Task.Run (() => { var left = Expression.Parameter (typeof(T)); var right = Expression.Parameter (typeof(T)); var or = Expression.Or (left, right); return Expression.Lambda (or, left, right).Compile (); }); }); } } }