diff options
Diffstat (limited to 'csharp')
-rw-r--r-- | csharp/Google.Protobuf.Tools.nuspec | 2 | ||||
-rw-r--r-- | csharp/README.md | 6 | ||||
-rwxr-xr-x | csharp/buildall.sh | 2 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj | 8 | ||||
-rw-r--r--[-rwxr-xr-x] | csharp/src/Google.Protobuf/Compatibility/MethodInfoExtensions.cs (renamed from csharp/src/Google.Protobuf.Test/Program.cs) | 25 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/Google.Protobuf.csproj | 2 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs | 2 | ||||
-rw-r--r-- | csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs | 173 |
8 files changed, 157 insertions, 63 deletions
diff --git a/csharp/Google.Protobuf.Tools.nuspec b/csharp/Google.Protobuf.Tools.nuspec index 62eb3b58..4294949f 100644 --- a/csharp/Google.Protobuf.Tools.nuspec +++ b/csharp/Google.Protobuf.Tools.nuspec @@ -5,7 +5,7 @@ <title>Google Protocol Buffers tools</title> <summary>Tools for Protocol Buffers - Google's data interchange format.</summary> <description>See project site for more info.</description> - <version>3.5.1</version> + <version>3.5.2</version> <authors>Google Inc.</authors> <owners>protobuf-packages</owners> <licenseUrl>https://github.com/google/protobuf/blob/master/LICENSE</licenseUrl> diff --git a/csharp/README.md b/csharp/README.md index c1d12419..436248bb 100644 --- a/csharp/README.md +++ b/csharp/README.md @@ -44,10 +44,8 @@ they're only relevant when building the `Google.Protobuf` assembly. Testing ======= -The unit tests use [NUnit 3](https://github.com/nunit/nunit). NUnit doesn't yet -support `dotnet test`, so for now the test project is a console application -using NUnitLite. Simply run `Google.Protobuf.Test.exe` to run the unit tests -directly, or else use `dotnet run`. +The unit tests use [NUnit 3](https://github.com/nunit/nunit). Tests can be +run using the Visual Studio Test Explorer or `dotnet test`. .NET 3.5 ======== diff --git a/csharp/buildall.sh b/csharp/buildall.sh index dd4b463d..50d8906d 100755 --- a/csharp/buildall.sh +++ b/csharp/buildall.sh @@ -14,4 +14,4 @@ echo Running tests. # If we want to test the .NET 4.5 version separately, we could # run Mono explicitly. However, we don't have any differences between # the .NET 4.5 and netstandard1.0 assemblies. -dotnet run -c $CONFIG -f netcoreapp1.0 -p $SRC/Google.Protobuf.Test/Google.Protobuf.Test.csproj +dotnet test -c $CONFIG -f netcoreapp1.0 $SRC/Google.Protobuf.Test/Google.Protobuf.Test.csproj diff --git a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj index 06d07b9f..6a430116 100644 --- a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj +++ b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj @@ -1,7 +1,6 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <OutputType>Exe</OutputType> <TargetFrameworks>net451;netcoreapp1.0</TargetFrameworks> <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> @@ -14,8 +13,9 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="NUnit" Version="3.6.1" /> - <PackageReference Include="NUnitLite" Version="3.6.1" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" /> + <PackageReference Include="NUnit" Version="3.9.0" /> + <PackageReference Include="NUnit3TestAdapter" Version="3.9.0" /> </ItemGroup> <!-- @@ -26,5 +26,5 @@ <PropertyGroup Condition="'$(OS)' != 'Windows_NT'"> <TargetFrameworks>netcoreapp1.0</TargetFrameworks> </PropertyGroup> - + </Project> diff --git a/csharp/src/Google.Protobuf.Test/Program.cs b/csharp/src/Google.Protobuf/Compatibility/MethodInfoExtensions.cs index 954c02b5..7b946cb6 100755..100644 --- a/csharp/src/Google.Protobuf.Test/Program.cs +++ b/csharp/src/Google.Protobuf/Compatibility/MethodInfoExtensions.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Protocol Buffers - Google's data interchange format // Copyright 2017 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ @@ -29,20 +29,19 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion -using NUnitLite; + +#if NET35 +using System; using System.Reflection; -namespace Google.Protobuf.Test +namespace Google.Protobuf.Compatibility { - class Program + // .NET Core (at least netstandard1.0) doesn't have Delegate.CreateDelegate, and .NET 3.5 doesn't have + // MethodInfo.CreateDelegate. Proxy from one to the other on .NET 3.5... + internal static class MethodInfoExtensions { - public static int Main(string[] args) - { - #if NET35 - return new AutoRun(typeof(Program).Assembly).Execute(args);
- #else - return new AutoRun(typeof(Program).GetTypeInfo().Assembly).Execute(args); - #endif - } + internal static Delegate CreateDelegate(this MethodInfo method, Type type) => + Delegate.CreateDelegate(type, method); } -}
\ No newline at end of file +} +#endif diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index 4df0bda6..6e4e1e2b 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -4,7 +4,7 @@ <Description>C# runtime library for Protocol Buffers - Google's data interchange format.</Description> <Copyright>Copyright 2015, Google Inc.</Copyright> <AssemblyTitle>Google Protocol Buffers</AssemblyTitle> - <VersionPrefix>3.5.1</VersionPrefix> + <VersionPrefix>3.5.2</VersionPrefix> <Authors>Google Inc.</Authors> <TargetFrameworks>netstandard1.0;net45</TargetFrameworks> <GenerateDocumentationFile>true</GenerateDocumentationFile> diff --git a/csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs b/csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs index 8714ab18..97596218 100644 --- a/csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs @@ -52,7 +52,7 @@ namespace Google.Protobuf.Reflection throw new ArgumentException("Cannot read from property"); } this.descriptor = descriptor; - caseDelegate = ReflectionUtil.CreateFuncIMessageT<int>(caseProperty.GetGetMethod()); + caseDelegate = ReflectionUtil.CreateFuncIMessageInt32(caseProperty.GetGetMethod()); this.descriptor = descriptor; clearDelegate = ReflectionUtil.CreateActionIMessage(clearMethod); diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index df820ca3..80d5c774 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -30,11 +30,14 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion +using Google.Protobuf.Compatibility; using System; -using System.Collections.Generic; -using System.Linq.Expressions; using System.Reflection; +#if NET35 +using Google.Protobuf.Compatibility; +#endif + namespace Google.Protobuf.Reflection { /// <summary> @@ -53,55 +56,149 @@ namespace Google.Protobuf.Reflection internal static readonly Type[] EmptyTypes = new Type[0]; /// <summary> - /// Creates a delegate which will cast the argument to the appropriate method target type, + /// Creates a delegate which will cast the argument to the type that declares the method, /// call the method on it, then convert the result to object. /// </summary> - internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) - { - ParameterExpression parameter = Expression.Parameter(typeof(IMessage), "p"); - Expression downcast = Expression.Convert(parameter, method.DeclaringType); - Expression call = Expression.Call(downcast, method); - Expression upcast = Expression.Convert(call, typeof(object)); - return Expression.Lambda<Func<IMessage, object>>(upcast, parameter).Compile(); - } + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageObject(method); /// <summary> - /// Creates a delegate which will cast the argument to the appropriate method target type, - /// call the method on it, then convert the result to the specified type. + /// Creates a delegate which will cast the argument to the type that declares the method, + /// call the method on it, then convert the result to the specified type. The method is expected + /// to actually return an enum (because of where we're calling it - for oneof cases). Sometimes that + /// means we need some extra work to perform conversions. /// </summary> - internal static Func<IMessage, T> CreateFuncIMessageT<T>(MethodInfo method) - { - ParameterExpression parameter = Expression.Parameter(typeof(IMessage), "p"); - Expression downcast = Expression.Convert(parameter, method.DeclaringType); - Expression call = Expression.Call(downcast, method); - Expression upcast = Expression.Convert(call, typeof(T)); - return Expression.Lambda<Func<IMessage, T>>(upcast, parameter).Compile(); - } + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageInt32(method); + + /// <summary> + /// Creates a delegate which will execute the given method after casting the first argument to + /// the type that declares the method, and the second argument to the first parameter type of the method. + /// </summary> + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, method.GetParameters()[0].ParameterType).CreateActionIMessageObject(method); /// <summary> /// Creates a delegate which will execute the given method after casting the first argument to - /// the target type of the method, and the second argument to the first parameter type of the method. + /// type that declares the method. + /// </summary> + /// <param name="method">The method to create a delegate for, which must be declared in an IMessage + /// implementation.</param> + internal static Action<IMessage> CreateActionIMessage(MethodInfo method) => + GetReflectionHelper(method.DeclaringType, typeof(object)).CreateActionIMessage(method); + + /// <summary> + /// Creates a reflection helper for the given type arguments. Currently these are created on demand + /// rather than cached; this will be "busy" when initially loading a message's descriptor, but after that + /// they can be garbage collected. We could cache them by type if that proves to be important, but creating + /// an object is pretty cheap. /// </summary> - internal static Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) + private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => + (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); + + // Non-generic interface allowing us to use an instance of ReflectionHelper<T1, T2> without statically + // knowing the types involved. + private interface IReflectionHelper { - ParameterExpression targetParameter = Expression.Parameter(typeof(IMessage), "target"); - ParameterExpression argParameter = Expression.Parameter(typeof(object), "arg"); - Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType); - Expression castArgument = Expression.Convert(argParameter, method.GetParameters()[0].ParameterType); - Expression call = Expression.Call(castTarget, method, castArgument); - return Expression.Lambda<Action<IMessage, object>>(call, targetParameter, argParameter).Compile(); + Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method); + Action<IMessage> CreateActionIMessage(MethodInfo method); + Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method); + Action<IMessage, object> CreateActionIMessageObject(MethodInfo method); + } + + private class ReflectionHelper<T1, T2> : IReflectionHelper + { + + public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) + { + // On pleasant runtimes, we can create a Func<int> from a method returning + // an enum based on an int. That's the fast path. + if (CanConvertEnumFuncToInt32Func) + { + var del = (Func<T1, int>) method.CreateDelegate(typeof(Func<T1, int>)); + return message => del((T1) message); + } + else + { + // On some runtimes (e.g. old Mono) the return type has to be exactly correct, + // so we go via boxing. Reflection is already fairly inefficient, and this is + // only used for one-of case checking, fortunately. + var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); + return message => (int) (object) del((T1) message); + } + } + + public Action<IMessage> CreateActionIMessage(MethodInfo method) + { + var del = (Action<T1>) method.CreateDelegate(typeof(Action<T1>)); + return message => del((T1) message); + } + + public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) + { + var del = (Func<T1, T2>) method.CreateDelegate(typeof(Func<T1, T2>)); + return message => del((T1) message); + } + + public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) + { + var del = (Action<T1, T2>) method.CreateDelegate(typeof(Action<T1, T2>)); + return (message, arg) => del((T1) message, (T2) arg); + } + } + + // Runtime compatibility checking code - see ReflectionHelper<T1, T2>.CreateFuncIMessageInt32 for + // details about why we're doing this. + + // Deliberately not inside the generic type. We only want to check this once. + private static bool CanConvertEnumFuncToInt32Func { get; } = CheckCanConvertEnumFuncToInt32Func(); + + private static bool CheckCanConvertEnumFuncToInt32Func() + { + try + { + PreventLinkerFailures(); + // Try to do the conversion using reflection, so we can see whether it's supported. + MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod)); + // If this passes, we're in a reasonable runtime. + method.CreateDelegate(typeof(Func<int>)); + return true; + } + catch (ArgumentException) + { + return false; + } } /// <summary> - /// Creates a delegate which will execute the given method after casting the first argument to - /// the target type of the method. + /// This method is effectively pointless, but only called once. It's present (and called) + /// to avoid the Unity linker from removing code that's only called via reflection. /// </summary> - internal static Action<IMessage> CreateActionIMessage(MethodInfo method) + private static void PreventLinkerFailures() { - ParameterExpression targetParameter = Expression.Parameter(typeof(IMessage), "target"); - Expression castTarget = Expression.Convert(targetParameter, method.DeclaringType); - Expression call = Expression.Call(castTarget, method); - return Expression.Lambda<Action<IMessage>>(call, targetParameter).Compile(); - } + // Exercise the method directly. This should avoid any pro-active linkers from stripping + // the method out. + SampleEnum x = SampleEnumMethod(); + if (x != SampleEnum.X) + { + throw new InvalidOperationException("Failure in reflection utilities"); + } + // Make sure the ReflectionHelper parameterless constructor isn't removed... + var helper = new ReflectionHelper<int, int>(); + } + + public enum SampleEnum + { + X + } + + // Public to make the reflection simpler. + public static SampleEnum SampleEnumMethod() => SampleEnum.X; } -}
\ No newline at end of file +} |