C# · Platform Notes

Platform Notes

3.0.0-rc.1 · 4c3e5023

§ Interop

Every entry point is declared with [LibraryImport], so the marshalling stubs are source-generated and compiled in rather than reflected at runtime, and the assembly stays trimmable and ahead-of-time compilable. Every verb takes one argument struct, as the C interface does, and holds a reference on each SafeHandle the struct names for the length of the C call, so a finalizer can never free a handle out from under a running verb, and the free of one a deferred call still names waits for that call.

§ What This Binding Does Not Expose

  • Progress handlers. Every verb passes the C progress callback as null, so a long Insert, Refine, Absorb, Split, Sample or Save runs to its end, and Status.Cancelled never arises.
  • Deferred cursors and streams. Cursor and SearchStream pull blocking, so a DenseIndexDeferredOn lacks them.
  • Constructors on receivers. The constructors address no index yet, so no receiver offers them; the ones the C interface lets fan out take an executor argument and run on the caller’s thread without one.
  • Version numbers and class names. usearch_version and the CPU and GPU class spellings stay unbound; USearchCapabilities.Version() answers the version as text.

§ Design

  • A thin layer over the C ABI. Every name follows include/usearch/: a C verb usearch_dense_<verb> is a <Verb> method here, and enums mirror C enums, never strings.
  • The receiver chooses, the verb stays. The index runs a verb on the calling thread, On(executor) blocks on an executor, and Deferred(executor) submits it, all with the same arguments.
  • Deferred calls use the language’s own idioms. Status polls through usearch_outcome_status, Wait() blocks through usearch_outcome_wait, and await hands that wait to the thread pool.
  • No life of its own. The binding starts no threads and keeps no queues; releasing a Deferred rides Dispose and using, which waits for the call first.
  • Arenas are lent and reused, never shared. Every output whose length scales with the call lands in an arena the caller passes; a view the arena’s next call outdated throws rather than reading rewound memory.
  • Nothing costs anything until it is used. Submitting a call allocates nothing, and an arena allocates only when a call writes into it.
  • Blocking calls stay on the stack. A blocking call keeps its outputs on the stack, fixes its buffers in place without a pinning handle, and answers a view by value; a deferred one allocates its handle and one pinned outputs struct.
  • Exceptions never cross the native boundary. A predicate or rowOf delegate that throws is captured on the worker thread and rethrown with its own type once the call is final.