Skip to content

Commit cfdea15

Browse files
committed
Improved ObjectId parsing and ToString() performance
1 parent b11826d commit cfdea15

File tree

1 file changed

+84
-32
lines changed

1 file changed

+84
-32
lines changed

src/MongoDB.Bson/ObjectModel/ObjectId.cs

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId>, IConvertib
4141
private readonly int _c;
4242

4343
// constructors
44+
/// <summary>
45+
/// Initializes a new instance of the ObjectId class.
46+
/// </summary>
47+
/// <param name="a">First 32 bits</param>
48+
/// <param name="b">Second 32 bits</param>
49+
/// <param name="c">Third 32 bits</param>
50+
private ObjectId(int a, int b, int c)
51+
{
52+
_a = a; _b = b; _c = c;
53+
}
54+
4455
/// <summary>
4556
/// Initializes a new instance of the ObjectId class.
4657
/// </summary>
@@ -49,11 +60,11 @@ public ObjectId(byte[] bytes)
4960
{
5061
if (bytes == null)
5162
{
52-
throw new ArgumentNullException("bytes");
63+
throw new ArgumentNullException(nameof(bytes));
5364
}
5465
if (bytes.Length != 12)
5566
{
56-
throw new ArgumentException("Byte array must be 12 bytes long", "bytes");
67+
throw new ArgumentException("Byte array must be 12 bytes long", nameof(bytes));
5768
}
5869

5970
FromByteArray(bytes, 0, out _a, out _b, out _c);
@@ -92,16 +103,16 @@ public ObjectId(int timestamp, int machine, short pid, int increment)
92103
{
93104
if ((machine & 0xff000000) != 0)
94105
{
95-
throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
106+
throw new ArgumentOutOfRangeException(nameof(machine), "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
96107
}
97108
if ((increment & 0xff000000) != 0)
98109
{
99-
throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
110+
throw new ArgumentOutOfRangeException(nameof(increment), "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
100111
}
101112

102113
_a = timestamp;
103-
_b = (machine << 8) | (((int)pid >> 8) & 0xff);
104-
_c = ((int)pid << 24) | increment;
114+
_b = (machine << 8) | ((pid >> 8) & 0xff);
115+
_c = (pid << 24) | increment;
105116
}
106117

107118
/// <summary>
@@ -112,11 +123,13 @@ public ObjectId(string value)
112123
{
113124
if (value == null)
114125
{
115-
throw new ArgumentNullException("value");
126+
throw new ArgumentNullException(nameof(value));
116127
}
117128

118-
var bytes = BsonUtils.ParseHexString(value);
119-
FromByteArray(bytes, 0, out _a, out _b, out _c);
129+
if (!TryParse(value, out _a, out _b, out _c))
130+
{
131+
throw new ArgumentException("String value is not valid.", nameof(value));
132+
}
120133
}
121134

122135
// public static properties
@@ -279,11 +292,11 @@ public static byte[] Pack(int timestamp, int machine, short pid, int increment)
279292
{
280293
if ((machine & 0xff000000) != 0)
281294
{
282-
throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
295+
throw new ArgumentOutOfRangeException(nameof(machine), "The machine value must be between 0 and 16777215 (it must fit in 3 bytes).");
283296
}
284297
if ((increment & 0xff000000) != 0)
285298
{
286-
throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
299+
throw new ArgumentOutOfRangeException(nameof(increment), "The increment value must be between 0 and 16777215 (it must fit in 3 bytes).");
287300
}
288301

289302
byte[] bytes = new byte[12];
@@ -311,7 +324,7 @@ public static ObjectId Parse(string s)
311324
{
312325
if (s == null)
313326
{
314-
throw new ArgumentNullException("s");
327+
throw new ArgumentNullException(nameof(s));
315328
}
316329

317330
ObjectId objectId;
@@ -335,14 +348,11 @@ public static ObjectId Parse(string s)
335348
public static bool TryParse(string s, out ObjectId objectId)
336349
{
337350
// don't throw ArgumentNullException if s is null
338-
if (s != null && s.Length == 24)
351+
int a, b, c;
352+
if (TryParse(s, out a, out b, out c))
339353
{
340-
byte[] bytes;
341-
if (BsonUtils.TryParseHexString(s, out bytes))
342-
{
343-
objectId = new ObjectId(bytes);
344-
return true;
345-
}
354+
objectId = new ObjectId(a, b, c);
355+
return true;
346356
}
347357

348358
objectId = default(ObjectId);
@@ -361,11 +371,11 @@ public static void Unpack(byte[] bytes, out int timestamp, out int machine, out
361371
{
362372
if (bytes == null)
363373
{
364-
throw new ArgumentNullException("bytes");
374+
throw new ArgumentNullException(nameof(bytes));
365375
}
366376
if (bytes.Length != 12)
367377
{
368-
throw new ArgumentOutOfRangeException("bytes", "Byte array must be 12 bytes long.");
378+
throw new ArgumentOutOfRangeException(nameof(bytes), "Byte array must be 12 bytes long.");
369379
}
370380

371381
timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
@@ -429,6 +439,38 @@ private static int GetTimestampFromDateTime(DateTime timestamp)
429439
return (int)secondsSinceEpoch;
430440
}
431441

442+
private static bool TryParse(string value, out int a, out int b, out int c)
443+
{
444+
a = 0; b = 0; c = 0;
445+
446+
if (value == null || value.Length != 24)
447+
{
448+
return false;
449+
}
450+
451+
var err = 0;
452+
453+
for (int i = 0; i < 8 && err == 0; i++)
454+
{
455+
var ch = value[i] | 0x20;
456+
var r = (ch >= '0' && ch <= '9') ? (ch - '0') : (ch >= 'a' && ch <= 'f') ? (ch - 'a' + 10) : 16;
457+
a = (a << 4) | (r & 0x0F);
458+
err |= (r & 0xF0);
459+
460+
ch = value[i + 8] | 0x20;
461+
r = (ch >= '0' && ch <= '9') ? (ch - '0') : (ch >= 'a' && ch <= 'f') ? (ch - 'a' + 10) : 16;
462+
b = (b << 4) | (r & 0x0F);
463+
err |= (r & 0xF0);
464+
465+
ch = value[i + 16] | 0x20;
466+
r = (ch >= '0' && ch <= '9') ? (ch - '0') : (ch >= 'a' && ch <= 'f') ? (ch - 'a' + 10) : 16;
467+
c = (c << 4) | (r & 0x0F);
468+
err |= (r & 0xF0);
469+
}
470+
471+
return err == 0;
472+
}
473+
432474
private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c)
433475
{
434476
a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
@@ -471,14 +513,7 @@ public bool Equals(ObjectId rhs)
471513
/// <returns>True if the other object is an ObjectId and equal to this one.</returns>
472514
public override bool Equals(object obj)
473515
{
474-
if (obj is ObjectId)
475-
{
476-
return Equals((ObjectId)obj);
477-
}
478-
else
479-
{
480-
return false;
481-
}
516+
return obj is ObjectId && Equals((ObjectId)obj);
482517
}
483518

484519
/// <summary>
@@ -514,11 +549,11 @@ public void ToByteArray(byte[] destination, int offset)
514549
{
515550
if (destination == null)
516551
{
517-
throw new ArgumentNullException("destination");
552+
throw new ArgumentNullException(nameof(destination));
518553
}
519554
if (offset + 12 > destination.Length)
520555
{
521-
throw new ArgumentException("Not enough room in destination buffer.", "offset");
556+
throw new ArgumentException("Not enough room in destination buffer.", nameof(offset));
522557
}
523558

524559
destination[offset + 0] = (byte)(_a >> 24);
@@ -541,7 +576,24 @@ public void ToByteArray(byte[] destination, int offset)
541576
/// <returns>A string representation of the value.</returns>
542577
public override string ToString()
543578
{
544-
return BsonUtils.ToHexString(ToByteArray());
579+
var buffer = new char[24];
580+
581+
for (int i = 7, a = _a, b = _b, c = _c; i >= 0; i--)
582+
{
583+
var r = (a & 0x0F);
584+
buffer[i] = (char)(r < 10 ? (r + '0') : (r - 10 + 'a'));
585+
a >>= 4;
586+
587+
r = (b & 0x0F);
588+
buffer[i + 8] = (char)(r < 10 ? (r + '0') : (r - 10 + 'a'));
589+
b >>= 4;
590+
591+
r = (c & 0x0F);
592+
buffer[i + 16] = (char)(r < 10 ? (r + '0') : (r - 10 + 'a'));
593+
c >>= 4;
594+
}
595+
596+
return new string(buffer);
545597
}
546598

547599
// explicit IConvertible implementation

0 commit comments

Comments
 (0)