aboutsummaryrefslogtreecommitdiffhomepage
path: root/csharp
diff options
context:
space:
mode:
Diffstat (limited to 'csharp')
-rw-r--r--csharp/Google.Protobuf.Tools.nuspec2
-rw-r--r--csharp/README.md6
-rwxr-xr-xcsharp/buildall.sh2
-rw-r--r--csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj8
-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.csproj2
-rw-r--r--csharp/src/Google.Protobuf/Reflection/OneofAccessor.cs2
-rw-r--r--csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs173
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
+}