Table of Contents

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

ReadOnlySpan<byte>

HasTsOut

true when the stream appends an 8-byte ts_out to every record.

public bool HasTsOut { get; }

Property Value

bool

Header

The common record header, read in place off this object's own storage.

public ref readonly RecordHeader Header { get; }

Property Value

RecordHeader

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

ulong

SizeInBytes

The record's total length on the wire in bytes, ts_out included.

public int SizeInBytes { get; }

Property Value

int

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

int

TsOut

The live gateway's send timestamp appended after the record, in nanoseconds since the UNIX epoch.

public ulong TsOut { get; }

Property Value

ulong

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

record RecordRef

The 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

T

The 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

bool

true if Get<T>() would succeed.

Type Parameters

T

The 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

record T

Receives a copy of the record, or default when this is not a T.

Returns

bool

true if this record is a T.

Type Parameters

T

The record struct to read this record as.