[C#] Update GetExtension to support getting typed value (#9655)

pull/9661/head
James Newton-King 3 years ago committed by GitHub
parent 405913c712
commit e5ae3bb763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj
  2. 61
      csharp/src/Google.Protobuf.Test/ExtensionSetTest.cs
  3. 2
      csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj
  4. 2
      csharp/src/Google.Protobuf.Test/RefStructCompatibilityTest.cs
  5. 2
      csharp/src/Google.Protobuf/Extension.cs
  6. 55
      csharp/src/Google.Protobuf/ExtensionSet.cs
  7. 5
      csharp/src/Google.Protobuf/ExtensionValue.cs

@ -6,7 +6,7 @@
and without the internal visibility from the test project (all of which have caused issues in the past). and without the internal visibility from the test project (all of which have caused issues in the past).
--> -->
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net45;netstandard1.1;netstandard2.0</TargetFrameworks> <TargetFrameworks>net462;netstandard1.1;netstandard2.0</TargetFrameworks>
<LangVersion>3.0</LangVersion> <LangVersion>3.0</LangVersion>
<AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>

@ -1,4 +1,6 @@
using Google.Protobuf.TestProtos.Proto2; using System;
using System.Collections;
using Google.Protobuf.TestProtos.Proto2;
using NUnit.Framework; using NUnit.Framework;
using static Google.Protobuf.TestProtos.Proto2.UnittestExtensions; using static Google.Protobuf.TestProtos.Proto2.UnittestExtensions;
@ -79,6 +81,63 @@ namespace Google.Protobuf
Assert.AreEqual("abcd", ExtensionSet.Get(ref extensionSet, OptionalStringExtension)); Assert.AreEqual("abcd", ExtensionSet.Get(ref extensionSet, OptionalStringExtension));
} }
[Test]
public void GetSingle()
{
var extensionValue = new TestAllTypes.Types.NestedMessage() { Bb = 42 };
var untypedExtension = new Extension<TestAllExtensions, object>(OptionalNestedMessageExtension.FieldNumber, codec: null);
var wrongTypedExtension = new Extension<TestAllExtensions, TestAllTypes>(OptionalNestedMessageExtension.FieldNumber, codec: null);
var message = new TestAllExtensions();
var value1 = message.GetExtension(untypedExtension);
Assert.IsNull(value1);
message.SetExtension(OptionalNestedMessageExtension, extensionValue);
var value2 = message.GetExtension(untypedExtension);
Assert.IsNotNull(value2);
var valueBytes = ((IMessage)value2).ToByteArray();
var parsedValue = TestProtos.Proto2.TestAllTypes.Types.NestedMessage.Parser.ParseFrom(valueBytes);
Assert.AreEqual(extensionValue, parsedValue);
var ex = Assert.Throws<InvalidOperationException>(() => message.GetExtension(wrongTypedExtension));
var expectedMessage = "The stored extension value has a type of 'Google.Protobuf.TestProtos.Proto2.TestAllTypes+Types+NestedMessage, Google.Protobuf.Test.TestProtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'. " +
"This a different from the requested type of 'Google.Protobuf.TestProtos.Proto2.TestAllTypes, Google.Protobuf.Test.TestProtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'.";
Assert.AreEqual(expectedMessage, ex.Message);
}
[Test]
public void GetRepeated()
{
var extensionValue = new TestAllTypes.Types.NestedMessage() { Bb = 42 };
var untypedExtension = new Extension<TestAllExtensions, IList>(RepeatedNestedMessageExtension.FieldNumber, codec: null);
var wrongTypedExtension = new RepeatedExtension<TestAllExtensions, TestAllTypes>(RepeatedNestedMessageExtension.FieldNumber, codec: null);
var message = new TestAllExtensions();
var value1 = message.GetExtension(untypedExtension);
Assert.IsNull(value1);
var repeatedField = message.GetOrInitializeExtension<TestAllTypes.Types.NestedMessage>(RepeatedNestedMessageExtension);
repeatedField.Add(extensionValue);
var value2 = message.GetExtension(untypedExtension);
Assert.IsNotNull(value2);
Assert.AreEqual(1, value2.Count);
var valueBytes = ((IMessage)value2[0]).ToByteArray();
var parsedValue = TestProtos.Proto2.TestAllTypes.Types.NestedMessage.Parser.ParseFrom(valueBytes);
Assert.AreEqual(extensionValue, parsedValue);
var ex = Assert.Throws<InvalidOperationException>(() => message.GetExtension(wrongTypedExtension));
var expectedMessage = "The stored extension value has a type of 'Google.Protobuf.TestProtos.Proto2.TestAllTypes+Types+NestedMessage, Google.Protobuf.Test.TestProtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'. " +
"This a different from the requested type of 'Google.Protobuf.TestProtos.Proto2.TestAllTypes, Google.Protobuf.Test.TestProtos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604'.";
Assert.AreEqual(expectedMessage, ex.Message);
}
[Test] [Test]
public void TestEquals() public void TestEquals()
{ {

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net451;netcoreapp3.1;net60</TargetFrameworks> <TargetFrameworks>net462;netcoreapp3.1;net60</TargetFrameworks>
<AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>../../keys/Google.Protobuf.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<IsPackable>False</IsPackable> <IsPackable>False</IsPackable>

@ -60,7 +60,7 @@ namespace Google.Protobuf
var currentAssemblyDir = Path.GetDirectoryName(typeof(RefStructCompatibilityTest).GetTypeInfo().Assembly.Location); var currentAssemblyDir = Path.GetDirectoryName(typeof(RefStructCompatibilityTest).GetTypeInfo().Assembly.Location);
var testProtosProjectDir = Path.GetFullPath(Path.Combine(currentAssemblyDir, "..", "..", "..", "..", "Google.Protobuf.Test.TestProtos")); var testProtosProjectDir = Path.GetFullPath(Path.Combine(currentAssemblyDir, "..", "..", "..", "..", "Google.Protobuf.Test.TestProtos"));
var testProtosOutputDir = (currentAssemblyDir.Contains("bin/Debug/") || currentAssemblyDir.Contains("bin\\Debug\\")) ? "bin\\Debug\\net45" : "bin\\Release\\net45"; var testProtosOutputDir = (currentAssemblyDir.Contains("bin/Debug/") || currentAssemblyDir.Contains("bin\\Debug\\")) ? "bin\\Debug\\net462" : "bin\\Release\\net462";
// If "ref struct" types are used in the generated code, compilation with an old compiler will fail with the following error: // If "ref struct" types are used in the generated code, compilation with an old compiler will fail with the following error:
// "XYZ is obsolete: 'Types with embedded references are not supported in this version of your compiler.'" // "XYZ is obsolete: 'Types with embedded references are not supported in this version of your compiler.'"

@ -77,7 +77,7 @@ namespace Google.Protobuf
this.codec = codec; this.codec = codec;
} }
internal TValue DefaultValue => codec.DefaultValue; internal TValue DefaultValue => codec != null ? codec.DefaultValue : default(TValue);
internal override Type TargetType => typeof(TTarget); internal override Type TargetType => typeof(TTarget);

@ -34,6 +34,7 @@ using Google.Protobuf.Collections;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Security; using System.Security;
namespace Google.Protobuf namespace Google.Protobuf
@ -63,7 +64,39 @@ namespace Google.Protobuf
IExtensionValue value; IExtensionValue value;
if (TryGetValue(ref set, extension, out value)) if (TryGetValue(ref set, extension, out value))
{ {
return ((ExtensionValue<TValue>)value).GetValue(); // The stored ExtensionValue can be a different type to what is being requested.
// This happens when the same extension proto is compiled in different assemblies.
// To allow consuming assemblies to still get the value when the TValue type is
// different, this get method:
// 1. Attempts to cast the value to the expected ExtensionValue<TValue>.
// This is the usual case. It is used first because it avoids possibly boxing the value.
// 2. Fallback to get the value as object from IExtensionValue then casting.
// This allows for someone to specify a TValue of object. They can then convert
// the values to bytes and reparse using expected value.
// 3. If neither of these work, throw a user friendly error that the types aren't compatible.
if (value is ExtensionValue<TValue> extensionValue)
{
return extensionValue.GetValue();
}
else if (value.GetValue() is TValue underlyingValue)
{
return underlyingValue;
}
else
{
var valueType = value.GetType().GetTypeInfo();
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(ExtensionValue<>))
{
var storedType = valueType.GenericTypeArguments[0];
throw new InvalidOperationException(
"The stored extension value has a type of '" + storedType.AssemblyQualifiedName + "'. " +
"This a different from the requested type of '" + typeof(TValue).AssemblyQualifiedName + "'.");
}
else
{
throw new InvalidOperationException("Unexpected extension value type: " + valueType.AssemblyQualifiedName);
}
}
} }
else else
{ {
@ -79,7 +112,25 @@ namespace Google.Protobuf
IExtensionValue value; IExtensionValue value;
if (TryGetValue(ref set, extension, out value)) if (TryGetValue(ref set, extension, out value))
{ {
return ((RepeatedExtensionValue<TValue>)value).GetValue(); if (value is RepeatedExtensionValue<TValue> extensionValue)
{
return extensionValue.GetValue();
}
else
{
var valueType = value.GetType().GetTypeInfo();
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(RepeatedExtensionValue<>))
{
var storedType = valueType.GenericTypeArguments[0];
throw new InvalidOperationException(
"The stored extension value has a type of '" + storedType.AssemblyQualifiedName + "'. " +
"This a different from the requested type of '" + typeof(TValue).AssemblyQualifiedName + "'.");
}
else
{
throw new InvalidOperationException("Unexpected extension value type: " + valueType.AssemblyQualifiedName);
}
}
} }
else else
{ {

@ -44,6 +44,7 @@ namespace Google.Protobuf
void WriteTo(ref WriteContext ctx); void WriteTo(ref WriteContext ctx);
int CalculateSize(); int CalculateSize();
bool IsInitialized(); bool IsInitialized();
object GetValue();
} }
internal sealed class ExtensionValue<T> : IExtensionValue internal sealed class ExtensionValue<T> : IExtensionValue
@ -118,6 +119,8 @@ namespace Google.Protobuf
public T GetValue() => field; public T GetValue() => field;
object IExtensionValue.GetValue() => field;
public void SetValue(T value) public void SetValue(T value)
{ {
field = value; field = value;
@ -201,6 +204,8 @@ namespace Google.Protobuf
public RepeatedField<T> GetValue() => field; public RepeatedField<T> GetValue() => field;
object IExtensionValue.GetValue() => field;
public bool IsInitialized() public bool IsInitialized()
{ {
for (int i = 0; i < field.Count; i++) for (int i = 0; i < field.Count; i++)

Loading…
Cancel
Save