Known Limitations
An implementation-backed boundary map for deciding what Sharp Runtime can safely replace in a native C++ port.
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 kind | What it means | How to respond |
|---|---|---|
| Architectural non-goal | The behavior would require a managed runtime model the project does not provide | Redesign the port around native C++ ownership and build-time types |
| Reduced semantic model | The named API exists, but implements a smaller, documented contract | Check whether your inputs and invariants stay inside that contract |
| Platform gap | The source has an explicit unsupported branch or no native backend | Gate the feature, provide a backend, or select another component |
| Evidence gap | A path may compile, but has not received a current native runtime gate | Validate 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 facility | Sharp Runtime boundary | Porting implication |
|---|---|---|
| Tracing garbage collection | No managed heap or tracing collector | Express ownership with RAII, values, containers, and smart pointers; break ownership cycles explicitly |
Universal object base | System::Object is an opt-in C++ type, not a base of every value | Generic code cannot assume arbitrary native types have CLR object identity |
| Assembly loading and IL execution | No managed loader, verifier, JIT, or application-domain isolation model | Compile all participating code and dependencies as native targets |
| Runtime metadata universe | Only narrow RTTI-backed type information and explicit library metadata | Replace metadata-driven discovery with registration, templates, generated tables, or explicit factories |
| General boxing/unboxing | No universal conversion between arbitrary values and managed objects; the generic boxing helper rejects the operation | Carry values in concrete types or an explicit variant/type-erasure design |
| Dynamic delegate invocation | Typed delegates are useful; general DynamicInvoke is not implemented | Keep invocation typed at compile time |
| P/Invoke, remoting, and binary formatter services | No CLR interop/remoting/formatter substrate | Use 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.
| Topic | Current behavior | Consequence |
|---|---|---|
| Representation | std::string bytes | A byte offset is not necessarily a Unicode scalar or user-perceived character boundary |
| Null string | No distinct managed-null string value in the ordinary alias | Use an optional/nullable wrapper when absence differs from an empty string |
| Mutability | The underlying native string is mutable | Do not rely on CLR string identity or immutability for aliasing assumptions |
| Comparison modes | The named comparison values collapse to case-sensitive or byte-oriented case-insensitive paths | Culture, ordinal, and invariant labels are not fully distinct semantic engines |
| Case conversion | Byte/C-locale-oriented behavior | Do not use it as full Unicode locale-sensitive casing |
| Trimming | Default helpers recognize the implemented ASCII whitespace set | Additional Unicode whitespace requires explicit handling |
| Hashing | Native implementation-derived hash | Do not persist it or expect .NET-compatible/stable values across implementations |
| Normalization | The normalization extension currently reports normalized and returns the input unchanged | Non-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# expectation | Native boundary | Safe discipline |
|---|---|---|
| Array object retains its elements/storage | Vector owns storage; raw ranges do not | Keep the owner alive and stable for every borrowed view |
Memory<T> may outlive the producing stack frame | Current memory views do not retain a managed backing owner | Do not return a view to temporary/local storage |
| Pinning constrains a relocating collector | No relocating collector exists; pinning is pointer exposure | Prevent native reallocation/destruction through program structure |
| Rectangular multidimensional arrays | No general CLR-equivalent representation | Choose 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.
| Area | Current limitation | Operational advice |
|---|---|---|
| Async I/O | No base asynchronous stream operations | Schedule synchronous work at the application layer only when blocking and lifetime behavior are acceptable |
StreamReader | Reads bytes through the current narrow text path; no general encoding selection, BOM detection, or UTF-8 multibyte decoder | Use it for the documented byte-oriented inputs, not arbitrary international text |
StreamWriter | Writes native string bytes; no configurable encoder/BOM pipeline | Encode deliberately before writing when the file contract is not byte-transparent |
leaveOpen close state | Some text readers/writers can remain callable after Close when the underlying stream is left open; string reader/writer close is also effectively non-terminal | Do not treat every text wrapper as enforcing the same disposed-state contract |
| Whole-file text helpers | No encoding parameter or BOM policy; line writes use the implemented fixed newline behavior | Use explicit stream/encoding logic when interchange format matters |
| Windows path grammar | The path model is smaller than complete drive-relative, device, and UNC semantics | Test production Windows path classes and normalization cases explicitly |
| Directory enumeration | Eager, single-level APIs; no complete lazy Enumerate*/search-option family | Implement recursion and cancellation at the application layer |
| Creation time on POSIX | May be approximated with inode metadata-change time | Do not use it as a portable birth-time guarantee |
DriveInfo | Reduced drive type/format/label model; non-Windows enumeration is minimal | Treat capacity as the strongest portable result and qualify the rest |
| File sharing/options | Related enum/options types exist, but active FileStream constructors do not reproduce the complete .NET sharing/options contract | Do 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.
| Area | Current boundary | Porting consequence |
|---|---|---|
HttpClient | Plain HTTP over a buffered HTTP/1.1 TCP path; HTTPS/TLS and automatic redirects are not implemented | Do not send credentials or production traffic requiring TLS; provide a secure transport before claiming HTTPS compatibility |
| HTTP body handling | Request/response path is buffered rather than a complete streaming transport | Account for memory use and avoid assuming progressive backpressure |
ClientWebSocket | ws:// only; wss:// and per-message compression are absent | Secure WebSockets require another TLS-capable layer |
| WebSocket async buffers | Send/receive work can retain references to caller-provided buffers until completion | Keep buffers alive and unmoved until the returned task finishes |
NetworkInterface | Linux-only implementation | Do not assume interface enumeration on Windows, macOS, or WebAssembly |
Ping | Linux host policy can require a raw-ICMP fallback that is currently incomplete | Expect environment-sensitive failures under restrictive ping_group_range/capability settings |
| Emscripten native networking | Native socket/DNS operations reject the unsupported path | Use 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 boundary | Current result |
|---|---|
| MSVC native 128-bit integers | Decimal, Int128, and UInt128 require the GCC/Clang __int128 extension and intentionally fail under the MSVC frontend |
| Windows | No current native gate; Process, FileSystemWatcher, and NetworkInterface lack Windows backends |
| macOS | No current full gate; watcher and network-interface gaps remain |
| Emscripten | Library cross-build only; native sockets, DNS, process, random-access descriptors, and several threading operations throw |
| Android | Storage 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
Pathchange now retires the old thread/watch, stores the validated path, and rearms the new directory. - FileSystemWatcher event-class filtering:
NotifyFilternow 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
- Identify the exact C#/.NET behavior your application relies on; do not start from type-name similarity.
- Find the physical Sharp Runtime component and inspect its current public header and tests.
- Check representation and ownership: value, owner, borrowed pointer, borrowed span, or shared state.
- Check whether text is bytes, UTF-8, UTF-16 code units, or an API-specific conversion.
- Check platform implementation and native runtime evidence separately.
- Check error behavior, close/dispose state, and callback thread/lifetime rules.
- Write a focused consumer test for every reduced semantic boundary your product accepts.
- Document intentional deviations in the port so later maintainers do not “fix” them back into unsafe assumptions.