Imported Upstream version 3.8.0
[platform/upstream/protobuf.git] / csharp / src / Google.Protobuf / Reflection / ReflectionUtil.cs
1 #region Copyright notice and license
2 // Protocol Buffers - Google's data interchange format
3 // Copyright 2008 Google Inc.  All rights reserved.
4 // https://developers.google.com/protocol-buffers/
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are
8 // met:
9 //
10 //     * Redistributions of source code must retain the above copyright
11 // notice, this list of conditions and the following disclaimer.
12 //     * Redistributions in binary form must reproduce the above
13 // copyright notice, this list of conditions and the following disclaimer
14 // in the documentation and/or other materials provided with the
15 // distribution.
16 //     * Neither the name of Google Inc. nor the names of its
17 // contributors may be used to endorse or promote products derived from
18 // this software without specific prior written permission.
19 //
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #endregion
32
33 using Google.Protobuf.Compatibility;
34 using System;
35 using System.Reflection;
36
37 namespace Google.Protobuf.Reflection
38 {
39     /// <summary>
40     /// The methods in this class are somewhat evil, and should not be tampered with lightly.
41     /// Basically they allow the creation of relatively weakly typed delegates from MethodInfos
42     /// which are more strongly typed. They do this by creating an appropriate strongly typed
43     /// delegate from the MethodInfo, and then calling that within an anonymous method.
44     /// Mind-bending stuff (at least to your humble narrator) but the resulting delegates are
45     /// very fast compared with calling Invoke later on.
46     /// </summary>
47     internal static class ReflectionUtil
48     {
49         static ReflectionUtil()
50         {
51             ForceInitialize<string>(); // Handles all reference types
52             ForceInitialize<int>();
53             ForceInitialize<long>();
54             ForceInitialize<uint>();
55             ForceInitialize<ulong>();
56             ForceInitialize<float>();
57             ForceInitialize<double>();
58             ForceInitialize<bool>();
59             ForceInitialize<int?>();
60             ForceInitialize<long?>();
61             ForceInitialize<uint?>();
62             ForceInitialize<ulong?>();
63             ForceInitialize<float?>();
64             ForceInitialize<double?>();
65             ForceInitialize<bool?>();
66             ForceInitialize<SampleEnum>();
67             SampleEnumMethod();
68         }
69
70         internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>();
71
72         /// <summary>
73         /// Empty Type[] used when calling GetProperty to force property instead of indexer fetching.
74         /// </summary>
75         internal static readonly Type[] EmptyTypes = new Type[0];
76
77         /// <summary>
78         /// Creates a delegate which will cast the argument to the type that declares the method,
79         /// call the method on it, then convert the result to object.
80         /// </summary>
81         /// <param name="method">The method to create a delegate for, which must be declared in an IMessage
82         /// implementation.</param>
83         internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) =>
84             GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method);
85
86         /// <summary>
87         /// Creates a delegate which will cast the argument to the type that declares the method,
88         /// call the method on it, then convert the result to the specified type. The method is expected
89         /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that
90         /// means we need some extra work to perform conversions.
91         /// </summary>
92         /// <param name="method">The method to create a delegate for, which must be declared in an IMessage
93         /// implementation.</param>
94         internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) =>
95             GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method);
96
97         /// <summary>
98         /// Creates a delegate which will execute the given method after casting the first argument to
99         /// the type that declares the method, and the second argument to the first parameter type of the method.
100         /// </summary>
101         /// <param name="method">The method to create a delegate for, which must be declared in an IMessage
102         /// implementation.</param>
103         internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) =>
104             GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method);
105
106         /// <summary>
107         /// Creates a delegate which will execute the given method after casting the first argument to
108         /// type that declares the method.
109         /// </summary>
110         /// <param name="method">The method to create a delegate for, which must be declared in an IMessage
111         /// implementation.</param>
112         internal static Action<IMessage> CreateActionIMessage(MethodInfo method) =>
113             GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method);
114
115         internal static Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) =>
116             GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method);
117
118         /// <summary>
119         /// Creates a reflection helper for the given type arguments. Currently these are created on demand
120         /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that
121         /// they can be garbage collected. We could cache them by type if that proves to be important, but creating
122         /// an object is pretty cheap.
123         /// </summary>
124         private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) =>
125             (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2));
126
127         // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically
128         // knowing the types involved.
129         private interface IReflectionHelper
130         {
131             Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method);
132             Action<IMessage> CreateActionIMessage(MethodInfo method);
133             Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method);
134             Action<IMessage, object> CreateActionIMessageObject(MethodInfo method);
135             Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method);
136         }
137
138         private class ReflectionHelper<T1, T2> : IReflectionHelper
139         {
140
141             public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method)
142             {
143                 // On pleasant runtimes, we can create a Func<int> from a method returning
144                 // an enum based on an int. That's the fast path.
145                 if (CanConvertEnumFuncToInt32Func)
146                 {
147                     var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>));
148                     return message => del((T1) message);
149                 }
150                 else
151                 {
152                     // On some runtimes (e.g. old Mono) the return type has to be exactly correct,
153                     // so we go via boxing. Reflection is already fairly inefficient, and this is
154                     // only used for one-of case checking, fortunately.
155                     var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
156                     return message => (int) (object) del((T1) message);
157                 }
158             }
159
160             public Action<IMessage> CreateActionIMessage(MethodInfo method)
161             {
162                 var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>));
163                 return message => del((T1) message);
164             }
165
166             public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method)
167             {
168                 var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>));
169                 return message => del((T1) message);
170             }
171
172             public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method)
173             {
174                 var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>));
175                 return (message, arg) => del((T1) message, (T2) arg);
176             }
177
178             public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method)
179             {
180                 var del = (Func<T1, bool>)method.CreateDelegate(typeof(Func<T1, bool>));
181                 return message => del((T1)message);
182             }
183         }
184
185         // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for
186         // details about why we're doing this.
187
188         // Deliberately not inside the generic type. We only want to check this once.
189         private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func();
190
191         private static bool CheckCanConvertEnumFuncToInt32Func()
192         {
193             try
194             {
195                 // Try to do the conversion using reflection, so we can see whether it's supported.
196                 MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod));
197                 // If this passes, we're in a reasonable runtime.
198                 method.CreateDelegate(typeof(Func<int>));
199                 return true;
200             }
201             catch (ArgumentException)
202             {
203                 return false;
204             }
205         }
206
207         public enum SampleEnum
208         {
209             X
210         }
211
212         // Public to make the reflection simpler.
213         public static SampleEnum SampleEnumMethod() => SampleEnum.X;
214     }
215 }