@@ -70,7 +70,6 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
70
70
var serializer = BsonSerializer . LookupSerializer ( actualType ) ;
71
71
if ( serializer != this )
72
72
{
73
- // in rare cases a concrete actualType might have a more specialized serializer
74
73
return serializer . Deserialize ( bsonReader , nominalType , actualType , options ) ;
75
74
}
76
75
}
@@ -102,18 +101,24 @@ public object Deserialize(
102
101
}
103
102
else
104
103
{
104
+ if ( actualType != _classMap . ClassType )
105
+ {
106
+ var message = string . Format ( "BsonClassMapSerializer.Deserialize for type {0} was called with actualType {1}." ,
107
+ BsonUtils . GetFriendlyTypeName ( _classMap . ClassType ) , BsonUtils . GetFriendlyTypeName ( actualType ) ) ;
108
+ throw new BsonSerializationException ( message ) ;
109
+ }
110
+
105
111
if ( actualType . IsValueType )
106
112
{
107
113
var message = string . Format ( "Value class {0} cannot be deserialized." , actualType . FullName ) ;
108
114
throw new BsonSerializationException ( message ) ;
109
115
}
110
116
111
- var classMap = BsonClassMap . LookupClassMap ( actualType ) ;
112
- if ( classMap . IsAnonymous )
117
+ if ( _classMap . IsAnonymous )
113
118
{
114
119
throw new InvalidOperationException ( "An anonymous class cannot be deserialized." ) ;
115
120
}
116
- var obj = classMap . CreateInstance ( ) ;
121
+ var obj = _classMap . CreateInstance ( ) ;
117
122
118
123
if ( bsonType != BsonType . Document )
119
124
{
@@ -129,9 +134,10 @@ public object Deserialize(
129
134
supportsInitialization . BeginInit ( ) ;
130
135
}
131
136
137
+ var discriminatorConvention = _classMap . GetDiscriminatorConvention ( ) ;
138
+ var fastMemberMapFinder = new FastMemberMapFinder ( _classMap ) ;
139
+
132
140
bsonReader . ReadStartDocument ( ) ;
133
- var missingElementMemberMaps = new HashSet < BsonMemberMap > ( classMap . AllMemberMaps ) ; // make a copy!
134
- var discriminatorConvention = classMap . GetDiscriminatorConvention ( ) ;
135
141
while ( bsonReader . ReadBsonType ( ) != BsonType . EndOfDocument )
136
142
{
137
143
var elementName = bsonReader . ReadName ( ) ;
@@ -141,8 +147,8 @@ public object Deserialize(
141
147
continue ;
142
148
}
143
149
144
- var memberMap = classMap . GetMemberMapForElement ( elementName ) ;
145
- if ( memberMap != null && memberMap != classMap . ExtraElementsMemberMap )
150
+ var memberMap = fastMemberMapFinder . GetMemberMapForElement ( elementName ) ;
151
+ if ( memberMap != null )
146
152
{
147
153
if ( memberMap . IsReadOnly )
148
154
{
@@ -152,47 +158,49 @@ public object Deserialize(
152
158
{
153
159
DeserializeMember ( bsonReader , obj , memberMap ) ;
154
160
}
155
- missingElementMemberMaps . Remove ( memberMap ) ;
156
161
}
157
162
else
158
163
{
159
- if ( classMap . ExtraElementsMemberMap != null )
164
+ if ( _classMap . ExtraElementsMemberMap != null )
160
165
{
161
- DeserializeExtraElement ( bsonReader , obj , elementName , classMap . ExtraElementsMemberMap ) ;
162
- missingElementMemberMaps . Remove ( classMap . ExtraElementsMemberMap ) ;
166
+ DeserializeExtraElement ( bsonReader , obj , elementName , _classMap . ExtraElementsMemberMap ) ;
163
167
}
164
- else if ( classMap . IgnoreExtraElements )
168
+ else if ( _classMap . IgnoreExtraElements )
165
169
{
166
170
bsonReader . SkipValue ( ) ;
167
171
}
168
172
else
169
173
{
170
174
var message = string . Format (
171
175
"Element '{0}' does not match any field or property of class {1}." ,
172
- elementName , classMap . ClassType . FullName ) ;
176
+ elementName , _classMap . ClassType . FullName ) ;
173
177
throw new FileFormatException ( message ) ;
174
178
}
175
179
}
176
180
}
177
181
bsonReader . ReadEndDocument ( ) ;
178
182
179
- foreach ( var memberMap in missingElementMemberMaps )
183
+ // check any members left over that we didn't have elements for
184
+ if ( fastMemberMapFinder . HasLeftOverMemberMaps ( ) )
180
185
{
181
- if ( memberMap . IsReadOnly )
186
+ foreach ( var memberMap in fastMemberMapFinder . GetLeftOverMemberMaps ( ) )
182
187
{
183
- continue ;
184
- }
188
+ if ( memberMap . IsReadOnly )
189
+ {
190
+ continue ;
191
+ }
185
192
186
- if ( memberMap . IsRequired )
187
- {
188
- var fieldOrProperty = ( memberMap . MemberInfo . MemberType == MemberTypes . Field ) ? "field" : "property" ;
189
- var message = string . Format (
190
- "Required element '{0}' for {1} '{2}' of class {3} is missing." ,
191
- memberMap . ElementName , fieldOrProperty , memberMap . MemberName , classMap . ClassType . FullName ) ;
192
- throw new FileFormatException ( message ) ;
193
- }
193
+ if ( memberMap . IsRequired )
194
+ {
195
+ var fieldOrProperty = ( memberMap . MemberInfo . MemberType == MemberTypes . Field ) ? "field" : "property" ;
196
+ var message = string . Format (
197
+ "Required element '{0}' for {1} '{2}' of class {3} is missing." ,
198
+ memberMap . ElementName , fieldOrProperty , memberMap . MemberName , _classMap . ClassType . FullName ) ;
199
+ throw new FileFormatException ( message ) ;
200
+ }
194
201
195
- memberMap . ApplyDefaultValue ( obj ) ;
202
+ memberMap . ApplyDefaultValue ( obj ) ;
203
+ }
196
204
}
197
205
198
206
if ( supportsInitialization != null )
@@ -227,8 +235,7 @@ public bool GetDocumentId(
227
235
out Type idNominalType ,
228
236
out IIdGenerator idGenerator )
229
237
{
230
- var classMap = BsonClassMap . LookupClassMap ( document . GetType ( ) ) ;
231
- var idMemberMap = classMap . IdMemberMap ;
238
+ var idMemberMap = _classMap . IdMemberMap ;
232
239
if ( idMemberMap != null )
233
240
{
234
241
id = idMemberMap . Getter ( document ) ;
@@ -309,7 +316,12 @@ public void Serialize(
309
316
310
317
VerifyNominalType ( nominalType ) ;
311
318
var actualType = ( value == null ) ? nominalType : value . GetType ( ) ;
312
- var classMap = BsonClassMap . LookupClassMap ( actualType ) ;
319
+ if ( actualType != _classMap . ClassType )
320
+ {
321
+ var message = string . Format ( "BsonClassMapSerializer.Serialize for type {0} was called with actualType {1}." ,
322
+ BsonUtils . GetFriendlyTypeName ( _classMap . ClassType ) , BsonUtils . GetFriendlyTypeName ( actualType ) ) ;
323
+ throw new BsonSerializationException ( message ) ;
324
+ }
313
325
314
326
var documentSerializationOptions = ( options ?? DocumentSerializationOptions . Defaults ) as DocumentSerializationOptions ;
315
327
if ( documentSerializationOptions == null )
@@ -325,19 +337,19 @@ public void Serialize(
325
337
BsonMemberMap idMemberMap = null ;
326
338
if ( documentSerializationOptions . SerializeIdFirst )
327
339
{
328
- idMemberMap = classMap . IdMemberMap ;
340
+ idMemberMap = _classMap . IdMemberMap ;
329
341
if ( idMemberMap != null )
330
342
{
331
343
SerializeMember ( bsonWriter , value , idMemberMap ) ;
332
344
}
333
345
}
334
346
335
- if ( actualType != nominalType || classMap . DiscriminatorIsRequired || classMap . HasRootClass )
347
+ if ( actualType != nominalType || _classMap . DiscriminatorIsRequired || _classMap . HasRootClass )
336
348
{
337
349
// never write out a discriminator for an anonymous class
338
- if ( ! classMap . IsAnonymous )
350
+ if ( ! _classMap . IsAnonymous )
339
351
{
340
- var discriminatorConvention = classMap . GetDiscriminatorConvention ( ) ;
352
+ var discriminatorConvention = _classMap . GetDiscriminatorConvention ( ) ;
341
353
var discriminator = discriminatorConvention . GetDiscriminator ( nominalType , actualType ) ;
342
354
if ( discriminator != null )
343
355
{
@@ -347,12 +359,12 @@ public void Serialize(
347
359
}
348
360
}
349
361
350
- foreach ( var memberMap in classMap . AllMemberMaps )
362
+ foreach ( var memberMap in _classMap . AllMemberMaps )
351
363
{
352
364
// note: if serializeIdFirst is false then idMemberMap will be null (so no property will be skipped)
353
365
if ( memberMap != idMemberMap )
354
366
{
355
- if ( memberMap == classMap . ExtraElementsMemberMap )
367
+ if ( memberMap == _classMap . ExtraElementsMemberMap )
356
368
{
357
369
SerializeExtraElements ( bsonWriter , value , memberMap ) ;
358
370
}
@@ -380,8 +392,7 @@ public void SetDocumentId(object document, object id)
380
392
throw new BsonSerializationException ( message ) ;
381
393
}
382
394
383
- var classMap = BsonClassMap . LookupClassMap ( documentType ) ;
384
- var idMemberMap = classMap . IdMemberMap ;
395
+ var idMemberMap = _classMap . IdMemberMap ;
385
396
if ( idMemberMap != null )
386
397
{
387
398
idMemberMap . Setter ( document , id ) ;
@@ -518,5 +529,93 @@ private void VerifyNominalType(Type nominalType)
518
529
throw new BsonSerializationException ( message ) ;
519
530
}
520
531
}
532
+
533
+ // nested classes
534
+ // helper class that implements fast linear searching of member maps by using a shrinking range
535
+ // and optimized for the case when the elements occur in the same order as the maps
536
+ private class FastMemberMapFinder
537
+ {
538
+ private BsonClassMap _classMap ;
539
+ private BsonMemberMap _extraElementsMemberMap ;
540
+ private BsonMemberMap [ ] _memberMaps ;
541
+ private int _from ;
542
+ private int _to ;
543
+
544
+ public FastMemberMapFinder ( BsonClassMap classMap )
545
+ {
546
+ _classMap = classMap ;
547
+ _extraElementsMemberMap = classMap . ExtraElementsMemberMap ;
548
+ _memberMaps = classMap . AllMemberMaps . ToArray ( ) ;
549
+ _from = 0 ;
550
+ _to = _memberMaps . Length - 1 ;
551
+ if ( _extraElementsMemberMap != null )
552
+ {
553
+ var i = Array . IndexOf ( _memberMaps , _extraElementsMemberMap ) ;
554
+ _memberMaps [ i ] = null ;
555
+ }
556
+ }
557
+
558
+ public BsonMemberMap GetMemberMapForElement ( string elementName )
559
+ {
560
+ // linear search should be fast because elements will normally be in the same order as the member maps
561
+ for ( int i = _from ; i <= _to ; i ++ )
562
+ {
563
+ var memberMap = _memberMaps [ i ] ;
564
+ if ( memberMap == null )
565
+ {
566
+ if ( i == _from )
567
+ {
568
+ _from ++ ; // shrink the range from the left
569
+ }
570
+ continue ;
571
+ }
572
+
573
+ if ( memberMap . ElementName == elementName )
574
+ {
575
+ if ( i == _from )
576
+ {
577
+ _from ++ ; // shrink the range from the left
578
+ }
579
+ else if ( i == _to )
580
+ {
581
+ _to -- ; // shrink the range from the right
582
+ }
583
+ else
584
+ {
585
+ _memberMaps [ i ] = null ; // set to null so we don't think it's missing
586
+ }
587
+ return memberMap ;
588
+ }
589
+ }
590
+
591
+ // fall back to the class map in case it's a duplicate element name that we've already null'ed out
592
+ // but make sure not to return the extraElementsMemberMap
593
+ if ( _extraElementsMemberMap == null || elementName != _extraElementsMemberMap . ElementName )
594
+ {
595
+ return _classMap . GetMemberMapForElement ( elementName ) ;
596
+ }
597
+ else
598
+ {
599
+ return null ;
600
+ }
601
+ }
602
+
603
+ public bool HasLeftOverMemberMaps ( )
604
+ {
605
+ for ( int i = _from ; i <= _to ; i ++ )
606
+ {
607
+ if ( _memberMaps [ i ] != null )
608
+ {
609
+ return true ;
610
+ }
611
+ }
612
+ return false ;
613
+ }
614
+
615
+ public IEnumerable < BsonMemberMap > GetLeftOverMemberMaps ( )
616
+ {
617
+ return _memberMaps . Where ( ( m , i ) => ( i >= _from ) && ( i <= _to ) && ( m != null ) ) ;
618
+ }
619
+ }
521
620
}
522
621
}
0 commit comments