diff --git a/src/csharp/Grpc.Core/DeserializationContext.cs b/src/csharp/Grpc.Core/DeserializationContext.cs
new file mode 100644
index 00000000000..17f0ba2805c
--- /dev/null
+++ b/src/csharp/Grpc.Core/DeserializationContext.cs
@@ -0,0 +1,49 @@
+#region Copyright notice and license
+
+// Copyright 2018 The gRPC Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#endregion
+
+namespace Grpc.Core
+{
+ ///
+ /// Provides access to the payload being deserialized when deserializing messages.
+ ///
+ public abstract class DeserializationContext
+ {
+ ///
+ /// Returns true if there is a payload to deserialize (= payload is not null), false otherwise.
+ ///
+ public virtual bool HasPayload => PayloadLength.HasValue;
+
+ ///
+ /// Get the total length of the payload in bytes or null if the payload is null.
+ ///
+ public abstract int? PayloadLength { get; }
+
+ ///
+ /// Gets the entire payload as a newly allocated byte array.
+ /// Once the byte array is returned, the byte array becomes owned by the caller and won't be ever accessed or reused by gRPC again.
+ /// NOTE: Obtaining the buffer as a newly allocated byte array is the simplest way of accessing the payload,
+ /// but it can have important consequences in high-performance scenarios.
+ /// In particular, using this method usually requires copying of the entire buffer one extra time.
+ /// Also, allocating a new buffer each time can put excessive pressure on GC, especially if
+ /// the payload is more than 86700 bytes large (which means the newly allocated buffer will be placed in LOH,
+ /// and LOH object can only be garbage collected via a full ("stop the world") GC run).
+ ///
+ /// byte array containing the entire payload or null if there is no payload.
+ public abstract byte[] PayloadAsNewBuffer();
+ }
+}
diff --git a/src/csharp/Grpc.Core/Marshaller.cs b/src/csharp/Grpc.Core/Marshaller.cs
index 1d758ca935d..df59cbd8d3a 100644
--- a/src/csharp/Grpc.Core/Marshaller.cs
+++ b/src/csharp/Grpc.Core/Marshaller.cs
@@ -29,36 +29,122 @@ namespace Grpc.Core
readonly Func serializer;
readonly Func deserializer;
+ readonly Action contextualSerializer;
+ readonly Func contextualDeserializer;
+
///
- /// Initializes a new marshaller.
+ /// Initializes a new marshaller from simple serialize/deserialize functions.
///
/// Function that will be used to serialize messages.
/// Function that will be used to deserialize messages.
public Marshaller(Func serializer, Func deserializer)
{
- this.serializer = GrpcPreconditions.CheckNotNull(serializer, "serializer");
- this.deserializer = GrpcPreconditions.CheckNotNull(deserializer, "deserializer");
+ this.serializer = GrpcPreconditions.CheckNotNull(serializer, nameof(serializer));
+ this.deserializer = GrpcPreconditions.CheckNotNull(deserializer, nameof(deserializer));
+ this.contextualSerializer = EmulateContextualSerializer;
+ this.contextualDeserializer = EmulateContextualDeserializer;
}
///
- /// Gets the serializer function.
+ /// Initializes a new marshaller from serialize/deserialize fuctions that can access serialization and deserialization
+ /// context. Compared to the simple serializer/deserializer functions, using the contextual version provides more
+ /// flexibility and can lead to increased efficiency (and better performance).
+ /// Note: This constructor is part of an experimental API that can change or be removed without any prior notice.
///
- public Func Serializer
+ /// Function that will be used to serialize messages.
+ /// Function that will be used to deserialize messages.
+ public Marshaller(Action serializer, Func deserializer)
{
- get
- {
- return this.serializer;
- }
+ this.contextualSerializer = GrpcPreconditions.CheckNotNull(serializer, nameof(serializer));
+ this.contextualDeserializer = GrpcPreconditions.CheckNotNull(deserializer, nameof(deserializer));
+ this.serializer = EmulateSimpleSerializer;
+ this.deserializer = EmulateSimpleDeserializer;
}
+ ///
+ /// Gets the serializer function.
+ ///
+ public Func Serializer => this.serializer;
+
///
/// Gets the deserializer function.
///
- public Func Deserializer
+ public Func Deserializer => this.deserializer;
+
+ ///
+ /// Gets the serializer function.
+ /// Note: experimental API that can change or be removed without any prior notice.
+ ///
+ public Action ContextualSerializer => this.contextualSerializer;
+
+ ///
+ /// Gets the serializer function.
+ /// Note: experimental API that can change or be removed without any prior notice.
+ ///
+ public Func ContextualDeserializer => this.contextualDeserializer;
+
+ // for backward compatibility, emulate the simple serializer using the contextual one
+ private byte[] EmulateSimpleSerializer(T msg)
{
- get
+ // TODO(jtattermusch): avoid the allocation by passing a thread-local instance
+ var context = new EmulatedSerializationContext();
+ this.contextualSerializer(msg, context);
+ return context.GetPayload();
+ }
+
+ // for backward compatibility, emulate the simple deserializer using the contextual one
+ private T EmulateSimpleDeserializer(byte[] payload)
+ {
+ // TODO(jtattermusch): avoid the allocation by passing a thread-local instance
+ var context = new EmulatedDeserializationContext(payload);
+ return this.contextualDeserializer(context);
+ }
+
+ // for backward compatibility, emulate the contextual serializer using the simple one
+ private void EmulateContextualSerializer(T message, SerializationContext context)
+ {
+ var payload = this.serializer(message);
+ context.Complete(payload);
+ }
+
+ // for backward compatibility, emulate the contextual deserializer using the simple one
+ private T EmulateContextualDeserializer(DeserializationContext context)
+ {
+ return this.deserializer(context.PayloadAsNewBuffer());
+ }
+
+ internal class EmulatedSerializationContext : SerializationContext
+ {
+ bool isComplete;
+ byte[] payload;
+
+ public override void Complete(byte[] payload)
+ {
+ GrpcPreconditions.CheckState(!isComplete);
+ this.isComplete = true;
+ this.payload = payload;
+ }
+
+ internal byte[] GetPayload()
+ {
+ return this.payload;
+ }
+ }
+
+ internal class EmulatedDeserializationContext : DeserializationContext
+ {
+ readonly byte[] payload;
+
+ public EmulatedDeserializationContext(byte[] payload)
+ {
+ this.payload = payload;
+ }
+
+ public override int? PayloadLength => payload?.Length;
+
+ public override byte[] PayloadAsNewBuffer()
{
- return this.deserializer;
+ return payload;
}
}
}
diff --git a/src/csharp/Grpc.Core/SerializationContext.cs b/src/csharp/Grpc.Core/SerializationContext.cs
new file mode 100644
index 00000000000..cf4d1595da2
--- /dev/null
+++ b/src/csharp/Grpc.Core/SerializationContext.cs
@@ -0,0 +1,34 @@
+#region Copyright notice and license
+
+// Copyright 2018 The gRPC Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#endregion
+
+namespace Grpc.Core
+{
+ ///
+ /// Provides storage for payload when serializing a message.
+ ///
+ public abstract class SerializationContext
+ {
+ ///
+ /// Use the byte array as serialized form of current message and mark serialization process as complete.
+ /// Complete() can only be called once. By calling this method the caller gives up the ownership of the
+ /// payload which must not be accessed afterwards.
+ ///
+ /// the serialized form of current message
+ public abstract void Complete(byte[] payload);
+ }
+}