Known Limitations

An implementation-backed boundary map for deciding what Sharp Runtime can safely replace in a native C++ port.

Current pinImplementation-backedCalm technical scope

How to use this page

This page records boundaries that are visible in the implementation at the pinned Sharp Runtime revision. A limitation may be an architectural non-goal, a deliberately reduced compatibility contract, a platform-specific implementation gap, or behavior that has not yet earned broad validation. It is not a list of every missing .NET member, and a matching namespace or type name is not evidence of CLR-equivalent semantics.

Read these boundaries before choosing components for a port. Then inspect the owning headers, tests, and component page for the exact API you intend to call. The project is a native C++ compatibility library: it can preserve useful .NET shapes without supplying a CLR underneath them.

Boundary kindWhat it meansHow to respond
Architectural non-goalThe behavior would require a managed runtime model the project does not provideRedesign the port around native C++ ownership and build-time types
Reduced semantic modelThe named API exists, but implements a smaller, documented contractCheck whether your inputs and invariants stay inside that contract
Platform gapThe source has an explicit unsupported branch or no native backendGate the feature, provide a backend, or select another component
Evidence gapA path may compile, but has not received a current native runtime gateValidate it on the real compiler, OS, filesystem, and network

Native runtime model, not a CLR

Sharp Runtime compiles into ordinary native C++ libraries. It does not load managed assemblies, execute IL, provide a JIT, install a tracing garbage collector, or reproduce CLR metadata and execution services. Objects follow the ownership expressed by their C++ representation: values, automatic storage, standard containers, raw non-owning views, and explicit smart ownership where an API chooses it.

C# / CLR facilitySharp Runtime boundaryPorting implication
Tracing garbage collectionNo managed heap or tracing collectorExpress ownership with RAII, values, containers, and smart pointers; break ownership cycles explicitly
Universal object baseSystem::Object is an opt-in C++ type, not a base of every valueGeneric code cannot assume arbitrary native types have CLR object identity
Assembly loading and IL executionNo managed loader, verifier, JIT, or application-domain isolation modelCompile all participating code and dependencies as native targets
Runtime metadata universeOnly narrow RTTI-backed type information and explicit library metadataReplace metadata-driven discovery with registration, templates, generated tables, or explicit factories
General boxing/unboxingNo universal conversion between arbitrary values and managed objects; the generic boxing helper rejects the operationCarry values in concrete types or an explicit variant/type-erasure design
Dynamic delegate invocationTyped delegates are useful; general DynamicInvoke is not implementedKeep invocation typed at compile time
P/Invoke, remoting, and binary formatter servicesNo CLR interop/remoting/formatter substrateUse native libraries, explicit serialization, and application-defined transport contracts

Attribute-like types can preserve source structure and intent, but they do not imply that every native declaration is discoverable through a runtime metadata API. Likewise, AppDomain exposes selected process/application facts; it is not a managed isolation, loading, or unload boundary.

Reflection and type information

System::Type wraps a limited native type identity. Names may follow compiler RTTI conventions rather than stable CLR full names, and several classification predicates are intentionally shallow. There is no complete catalogue of assemblies, constructors, members, custom attributes, generic definitions, or invocation metadata.

This is sufficient for code that needs a modest type token or equality check. It is not sufficient for serializers, dependency-injection containers, test frameworks, or plugin loaders that discover an arbitrary object graph through CLR reflection. Those systems need an explicit registry or generated metadata layer. Do not persist native RTTI names as a durable wire format: compiler, ABI, and build changes can alter them.

Strings, text, and globalization

The core SharpRuntime::String representation is a std::string byte value. System::String supplies static helpers over that representation; it is not the immutable UTF-16 reference type used by the CLR. Lengths, indexes, and substrings therefore operate on bytes unless a particular subsystem performs an explicit encoding conversion.

TopicCurrent behaviorConsequence
Representationstd::string bytesA byte offset is not necessarily a Unicode scalar or user-perceived character boundary
Null stringNo distinct managed-null string value in the ordinary aliasUse an optional/nullable wrapper when absence differs from an empty string
MutabilityThe underlying native string is mutableDo not rely on CLR string identity or immutability for aliasing assumptions
Comparison modesThe named comparison values collapse to case-sensitive or byte-oriented case-insensitive pathsCulture, ordinal, and invariant labels are not fully distinct semantic engines
Case conversionByte/C-locale-oriented behaviorDo not use it as full Unicode locale-sensitive casing
TrimmingDefault helpers recognize the implemented ASCII whitespace setAdditional Unicode whitespace requires explicit handling
HashingNative implementation-derived hashDo not persist it or expect .NET-compatible/stable values across implementations
NormalizationThe normalization extension currently reports normalized and returns the input unchangedNon-ASCII canonical equivalence is not normalized

Several boundary APIs do perform UTF-8 work. For example, binary character reading decodes UTF-8 bytes into UTF-16 code units. That local conversion does not change the project-wide string representation. Keep an explicit encoding contract at file, console, JSON, XML, HTTP, and native-library boundaries; never infer one merely from the C++ type name.

See String internals and String class guide for the operational model.

Arrays, spans, and memory

System::Array is a set of algorithms over native contiguous storage, particularly std::vector and pointer ranges. It is not a CLR array object hierarchy. The current model does not supply general rectangular multidimensional arrays, runtime covariance, or managed bounds metadata.

Span, ReadOnlySpan, Memory, ReadOnlyMemory, and ArraySegment are non-owning views in the current implementation. Constructing one from a vector does not make the view retain that vector as a managed owner. Vector reallocation, destruction, moves, or mutation that invalidates addresses also invalidates the view. A pin operation exposes a pointer; it does not stop a native owner from moving or releasing storage.

C# expectationNative boundarySafe discipline
Array object retains its elements/storageVector owns storage; raw ranges do notKeep the owner alive and stable for every borrowed view
Memory<T> may outlive the producing stack frameCurrent memory views do not retain a managed backing ownerDo not return a view to temporary/local storage
Pinning constrains a relocating collectorNo relocating collector exists; pinning is pointer exposurePrevent native reallocation/destruction through program structure
Rectangular multidimensional arraysNo general CLR-equivalent representationChoose a flat vector plus indexing policy or an application-specific matrix

These types are useful precisely because they are lightweight. Their cost model comes with an explicit lifetime contract. See Array internals and Memory management.

I/O and filesystem boundaries

The stream and filesystem surface is synchronous and intentionally smaller than System.IO in current .NET. The base Stream has no async virtual contract. Concrete classes vary in ownership: a FileStream owns its native file, a MemoryStream owns copied vector storage, an UnmanagedMemoryStream borrows its buffer, and wrappers/readers/writers commonly borrow a stream unless an explicit ownership flag says otherwise.

AreaCurrent limitationOperational advice
Async I/ONo base asynchronous stream operationsSchedule synchronous work at the application layer only when blocking and lifetime behavior are acceptable
StreamReaderReads bytes through the current narrow text path; no general encoding selection, BOM detection, or UTF-8 multibyte decoderUse it for the documented byte-oriented inputs, not arbitrary international text
StreamWriterWrites native string bytes; no configurable encoder/BOM pipelineEncode deliberately before writing when the file contract is not byte-transparent
leaveOpen close stateSome text readers/writers can remain callable after Close when the underlying stream is left open; string reader/writer close is also effectively non-terminalDo not treat every text wrapper as enforcing the same disposed-state contract
Whole-file text helpersNo encoding parameter or BOM policy; line writes use the implemented fixed newline behaviorUse explicit stream/encoding logic when interchange format matters
Windows path grammarThe path model is smaller than complete drive-relative, device, and UNC semanticsTest production Windows path classes and normalization cases explicitly
Directory enumerationEager, single-level APIs; no complete lazy Enumerate*/search-option familyImplement recursion and cancellation at the application layer
Creation time on POSIXMay be approximated with inode metadata-change timeDo not use it as a portable birth-time guarantee
DriveInfoReduced drive type/format/label model; non-Windows enumeration is minimalTreat capacity as the strongest portable result and qualify the rest
File sharing/optionsRelated enum/options types exist, but active FileStream constructors do not reproduce the complete .NET sharing/options contractDo not assume Windows sharing-mode equivalence from the type names

FileStream closed-state checks were repaired at this pin: reads, writes, flush, length, position, and length changes now route through the closed guard. That former warning is not a current limitation. The remaining text-wrapper and encoding boundaries are separate issues.

Read Streams, readers, and writers and Files, directories, paths, and metadata for the detailed contracts.

FileSystemWatcher

FileSystemWatcher is Linux-only and watches one directory with inotify. Enabling it on Windows, macOS, Emscripten, or another unsupported target throws. IncludeSubdirectories is stored but not implemented, and InternalBufferSize does not size the actual inotify read buffer.

NotifyFilter now separates name events from content/metadata events, but values within each class remain coarser than .NET: file and directory names are not distinguished, and individual size, write-time, access-time, creation-time, attribute, and security causes cannot be recovered reliably from the selected inotify masks. Rename pairing is limited to matching cookies in one read batch. Kernel queue overflow is not explicitly translated to the managed-style overflow error, so a watcher stream must be treated as hints that may require a rescan.

The callback self-stop defect is fixed at this source pin. An event handler may disable its own watcher without joining itself or terminating the process; ordinary handler exceptions are forwarded through Error, and error-handler exceptions on that callback-fault forwarding path are contained. Reassigning Path or NotifyFilter from inside a callback is deliberately rejected because those changes must retire and rearm the current worker. Handler and filter vectors are still public unsynchronized containers and must not be mutated concurrently with delivery.

See FileSystemWatcher for the exact event mapping, rearm sequence, callback rules, and residual loss modes.

Isolated storage

IsolatedStorageFile now rejects common lexical escapes and resolved symlink escapes for its member operations. That is materially stronger than simple string-prefix containment, so the old blanket claim that member paths are unconstrained is obsolete.

Two boundaries remain. First, validation and use are separate filesystem operations: another process can replace a checked path component between the check and the eventual operation, leaving a time-of-check/time-of-use race. Second, constructing IsolatedStorageFileStream directly does not apply the store member’s confinement policy. Applications with an adversarial local process or untrusted path input should not treat the current layer as a hardened security sandbox. The quota surface also does not provide enforced storage quotas.

Networking, HTTP, and WebSockets

The networking stack provides useful native sockets, DNS, HTTP, WebSocket, and network-information APIs, but it is not the whole .NET networking/security stack.

AreaCurrent boundaryPorting consequence
HttpClientPlain HTTP over a buffered HTTP/1.1 TCP path; HTTPS/TLS and automatic redirects are not implementedDo not send credentials or production traffic requiring TLS; provide a secure transport before claiming HTTPS compatibility
HTTP body handlingRequest/response path is buffered rather than a complete streaming transportAccount for memory use and avoid assuming progressive backpressure
ClientWebSocketws:// only; wss:// and per-message compression are absentSecure WebSockets require another TLS-capable layer
WebSocket async buffersSend/receive work can retain references to caller-provided buffers until completionKeep buffers alive and unmoved until the returned task finishes
NetworkInterfaceLinux-only implementationDo not assume interface enumeration on Windows, macOS, or WebAssembly
PingLinux host policy can require a raw-ICMP fallback that is currently incompleteExpect environment-sensitive failures under restrictive ping_group_range/capability settings
Emscripten native networkingNative socket/DNS operations reject the unsupported pathUse a browser-appropriate transport integration instead

The cryptography namespaces include useful primitives and secure-random support, but they do not form an X.509 validation, certificate-store, or TLS stack for HttpClient. Component names should not be combined into a security claim that the implementation does not make. See Networking components.

Threading, tasks, and callback lifetimes

Native threads, synchronization primitives, cancellation tokens, tasks, timers, and typed delegates can support familiar control flow. They do not add C# async/await syntax, a CLR scheduler, execution-context flow, or automatic lifetime extension for every object captured by background work.

The current ThreadPool::QueueUserWorkItem launches detached native work for each queued item rather than maintaining a CLR-style managed worker pool. System::Threading::Timer uses a dedicated native thread. This affects resource use, shutdown reasoning, affinity, and ordering. Single-threaded Emscripten builds reject operations that require pthreads.

Callback-owning classes do not all share one lifetime strategy. Before destroying a socket, timer, watcher, WebSocket, or task owner, inspect whether work captures a raw object address, shared state, or caller-owned buffers, and use the type’s stop/join/completion mechanism. Cancellation requests are cooperative; they do not make borrowed memory safe or force native calls to unwind instantly.

Date, time, and time zones

DateTime implements a useful value model but does not store the full CLR DateTimeKind state. The surface does not provide the complete local/universal conversion, SpecifyKind, OLE Automation date, file-time, and binary-serialization family. Parsing and formatting are intentionally narrower and more invariant than the complete .NET culture-aware engine.

TimeZoneInfo exposes current-zone and named-zone support through platform paths, but the object behaves largely as a fixed standard-offset model. Daylight-saving transitions, ambiguous/invalid local-time detection, adjustment rules, and serialization are not fully modeled; adjustment-rule enumeration is empty and related predicates return the reduced result. System-zone enumeration is limited rather than a complete platform catalogue. Emscripten treats local time as UTC and rejects named system-zone lookup.

Use an application-grade time-zone library when civil-time recurrence, historical transitions, scheduling across DST, or durable zone identifiers are requirements.

Regular expressions and XML

System::Text::RegularExpressions is backed by the C++ standard library’s ECMAScript regular-expression engine plus Sharp Runtime compatibility work. Named captures are supported at this pin, so the older “no named groups” warning is obsolete. The option surface is still reduced: only the implemented ignore-case and multiline behavior affects matching, there is no match-timeout enforcement, and the replacement engine does not reproduce every .NET substitution token or multi-digit group-reference corner.

The XML stack contains an actual tree, parser/serializer integration, LINQ-style operations, and a purpose-built XPath evaluator. XPath is a documented subset rather than a complete pluggable XPath/XSLT environment. Namespace resolver/context injection is absent, prefixes are handled by the reduced model, and unsupported grammar is rejected. XObject change-handler collections do not provide the full live notification behavior, while base-URI/line-information load options are currently inert.

Several older XML parser/serializer defects—including embedded-NUL/name/DOCTYPE cases covered by the latest remediation—were fixed before this pin and should not be presented as current limitations.

Platform and compiler availability

Linux/GCC is the only environment with the recorded complete native gate at this revision. MinGW-w64 and Emscripten have recorded library cross-build evidence without native/browser test execution. macOS has downstream Xcode 15.4 build evidence but no current complete standalone gate. Windows contains substantial Win32 implementations, yet has no current native test gate. Android has a storage/SDL integration path rather than a recorded whole-project device gate.

Platform/compiler boundaryCurrent result
MSVC native 128-bit integersDecimal, Int128, and UInt128 require the GCC/Clang __int128 extension and intentionally fail under the MSVC frontend
WindowsNo current native gate; Process, FileSystemWatcher, and NetworkInterface lack Windows backends
macOSNo current full gate; watcher and network-interface gaps remain
EmscriptenLibrary cross-build only; native sockets, DNS, process, random-access descriptors, and several threading operations throw
AndroidStorage uses parent-provided SDL3 and platform paths; no complete device/emulator result

The detailed evidence and subsystem matrix live in Platforms and portability. Configure/build evidence must not be promoted to runtime support without execution on the target.

Verification state is not a blanket green claim

The selected revision records a full Linux gate of 17,131 executed cases: 17,123 passed, two skipped, and six failed. Five failures exercise Ping under the host’s Linux ping_group_range policy and expose the incomplete raw-ICMP receive fallback. One failure depends on absent host IPv6/interface state. These conditions are environment-sensitive, but they are not erased from the product record.

A discovered test count, an executable count, and an executed gate are separate facts. Likewise, a passing component test does not validate a platform on which it was never run. Consumers should repeat the relevant native gate under their deployment toolchain and retain failures with enough environment data to distinguish portability defects from unavailable host facilities.

Warnings fixed at this source pin

Limitations pages tend to become stale because resolved defects remain phrased as permanent product boundaries. The following first-pass warnings must not be carried forward as current claims:

  • FileStream closed metadata access: closed guards now cover length, position, flushing, reads, writes, and length mutation.
  • FileSystemWatcher live path mismatch: a live Path change now retires the old thread/watch, stores the validated path, and rearms the new directory.
  • FileSystemWatcher event-class filtering: NotifyFilter now distinguishes name events from content/metadata events, though it still cannot distinguish values within those classes.
  • FileSystemWatcher callback self-join: the #2347 repair permits callback-triggered disable without self-join or process termination and contains handler exceptions.
  • Isolated-storage lexical/symlink escape: ordinary store member operations now check lexical and resolved containment; the residual TOCTOU and direct-stream-constructor boundaries remain documented above.
  • Regular-expression named captures: named groups are implemented; the remaining regex limitations concern options, timeouts, engine compatibility, and replacement corners.
  • Recent XML malformed-input/serialization cases: the repaired NUL, XML-name, and DOCTYPE paths are no longer listed as open limitations.

Compatibility decision checklist

  1. Identify the exact C#/.NET behavior your application relies on; do not start from type-name similarity.
  2. Find the physical Sharp Runtime component and inspect its current public header and tests.
  3. Check representation and ownership: value, owner, borrowed pointer, borrowed span, or shared state.
  4. Check whether text is bytes, UTF-8, UTF-16 code units, or an API-specific conversion.
  5. Check platform implementation and native runtime evidence separately.
  6. Check error behavior, close/dispose state, and callback thread/lifetime rules.
  7. Write a focused consumer test for every reduced semantic boundary your product accepts.
  8. Document intentional deviations in the port so later maintainers do not “fix” them back into unsafe assumptions.