Class OwnedRecord
- Namespace
- DatabentoDotNet.Dbn
- Assembly
- DatabentoDotNet.Dbn.dll
A record copied out of the decoder's buffer and onto the heap, free of the buffer's lifetime. The counterpart of RecordRef: same bytes, same accessors, opposite ownership.
public sealed class OwnedRecord
- Inheritance
-
OwnedRecord
- Inherited Members
Examples
// The zero-copy path: nothing allocated per record, and each record dies at the next call.
while (client.TryNextRecord(out RecordRef record))
{
Process(record);
}
// The owning path: two allocations per record, and the records outlive the loop.
var trades = new List<OwnedRecord>();
await foreach (OwnedRecord record in client.RecordsAsync())
{
if (record.Has<TradeMsg>())
{
trades.Add(record);
}
}
// Same accessors on both types, and AsRef() hands back a zero-copy view over the copy.
foreach (OwnedRecord record in trades)
{
ref readonly TradeMsg trade = ref record.Get<TradeMsg>();
Console.WriteLine($"{DbnTime.ToInstant(trade.IndexTs)} {trade.Price} x {trade.Size}");
}
Remarks
This type exists because RecordRef is a ref struct, and a
ref struct cannot cross an await or a yield return (CS4007). That
rules it out as the element type of an IAsyncEnumerable<T>, which is the
ergonomic surface most callers of a live stream will reach for. Something has to be copied for
that surface to exist at all, and this is the copy — made once, explicitly, at a boundary the
caller chose, rather than smuggled into the zero-copy path.
The price, stated rather than hidden: two allocations per record — the storage and this
object — where RecordRef costs none. On the low-level
FillBufferAsync/TryNextRecord pair nothing here is touched, which is what keeps
that path at zero managed bytes per record. Reach for this when convenience is worth the
allocation, and for the zero-copy path when it is not.
Storage is a ulong array, not a byte array, for the
same reason AlignedBuffer is: records are reinterpreted in place by
MemoryMarshal.AsRef<T>, which needs 8-byte alignment for correctness on platforms
that enforce it. The CLR aligns a ulong array's elements to eight bytes;
it makes no such promise about where a byte array's payload starts. A
byte array would be silently fine on x64 and a fault or a torn read
elsewhere — the failure mode this codec's alignment assertions exist to catch.
Properties
Bytes
The record's bytes, exactly SizeInBytes long, ts_out included.
public ReadOnlySpan<byte> Bytes { get; }
Property Value
HasTsOut
true when the stream appends an 8-byte ts_out to every record.
public bool HasTsOut { get; }
Property Value
Header
The common record header, read in place off this object's own storage.
public ref readonly RecordHeader Header { get; }
Property Value
IndexTs
The record's index timestamp: the one to sort by, and the one to key a symbol map with. Nanoseconds since the UNIX epoch. See IndexTs.
public ulong IndexTs { get; }
Property Value
SizeInBytes
The record's total length on the wire in bytes, ts_out included.
public int SizeInBytes { get; }
Property Value
StructSize
The length of the record struct itself: SizeInBytes minus the 8 bytes of
ts_out when the stream appends one.
public int StructSize { get; }
Property Value
TsOut
The live gateway's send timestamp appended after the record, in nanoseconds since the UNIX epoch.
public ulong TsOut { get; }
Property Value
Exceptions
- InvalidOperationException
The stream does not append
ts_out— see HasTsOut.
Methods
AsRef()
A RecordRef over this object's storage, for the accessors that are only worth writing once.
public RecordRef AsRef()
Returns
- RecordRef
A reference to the copied bytes.
Remarks
Safe to hold for as long as this object is reachable, unlike one obtained from a decoder:
the span points at a heap array this object owns and nothing ever moves or reuses it. It
is still a ref struct, so it cannot cross an await — hold the
OwnedRecord across the await and call this afterwards.
CopyOf(RecordRef)
Copies record's bytes onto the heap.
public static OwnedRecord CopyOf(RecordRef record)
Parameters
recordRecordRefThe record to copy. It is not retained.
Returns
- OwnedRecord
An independent copy, valid for as long as the caller keeps it.
Remarks
A static factory rather than a constructor because C# forbids a ref struct
parameter on a constructor of a non-ref type in some positions and, more usefully,
because CopyOf says at the call site what the call costs.
Get<T>()
Reinterprets this record as a T in place — no further copy.
public ref readonly T Get<T>() where T : unmanaged, IRecord<T>
Returns
- T
A read-only reference into this object's own storage.
Type Parameters
TThe record struct to read this record as.
Remarks
Written out rather than forwarded to Get<T>() like its neighbours, because a ref readonly returned through the temporary RecordRef that AsRef() produces cannot outlive it as far as the compiler's ref-safety analysis is concerned. Reading off Bytes directly ties the lifetime to this object, which is where it actually belongs. The neighbours forward freely because they all return by value.
Exceptions
- DbnDecodeException
This record is not a
T— see Has<T>().
Has<T>()
Reports whether this record is a T. See
Has<T>().
public bool Has<T>() where T : unmanaged, IRecord<T>
Returns
Type Parameters
TThe record struct to test for.
TryGet<T>(out T)
Copies this record out as a T if it is one. See
TryGet<T>(out T).
public bool TryGet<T>(out T record) where T : unmanaged, IRecord<T>
Parameters
recordTReceives a copy of the record, or default when this is not a
T.
Returns
Type Parameters
TThe record struct to read this record as.