2021-04-08 20:09:59 +08:00
using System ;
using System.Reflection ;
namespace ProtoBuf
{
internal enum TimeSpanScale
{
Days = 0 ,
Hours = 1 ,
Minutes = 2 ,
Seconds = 3 ,
Milliseconds = 4 ,
Ticks = 5 ,
MinMax = 15
}
/// <summary>
/// Provides support for common .NET types that do not have a direct representation
/// in protobuf, using the definitions from bcl.proto
/// </summary>
public static class BclHelpers
{
/// <summary>
/// Creates a new instance of the specified type, bypassing the constructor.
/// </summary>
/// <param name="type">The type to create</param>
/// <returns>The new instance</returns>
/// <exception cref="NotSupportedException">If the platform does not support constructor-skipping</exception>
public static object GetUninitializedObject ( Type type )
{
#if COREFX
object obj = TryGetUninitializedObjectWithFormatterServices ( type ) ;
if ( obj ! = null ) return obj ;
# endif
#if PLAT_BINARYFORMATTER && !(COREFX || PROFILE259)
return System . Runtime . Serialization . FormatterServices . GetUninitializedObject ( type ) ;
# else
throw new NotSupportedException ( "Constructor-skipping is not supported on this platform" ) ;
# endif
}
#if COREFX // this is inspired by DCS: https://github.com/dotnet/corefx/blob/c02d33b18398199f6acc17d375dab154e9a1df66/src/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlFormatReaderGenerator.cs#L854-L894
static Func < Type , object > getUninitializedObject ;
static internal object TryGetUninitializedObjectWithFormatterServices ( Type type )
{
if ( getUninitializedObject = = null )
{
try {
var formatterServiceType = typeof ( string ) . GetTypeInfo ( ) . Assembly . GetType ( "System.Runtime.Serialization.FormatterServices" ) ;
if ( formatterServiceType = = null )
{
// fallback for .Net Core 3.0
var formatterAssembly = Assembly . Load ( new AssemblyName ( "System.Runtime.Serialization.Formatters" ) ) ;
formatterServiceType = formatterAssembly . GetType ( "System.Runtime.Serialization.FormatterServices" ) ;
}
MethodInfo method = formatterServiceType ? . GetMethod ( "GetUninitializedObject" , BindingFlags . NonPublic | BindingFlags . Public | BindingFlags . Static ) ;
if ( method ! = null )
{
getUninitializedObject = ( Func < Type , object > ) method . CreateDelegate ( typeof ( Func < Type , object > ) ) ;
}
}
catch { /* best efforts only */ }
if ( getUninitializedObject = = null ) getUninitializedObject = x = > null ;
}
return getUninitializedObject ( type ) ;
}
# endif
const int FieldTimeSpanValue = 0x01 , FieldTimeSpanScale = 0x02 , FieldTimeSpanKind = 0x03 ;
internal static readonly DateTime [ ] EpochOrigin = {
new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , 0 , DateTimeKind . Unspecified ) ,
new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , 0 , DateTimeKind . Utc ) ,
new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , 0 , DateTimeKind . Local )
} ;
/// <summary>
/// The default value for dates that are following google.protobuf.Timestamp semantics
/// </summary>
private static readonly DateTime TimestampEpoch = EpochOrigin [ ( int ) DateTimeKind . Utc ] ;
/// <summary>
/// Writes a TimeSpan to a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
/// </summary>
public static void WriteTimeSpan ( TimeSpan timeSpan , ProtoWriter dest )
{
WriteTimeSpanImpl ( timeSpan , dest , DateTimeKind . Unspecified ) ;
}
private static void WriteTimeSpanImpl ( TimeSpan timeSpan , ProtoWriter dest , DateTimeKind kind )
{
if ( dest = = null ) throw new ArgumentNullException ( nameof ( dest ) ) ;
long value ;
switch ( dest . WireType )
{
case WireType . String :
case WireType . StartGroup :
TimeSpanScale scale ;
value = timeSpan . Ticks ;
if ( timeSpan = = TimeSpan . MaxValue )
{
value = 1 ;
scale = TimeSpanScale . MinMax ;
}
else if ( timeSpan = = TimeSpan . MinValue )
{
value = - 1 ;
scale = TimeSpanScale . MinMax ;
}
else if ( value % TimeSpan . TicksPerDay = = 0 )
{
scale = TimeSpanScale . Days ;
value / = TimeSpan . TicksPerDay ;
}
else if ( value % TimeSpan . TicksPerHour = = 0 )
{
scale = TimeSpanScale . Hours ;
value / = TimeSpan . TicksPerHour ;
}
else if ( value % TimeSpan . TicksPerMinute = = 0 )
{
scale = TimeSpanScale . Minutes ;
value / = TimeSpan . TicksPerMinute ;
}
else if ( value % TimeSpan . TicksPerSecond = = 0 )
{
scale = TimeSpanScale . Seconds ;
value / = TimeSpan . TicksPerSecond ;
}
else if ( value % TimeSpan . TicksPerMillisecond = = 0 )
{
scale = TimeSpanScale . Milliseconds ;
value / = TimeSpan . TicksPerMillisecond ;
}
else
{
scale = TimeSpanScale . Ticks ;
}
SubItemToken token = ProtoWriter . StartSubItem ( null , dest ) ;
if ( value ! = 0 )
{
ProtoWriter . WriteFieldHeader ( FieldTimeSpanValue , WireType . SignedVariant , dest ) ;
ProtoWriter . WriteInt64 ( value , dest ) ;
}
if ( scale ! = TimeSpanScale . Days )
{
ProtoWriter . WriteFieldHeader ( FieldTimeSpanScale , WireType . Variant , dest ) ;
ProtoWriter . WriteInt32 ( ( int ) scale , dest ) ;
}
if ( kind ! = DateTimeKind . Unspecified )
{
ProtoWriter . WriteFieldHeader ( FieldTimeSpanKind , WireType . Variant , dest ) ;
ProtoWriter . WriteInt32 ( ( int ) kind , dest ) ;
}
ProtoWriter . EndSubItem ( token , dest ) ;
break ;
case WireType . Fixed64 :
ProtoWriter . WriteInt64 ( timeSpan . Ticks , dest ) ;
break ;
default :
throw new ProtoException ( "Unexpected wire-type: " + dest . WireType . ToString ( ) ) ;
}
}
/// <summary>
/// Parses a TimeSpan from a protobuf stream using protobuf-net's own representation, bcl.TimeSpan
/// </summary>
public static TimeSpan ReadTimeSpan ( ProtoReader source )
{
long ticks = ReadTimeSpanTicks ( source , out DateTimeKind kind ) ;
if ( ticks = = long . MinValue ) return TimeSpan . MinValue ;
if ( ticks = = long . MaxValue ) return TimeSpan . MaxValue ;
return TimeSpan . FromTicks ( ticks ) ;
}
/// <summary>
/// Parses a TimeSpan from a protobuf stream using the standardized format, google.protobuf.Duration
/// </summary>
public static TimeSpan ReadDuration ( ProtoReader source )
{
long seconds = 0 ;
int nanos = 0 ;
SubItemToken token = ProtoReader . StartSubItem ( source ) ;
int fieldNumber ;
while ( ( fieldNumber = source . ReadFieldHeader ( ) ) > 0 )
{
switch ( fieldNumber )
{
case 1 :
seconds = source . ReadInt64 ( ) ;
break ;
case 2 :
nanos = source . ReadInt32 ( ) ;
break ;
default :
source . SkipField ( ) ;
break ;
}
}
ProtoReader . EndSubItem ( token , source ) ;
return FromDurationSeconds ( seconds , nanos ) ;
}
/// <summary>
/// Writes a TimeSpan to a protobuf stream using the standardized format, google.protobuf.Duration
/// </summary>
public static void WriteDuration ( TimeSpan value , ProtoWriter dest )
{
2021-04-11 19:50:39 +08:00
long seconds = ToDurationSeconds ( value , out int nanos ) ;
2021-04-08 20:09:59 +08:00
WriteSecondsNanos ( seconds , nanos , dest ) ;
}
private static void WriteSecondsNanos ( long seconds , int nanos , ProtoWriter dest )
{
SubItemToken token = ProtoWriter . StartSubItem ( null , dest ) ;
if ( seconds ! = 0 )
{
ProtoWriter . WriteFieldHeader ( 1 , WireType . Variant , dest ) ;
ProtoWriter . WriteInt64 ( seconds , dest ) ;
}
if ( nanos ! = 0 )
{
ProtoWriter . WriteFieldHeader ( 2 , WireType . Variant , dest ) ;
ProtoWriter . WriteInt32 ( nanos , dest ) ;
}
ProtoWriter . EndSubItem ( token , dest ) ;
}
/// <summary>
/// Parses a DateTime from a protobuf stream using the standardized format, google.protobuf.Timestamp
/// </summary>
public static DateTime ReadTimestamp ( ProtoReader source )
{
// note: DateTime is only defined for just over 0000 to just below 10000;
// TimeSpan has a range of +/- 10,675,199 days === 29k years;
// so we can just use epoch time delta
return TimestampEpoch + ReadDuration ( source ) ;
}
/// <summary>
/// Writes a DateTime to a protobuf stream using the standardized format, google.protobuf.Timestamp
/// </summary>
public static void WriteTimestamp ( DateTime value , ProtoWriter dest )
{
2021-04-11 19:50:39 +08:00
long seconds = ToDurationSeconds ( value - TimestampEpoch , out int nanos ) ;
2021-04-08 20:09:59 +08:00
if ( nanos < 0 )
{ // from Timestamp.proto:
// "Negative second values with fractions must still have
// non -negative nanos values that count forward in time."
seconds - - ;
nanos + = 1000000000 ;
}
WriteSecondsNanos ( seconds , nanos , dest ) ;
}
static TimeSpan FromDurationSeconds ( long seconds , int nanos )
{
long ticks = checked ( ( seconds * TimeSpan . TicksPerSecond )
+ ( nanos * TimeSpan . TicksPerMillisecond ) / 1000000 ) ;
return TimeSpan . FromTicks ( ticks ) ;
}
static long ToDurationSeconds ( TimeSpan value , out int nanos )
{
nanos = ( int ) ( ( ( value . Ticks % TimeSpan . TicksPerSecond ) * 1000000 )
/ TimeSpan . TicksPerMillisecond ) ;
return value . Ticks / TimeSpan . TicksPerSecond ;
}
/// <summary>
/// Parses a DateTime from a protobuf stream
/// </summary>
public static DateTime ReadDateTime ( ProtoReader source )
{
long ticks = ReadTimeSpanTicks ( source , out DateTimeKind kind ) ;
if ( ticks = = long . MinValue ) return DateTime . MinValue ;
if ( ticks = = long . MaxValue ) return DateTime . MaxValue ;
return EpochOrigin [ ( int ) kind ] . AddTicks ( ticks ) ;
}
/// <summary>
/// Writes a DateTime to a protobuf stream, excluding the <c>Kind</c>
/// </summary>
public static void WriteDateTime ( DateTime value , ProtoWriter dest )
{
WriteDateTimeImpl ( value , dest , false ) ;
}
/// <summary>
/// Writes a DateTime to a protobuf stream, including the <c>Kind</c>
/// </summary>
public static void WriteDateTimeWithKind ( DateTime value , ProtoWriter dest )
{
WriteDateTimeImpl ( value , dest , true ) ;
}
private static void WriteDateTimeImpl ( DateTime value , ProtoWriter dest , bool includeKind )
{
if ( dest = = null ) throw new ArgumentNullException ( nameof ( dest ) ) ;
TimeSpan delta ;
switch ( dest . WireType )
{
case WireType . StartGroup :
case WireType . String :
if ( value = = DateTime . MaxValue )
{
delta = TimeSpan . MaxValue ;
includeKind = false ;
}
else if ( value = = DateTime . MinValue )
{
delta = TimeSpan . MinValue ;
includeKind = false ;
}
else
{
delta = value - EpochOrigin [ 0 ] ;
}
break ;
default :
delta = value - EpochOrigin [ 0 ] ;
break ;
}
WriteTimeSpanImpl ( delta , dest , includeKind ? value . Kind : DateTimeKind . Unspecified ) ;
}
private static long ReadTimeSpanTicks ( ProtoReader source , out DateTimeKind kind )
{
kind = DateTimeKind . Unspecified ;
switch ( source . WireType )
{
case WireType . String :
case WireType . StartGroup :
SubItemToken token = ProtoReader . StartSubItem ( source ) ;
int fieldNumber ;
TimeSpanScale scale = TimeSpanScale . Days ;
long value = 0 ;
while ( ( fieldNumber = source . ReadFieldHeader ( ) ) > 0 )
{
switch ( fieldNumber )
{
case FieldTimeSpanScale :
scale = ( TimeSpanScale ) source . ReadInt32 ( ) ;
break ;
case FieldTimeSpanValue :
source . Assert ( WireType . SignedVariant ) ;
value = source . ReadInt64 ( ) ;
break ;
case FieldTimeSpanKind :
kind = ( DateTimeKind ) source . ReadInt32 ( ) ;
switch ( kind )
{
case DateTimeKind . Unspecified :
case DateTimeKind . Utc :
case DateTimeKind . Local :
break ; // fine
default :
throw new ProtoException ( "Invalid date/time kind: " + kind . ToString ( ) ) ;
}
break ;
default :
source . SkipField ( ) ;
break ;
}
}
ProtoReader . EndSubItem ( token , source ) ;
switch ( scale )
{
case TimeSpanScale . Days :
return value * TimeSpan . TicksPerDay ;
case TimeSpanScale . Hours :
return value * TimeSpan . TicksPerHour ;
case TimeSpanScale . Minutes :
return value * TimeSpan . TicksPerMinute ;
case TimeSpanScale . Seconds :
return value * TimeSpan . TicksPerSecond ;
case TimeSpanScale . Milliseconds :
return value * TimeSpan . TicksPerMillisecond ;
case TimeSpanScale . Ticks :
return value ;
case TimeSpanScale . MinMax :
switch ( value )
{
case 1 : return long . MaxValue ;
case - 1 : return long . MinValue ;
default : throw new ProtoException ( "Unknown min/max value: " + value . ToString ( ) ) ;
}
default :
throw new ProtoException ( "Unknown timescale: " + scale . ToString ( ) ) ;
}
case WireType . Fixed64 :
return source . ReadInt64 ( ) ;
default :
throw new ProtoException ( "Unexpected wire-type: " + source . WireType . ToString ( ) ) ;
}
}
const int FieldDecimalLow = 0x01 , FieldDecimalHigh = 0x02 , FieldDecimalSignScale = 0x03 ;
/// <summary>
/// Parses a decimal from a protobuf stream
/// </summary>
public static decimal ReadDecimal ( ProtoReader reader )
{
ulong low = 0 ;
uint high = 0 ;
uint signScale = 0 ;
int fieldNumber ;
SubItemToken token = ProtoReader . StartSubItem ( reader ) ;
while ( ( fieldNumber = reader . ReadFieldHeader ( ) ) > 0 )
{
switch ( fieldNumber )
{
case FieldDecimalLow : low = reader . ReadUInt64 ( ) ; break ;
case FieldDecimalHigh : high = reader . ReadUInt32 ( ) ; break ;
case FieldDecimalSignScale : signScale = reader . ReadUInt32 ( ) ; break ;
default : reader . SkipField ( ) ; break ;
}
}
ProtoReader . EndSubItem ( token , reader ) ;
int lo = ( int ) ( low & 0xFFFFFFFFL ) ,
mid = ( int ) ( ( low > > 32 ) & 0xFFFFFFFFL ) ,
hi = ( int ) high ;
bool isNeg = ( signScale & 0x0001 ) = = 0x0001 ;
byte scale = ( byte ) ( ( signScale & 0x01FE ) > > 1 ) ;
return new decimal ( lo , mid , hi , isNeg , scale ) ;
}
/// <summary>
/// Writes a decimal to a protobuf stream
/// </summary>
public static void WriteDecimal ( decimal value , ProtoWriter writer )
{
int [ ] bits = decimal . GetBits ( value ) ;
ulong a = ( ( ulong ) bits [ 1 ] ) < < 32 , b = ( ( ulong ) bits [ 0 ] ) & 0xFFFFFFFFL ;
ulong low = a | b ;
uint high = ( uint ) bits [ 2 ] ;
uint signScale = ( uint ) ( ( ( bits [ 3 ] > > 15 ) & 0x01FE ) | ( ( bits [ 3 ] > > 31 ) & 0x0001 ) ) ;
SubItemToken token = ProtoWriter . StartSubItem ( null , writer ) ;
if ( low ! = 0 )
{
ProtoWriter . WriteFieldHeader ( FieldDecimalLow , WireType . Variant , writer ) ;
ProtoWriter . WriteUInt64 ( low , writer ) ;
}
if ( high ! = 0 )
{
ProtoWriter . WriteFieldHeader ( FieldDecimalHigh , WireType . Variant , writer ) ;
ProtoWriter . WriteUInt32 ( high , writer ) ;
}
if ( signScale ! = 0 )
{
ProtoWriter . WriteFieldHeader ( FieldDecimalSignScale , WireType . Variant , writer ) ;
ProtoWriter . WriteUInt32 ( signScale , writer ) ;
}
ProtoWriter . EndSubItem ( token , writer ) ;
}
const int FieldGuidLow = 1 , FieldGuidHigh = 2 ;
/// <summary>
/// Writes a Guid to a protobuf stream
/// </summary>
public static void WriteGuid ( Guid value , ProtoWriter dest )
{
byte [ ] blob = value . ToByteArray ( ) ;
SubItemToken token = ProtoWriter . StartSubItem ( null , dest ) ;
if ( value ! = Guid . Empty )
{
ProtoWriter . WriteFieldHeader ( FieldGuidLow , WireType . Fixed64 , dest ) ;
ProtoWriter . WriteBytes ( blob , 0 , 8 , dest ) ;
ProtoWriter . WriteFieldHeader ( FieldGuidHigh , WireType . Fixed64 , dest ) ;
ProtoWriter . WriteBytes ( blob , 8 , 8 , dest ) ;
}
ProtoWriter . EndSubItem ( token , dest ) ;
}
/// <summary>
/// Parses a Guid from a protobuf stream
/// </summary>
public static Guid ReadGuid ( ProtoReader source )
{
ulong low = 0 , high = 0 ;
int fieldNumber ;
SubItemToken token = ProtoReader . StartSubItem ( source ) ;
while ( ( fieldNumber = source . ReadFieldHeader ( ) ) > 0 )
{
switch ( fieldNumber )
{
case FieldGuidLow : low = source . ReadUInt64 ( ) ; break ;
case FieldGuidHigh : high = source . ReadUInt64 ( ) ; break ;
default : source . SkipField ( ) ; break ;
}
}
ProtoReader . EndSubItem ( token , source ) ;
if ( low = = 0 & & high = = 0 ) return Guid . Empty ;
uint a = ( uint ) ( low > > 32 ) , b = ( uint ) low , c = ( uint ) ( high > > 32 ) , d = ( uint ) high ;
return new Guid ( ( int ) b , ( short ) a , ( short ) ( a > > 16 ) ,
( byte ) d , ( byte ) ( d > > 8 ) , ( byte ) ( d > > 16 ) , ( byte ) ( d > > 24 ) ,
( byte ) c , ( byte ) ( c > > 8 ) , ( byte ) ( c > > 16 ) , ( byte ) ( c > > 24 ) ) ;
}
private const int
FieldExistingObjectKey = 1 ,
FieldNewObjectKey = 2 ,
FieldExistingTypeKey = 3 ,
FieldNewTypeKey = 4 ,
FieldTypeName = 8 ,
FieldObject = 10 ;
/// <summary>
/// Optional behaviours that introduce .NET-specific functionality
/// </summary>
[Flags]
public enum NetObjectOptions : byte
{
/// <summary>
/// No special behaviour
/// </summary>
None = 0 ,
/// <summary>
/// Enables full object-tracking/full-graph support.
/// </summary>
AsReference = 1 ,
/// <summary>
/// Embeds the type information into the stream, allowing usage with types not known in advance.
/// </summary>
DynamicType = 2 ,
/// <summary>
/// If false, the constructor for the type is bypassed during deserialization, meaning any field initializers
/// or other initialization code is skipped.
/// </summary>
UseConstructor = 4 ,
/// <summary>
/// Should the object index be reserved, rather than creating an object promptly
/// </summary>
LateSet = 8
}
/// <summary>
/// Reads an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
/// </summary>
public static object ReadNetObject ( object value , ProtoReader source , int key , Type type , NetObjectOptions options )
{
SubItemToken token = ProtoReader . StartSubItem ( source ) ;
int fieldNumber ;
int newObjectKey = - 1 , newTypeKey = - 1 , tmp ;
while ( ( fieldNumber = source . ReadFieldHeader ( ) ) > 0 )
{
switch ( fieldNumber )
{
case FieldExistingObjectKey :
tmp = source . ReadInt32 ( ) ;
value = source . NetCache . GetKeyedObject ( tmp ) ;
break ;
case FieldNewObjectKey :
newObjectKey = source . ReadInt32 ( ) ;
break ;
case FieldExistingTypeKey :
tmp = source . ReadInt32 ( ) ;
type = ( Type ) source . NetCache . GetKeyedObject ( tmp ) ;
key = source . GetTypeKey ( ref type ) ;
break ;
case FieldNewTypeKey :
newTypeKey = source . ReadInt32 ( ) ;
break ;
case FieldTypeName :
string typeName = source . ReadString ( ) ;
type = source . DeserializeType ( typeName ) ;
if ( type = = null )
{
throw new ProtoException ( "Unable to resolve type: " + typeName + " (you can use the TypeModel.DynamicTypeFormatting event to provide a custom mapping)" ) ;
}
if ( type = = typeof ( string ) )
{
key = - 1 ;
}
else
{
key = source . GetTypeKey ( ref type ) ;
if ( key < 0 )
throw new InvalidOperationException ( "Dynamic type is not a contract-type: " + type . Name ) ;
}
break ;
case FieldObject :
bool isString = type = = typeof ( string ) ;
bool wasNull = value = = null ;
bool lateSet = wasNull & & ( isString | | ( ( options & NetObjectOptions . LateSet ) ! = 0 ) ) ;
if ( newObjectKey > = 0 & & ! lateSet )
{
if ( value = = null )
{
source . TrapNextObject ( newObjectKey ) ;
}
else
{
source . NetCache . SetKeyedObject ( newObjectKey , value ) ;
}
if ( newTypeKey > = 0 ) source . NetCache . SetKeyedObject ( newTypeKey , type ) ;
}
object oldValue = value ;
if ( isString )
{
value = source . ReadString ( ) ;
}
else
{
value = ProtoReader . ReadTypedObject ( oldValue , key , source , type ) ;
}
if ( newObjectKey > = 0 )
{
if ( wasNull & & ! lateSet )
{ // this both ensures (via exception) that it *was* set, and makes sure we don't shout
// about changed references
oldValue = source . NetCache . GetKeyedObject ( newObjectKey ) ;
}
if ( lateSet )
{
source . NetCache . SetKeyedObject ( newObjectKey , value ) ;
if ( newTypeKey > = 0 ) source . NetCache . SetKeyedObject ( newTypeKey , type ) ;
}
}
if ( newObjectKey > = 0 & & ! lateSet & & ! ReferenceEquals ( oldValue , value ) )
{
throw new ProtoException ( "A reference-tracked object changed reference during deserialization" ) ;
}
if ( newObjectKey < 0 & & newTypeKey > = 0 )
{ // have a new type, but not a new object
source . NetCache . SetKeyedObject ( newTypeKey , type ) ;
}
break ;
default :
source . SkipField ( ) ;
break ;
}
}
if ( newObjectKey > = 0 & & ( options & NetObjectOptions . AsReference ) = = 0 )
{
throw new ProtoException ( "Object key in input stream, but reference-tracking was not expected" ) ;
}
ProtoReader . EndSubItem ( token , source ) ;
return value ;
}
/// <summary>
/// Writes an *implementation specific* bundled .NET object, including (as options) type-metadata, identity/re-use, etc.
/// </summary>
public static void WriteNetObject ( object value , ProtoWriter dest , int key , NetObjectOptions options )
{
if ( dest = = null ) throw new ArgumentNullException ( "dest" ) ;
bool dynamicType = ( options & NetObjectOptions . DynamicType ) ! = 0 ,
asReference = ( options & NetObjectOptions . AsReference ) ! = 0 ;
WireType wireType = dest . WireType ;
SubItemToken token = ProtoWriter . StartSubItem ( null , dest ) ;
bool writeObject = true ;
if ( asReference )
{
int objectKey = dest . NetCache . AddObjectKey ( value , out bool existing ) ;
ProtoWriter . WriteFieldHeader ( existing ? FieldExistingObjectKey : FieldNewObjectKey , WireType . Variant , dest ) ;
ProtoWriter . WriteInt32 ( objectKey , dest ) ;
if ( existing )
{
writeObject = false ;
}
}
if ( writeObject )
{
if ( dynamicType )
{
Type type = value . GetType ( ) ;
if ( ! ( value is string ) )
{
key = dest . GetTypeKey ( ref type ) ;
if ( key < 0 ) throw new InvalidOperationException ( "Dynamic type is not a contract-type: " + type . Name ) ;
}
int typeKey = dest . NetCache . AddObjectKey ( type , out bool existing ) ;
ProtoWriter . WriteFieldHeader ( existing ? FieldExistingTypeKey : FieldNewTypeKey , WireType . Variant , dest ) ;
ProtoWriter . WriteInt32 ( typeKey , dest ) ;
if ( ! existing )
{
ProtoWriter . WriteFieldHeader ( FieldTypeName , WireType . String , dest ) ;
ProtoWriter . WriteString ( dest . SerializeType ( type ) , dest ) ;
}
}
ProtoWriter . WriteFieldHeader ( FieldObject , wireType , dest ) ;
if ( value is string )
{
ProtoWriter . WriteString ( ( string ) value , dest ) ;
}
else
{
ProtoWriter . WriteObject ( value , key , dest ) ;
}
}
ProtoWriter . EndSubItem ( token , dest ) ;
}
}
}