Эх сурвалжийг харах

Fix build error for missing method Enum.GetValues() on some platforms

csharptest 10 жил өмнө
parent
commit
1e29e701a7

+ 7 - 21
src/ProtocolBuffers.Serialization/AbstractReader.cs

@@ -469,23 +469,12 @@ namespace Google.ProtocolBuffers.Serialization
             rawValue = null;
             if (ReadEnum(ref rawValue))
             {
-                if (Enum.IsDefined(typeof(T), rawValue))
+                if (!EnumParser<T>.TryConvert(rawValue, ref value))
                 {
-                    if (rawValue is int)
-                    {
-                        value = (T) rawValue;
-                    }
-                    else if (rawValue is string)
-                    {
-                        value = (T) Enum.Parse(typeof(T), (string) rawValue, false);
-                    }
-                    else
-                    {
-                        value = default(T);
-                        return false;
-                    }
-                    return true;
+                    value = default(T);
+                    return false;
                 }
+                return true;
             }
             return false;
         }
@@ -560,13 +549,10 @@ namespace Google.ProtocolBuffers.Serialization
             {
                 foreach (object rawValue in array)
                 {
-                    if (rawValue is int)
-                    {
-                        list.Add((T) rawValue);
-                    }
-                    else if (rawValue is string)
+                    T val = default(T);
+                    if (EnumParser<T>.TryConvert(rawValue, ref val))
                     {
-                        list.Add((T) Enum.Parse(typeof(T), (string) rawValue, false));
+                        list.Add(val);
                     }
                     else
                     {

+ 1 - 80
src/ProtocolBuffers/CodedInputStream.cs

@@ -482,7 +482,7 @@ namespace Google.ProtocolBuffers
             where T : struct, IComparable, IFormattable
         {
             int number = (int) ReadRawVarint32();
-            if (EnumHelper<T>.TryConvert(number, ref value))
+            if (EnumParser<T>.TryConvert(number, ref value))
             {
                 unknown = null;
                 return true;
@@ -1860,84 +1860,5 @@ namespace Google.ProtocolBuffers
         }
 
         #endregion
-
-        /// <summary>
-        /// Helper class to make parsing enums faster.
-        /// </summary>
-        private static class EnumHelper<T> where T : struct
-        {
-            /// <summary>
-            /// We use the array form if all values are in the range [0, LimitForArray),
-            /// otherwise we build a dictionary.
-            /// </summary>
-            private const int LimitForArray = 32;
-            // Only one of these will be populated.
-            private static readonly Dictionary<int, T> dictionary;
-            private static readonly T?[] values;
-
-            static EnumHelper()
-            {
-                // It will actually be a T[], but the CLR will let us convert.
-                int[] array = (int[]) Enum.GetValues(typeof (T));
-                if (array.Length == 0)
-                {
-                    // Empty enum; model with an empty values array.
-                    values = new T?[0];
-                    return;
-                }
-                int min = int.MaxValue;
-                int max = int.MinValue;
-                foreach (int number in array)
-                {
-                    min = Math.Min(number, min);
-                    max = Math.Max(number, max);
-                }
-                if (min >= 0 && max < LimitForArray)
-                {
-                    values = new T?[max + 1];
-                    foreach (int number in array)
-                    {
-                        values[number] = (T)(object)number;
-                    }
-                }
-                else
-                {
-                    dictionary = new Dictionary<int, T>();
-                    foreach (int number in array)
-                    {
-                        dictionary[number] = (T)(object)number;
-                    }
-                }
-            }
-
-            /// <summary>
-            /// Tries to convert an integer to its enum representation. This would take an out parameter,
-            /// but the caller uses ref, so this approach is simpler.
-            /// </summary>
-            internal static bool TryConvert(int number, ref T value)
-            {
-                if (values != null)
-                {
-                    if (number < 0 || number >= values.Length)
-                    {
-                        return false;
-                    }
-                    T? maybeValue = values[number];
-                    if (maybeValue != null)
-                    {
-                        value = maybeValue.Value;
-                        return true;
-                    }
-                    return false;
-                }
-                T converted;
-                if (dictionary.TryGetValue(number, out converted))
-                {
-                    value = converted;
-                    return true;
-                }
-                return false;
-            }
-        }
     }
 }

+ 120 - 6
src/ProtocolBuffers/EnumLite.cs

@@ -95,26 +95,140 @@ namespace Google.ProtocolBuffers
 
         public IEnumLite FindValueByNumber(int number)
         {
-            if (Enum.IsDefined(typeof(TEnum), number))
+            TEnum val = default(TEnum);
+            if (EnumParser<TEnum>.TryConvert(number, ref val))
             {
-                return new EnumValue((TEnum)(object)number);
+                return new EnumValue(val);
             }
             return null;
         }
 
         public IEnumLite FindValueByName(string name)
         {
-            if (Enum.IsDefined(typeof(TEnum), name))
+            TEnum val = default(TEnum);
+            if (EnumParser<TEnum>.TryConvert(name, ref val))
             {
-                object evalue = Enum.Parse(typeof(TEnum), name, false);
-                return new EnumValue((TEnum)evalue);
+                return new EnumValue(val);
             }
             return null;
         }
 
         public bool IsValidValue(IEnumLite value)
         {
-            return Enum.IsDefined(typeof(TEnum), value.Number);
+            TEnum val = default(TEnum);
+            return EnumParser<TEnum>.TryConvert(value.Number, ref val);
+        }
+    }
+
+    public static class EnumParser<T> where T : struct, IComparable, IFormattable
+    {
+        private static readonly Dictionary<int, T> _byNumber;
+        private static Dictionary<string, T> _byName;
+
+        static EnumParser()
+        {
+            int[] array;
+            try
+            {
+#if CLIENTPROFILE
+                // It will actually be a T[], but the CLR will let us convert.
+                array = (int[])Enum.GetValues(typeof(T));
+#else
+                var temp = new List<T>();
+                foreach (var fld in typeof (T).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
+                {
+                    if (fld.IsLiteral && fld.FieldType == typeof(T))
+                    {
+                        temp.Add((T)fld.GetValue(null));
+                    }
+                }
+                array = (int[])(object)temp.ToArray();
+#endif
+            }
+            catch
+            {
+                _byNumber = null;
+                return;
+            }
+
+            _byNumber = new Dictionary<int, T>(array.Length);
+            foreach (int i in array)
+            {
+                _byNumber[i] = (T)(object)i;
+            }
+        }
+
+        public static bool TryConvert(object input, ref T value)
+        {
+            if (input is int || input is T)
+            {
+                return TryConvert((int)input, ref value);
+            }
+            if (input is string)
+            {
+                return TryConvert((string)input, ref value);
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Tries to convert an integer to its enum representation. This would take an out parameter,
+        /// but the caller uses ref, so this approach is simpler.
+        /// </summary>
+        public static bool TryConvert(int number, ref T value)
+        {
+            // null indicates an exception at construction, use native IsDefined.
+            if (_byNumber == null)
+            {
+                return Enum.IsDefined(typeof(T), number);
+            }
+            T converted;
+            if (_byNumber != null && _byNumber.TryGetValue(number, out converted))
+            {
+                value = converted;
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Tries to convert a string to its enum representation. This would take an out parameter,
+        /// but the caller uses ref, so this approach is simpler.
+        /// </summary>
+        public static bool TryConvert(string name, ref T value)
+        {
+            // null indicates an exception at construction, use native IsDefined/Parse.
+            if (_byNumber == null)
+            {
+                if (Enum.IsDefined(typeof(T), name))
+                {
+                    value = (T)Enum.Parse(typeof(T), name, false);
+                    return true;
+                }
+                return false;
+            }
+
+            // known race, possible multiple threads each build their own copy; however, last writer will win
+            var map = _byName;
+            if (map == null)
+            {
+                map = new Dictionary<string, T>(StringComparer.Ordinal);
+                foreach (var possible in _byNumber.Values)
+                {
+                    map[possible.ToString()] = possible;
+                }
+                _byName = map;
+            }
+
+            T converted;
+            if (map.TryGetValue(name, out converted))
+            {
+                value = converted;
+                return true;
+            }
+
+            return false;
         }
     }
 }