diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
index 4aecc998de..a3edd5950c 100644
--- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
+++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/AnyTest.cs
@@ -90,6 +90,24 @@ namespace Google.Protobuf.WellKnownTypes
Assert.AreEqual(message, unpacked);
}
+ [Test]
+ public void TryUnpack_WrongType()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ Assert.False(any.TryUnpack(out TestOneof unpacked));
+ Assert.Null(unpacked);
+ }
+
+ [Test]
+ public void TryUnpack_RightType()
+ {
+ var message = SampleMessages.CreateFullTestAllTypes();
+ var any = Any.Pack(message);
+ Assert.IsTrue(any.TryUnpack(out TestAllTypes unpacked));
+ Assert.AreEqual(message, unpacked);
+ }
+
[Test]
public void ToString_WithValues()
{
@@ -99,6 +117,16 @@ namespace Google.Protobuf.WellKnownTypes
Assert.That(text, Does.Contain("\"@value\": \"" + message.ToByteString().ToBase64() + "\""));
}
+ [Test]
+ [TestCase("proto://foo.bar", "foo.bar")]
+ [TestCase("/foo/bar/baz", "baz")]
+ [TestCase("foobar", "")]
+ public void GetTypeName(string typeUrl, string expectedTypeName)
+ {
+ var any = new Any { TypeUrl = typeUrl };
+ Assert.AreEqual(expectedTypeName, Any.GetTypeName(typeUrl));
+ }
+
[Test]
public void ToString_Empty()
{
diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
index f4fac73865..fca689dc08 100644
--- a/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
+++ b/csharp/src/Google.Protobuf/WellKnownTypes/AnyPartial.cs
@@ -44,17 +44,25 @@ namespace Google.Protobuf.WellKnownTypes
prefix.EndsWith("/") ? prefix + descriptor.FullName : prefix + "/" + descriptor.FullName;
///
- /// Retrieves the type name for a type URL. This is always just the last part of the URL,
- /// after the trailing slash. No validation of anything before the trailing slash is performed.
- /// If the type URL does not include a slash, an empty string is returned rather than an exception
- /// being thrown; this won't match any types, and the calling code is probably in a better position
- /// to give a meaningful error.
- /// There is no handling of fragments or queries at the moment.
+ /// Retrieves the type name for a type URL, matching the
+ /// of the packed message type.
///
+ ///
+ ///
+ /// This is always just the last part of the URL, after the final slash. No validation of
+ /// anything before the trailing slash is performed. If the type URL does not include a slash,
+ /// an empty string is returned rather than an exception being thrown; this won't match any types,
+ /// and the calling code is probably in a better position to give a meaningful error.
+ ///
+ ///
+ /// There is no handling of fragments or queries at the moment.
+ ///
+ ///
/// The URL to extract the type name from
/// The type name
- internal static string GetTypeName(string typeUrl)
+ public static string GetTypeName(string typeUrl)
{
+ ProtoPreconditions.CheckNotNull(typeUrl, nameof(typeUrl));
int lastSlash = typeUrl.LastIndexOf('/');
return lastSlash == -1 ? "" : typeUrl.Substring(lastSlash + 1);
}
@@ -80,6 +88,27 @@ namespace Google.Protobuf.WellKnownTypes
return target;
}
+ ///
+ /// Attempts to unpack the content of this Any message into the target message type,
+ /// if it matches the type URL within this Any message.
+ ///
+ /// The type of message to attempt to unpack the content into.
+ /// true if the message was successfully unpacked; false if the type name didn't match
+ public bool TryUnpack(out T result) where T : IMessage, new()
+ {
+ // Note: deliberately avoid writing anything to result until the end, in case it's being
+ // monitored by other threads. (That would be a bug in the calling code, but let's not make it worse.)
+ T target = new T();
+ if (GetTypeName(TypeUrl) != target.Descriptor.FullName)
+ {
+ result = default(T); // Can't use null as there's no class constraint, but this always *will* be null in real usage.
+ return false;
+ }
+ target.MergeFrom(Value);
+ result = target;
+ return true;
+ }
+
///
/// Packs the specified message into an Any message using a type URL prefix of "type.googleapis.com".
///