4. Types
4.1 CLR types and generics
CS-TYPE-001. DirectSharp is strongly typed and .NET-first. Normal CLR classes, interfaces and generics remain first-class.
CS-TYPE-002. Canonical generic syntax uses <of ...>, for example List <of String>.
data and type ... as one of have their own construction, equality and representation rules in chapters 9 and 11. Their value/type semantics differ from an ordinary class.
4.2 Absence and effective defaults
CS-NULL-001. nothing denotes actual absence. default denotes the effective default of the known target type. They are distinct concepts.
CS-NULL-002. Ordinary class references default to nothing unless an intrinsic or visible external default supplies a meaningful value.
CS-TYPE-004. An uncontextualized let value = nothing infers Object while retaining actual absence. An explicit or contextual target takes precedence. This differs from targetless default, which is invalid.
CS-DEFAULT-001. An expression consisting of default requires a determinable target type. The compiler must diagnose let value = default when no target is available.
CS-DEFAULT-002. Default resolution follows this priority: locked built-in default; intrinsic type default; one visible external default; otherwise nothing. Conflicting visible external defaults are compile-time errors rather than an import-order override.
| Type/category | Built-in or ordinary default |
|---|---|
| Numeric; Boolean; Char | Zero; false; UTF-16 zero character |
| String | Empty string |
| Date/time values; TimeSpan; Guid | Corresponding minimum date/time; zero duration; empty GUID |
| Enum | Zero value |
| Built-in List, Set, Dictionary, Queue, Stack and supported Array types | Empty collection |
| Tuples/records | Their fields' defaults, subject to the type's default rules |
| Ordinary class | nothing unless customized |
| Interface, delegate, nullable value, Object | nothing |
Default resolution does not bypass a data type's required construction rules or manufacture an ordinary class instance.
CS-DEFAULT-003. An intrinsic default ... end default belongs to the type. An external default for T supplies a default without modifying T. lock default can preserve the type's absent default and prevent external replacement. Locked built-in defaults cannot be overridden. An intrinsic default also prevents a competing external default for the same type.
CS-DEFAULT-004. External defaults are internal unless declared public. Resolution uses visibility in the consuming compilation; competing visible defaults are ambiguous.
CS-DEFAULT-005. Defaults and presets may compose, but cycles must be rejected. Repeated evaluations that produce mutable objects must yield independent results unless the definition explicitly returns a shared value.
let title as String = default
let customer as Customer = nothing
let options as Options = default
The last line invokes the effective Options default; it does not necessarily call a parameterless constructor.
A named preset is an alternative factory/value, chosen explicitly through preset T.name; it does not replace the unnamed default.
4.3 Default substitution and the CLR boundary
CS-NULL-003. DirectSharp-authored nullable arithmetic uses default substitution for supported operations and known operand types. Imported CLR operators retain their own behavior.
CS-NULL-004. Substitution is local to the supported arithmetic/string-concatenation operation. It does not change the stored operand. Passing the original value to an ordinary API still passes actual absence.
CS-NULL-005. ?? nothing produces or stores absence without adding persistent propagation metadata that changes later assignments or operations.
4.4 Diagnostics and default lowering
Reject targetless default, conflicting external defaults, attempts to override a locked default and default/preset cycles. Exact diagnostic codes are implementation work.
For known standard CLR targets:
string title = "";
Customer? customer = null;
These represent the first two examples above. An Options default may instead require a generated call to its definition. A universal replacement with C# default(T) would incorrectly make String absent and ignore meaningful defaults.
For two already-evaluated nullable numeric operands with zero defaults, (left ?? 0) + (right ?? 0) illustrates operation-local substitution. A backend must first bind the actual operator and types; this illustration must not rewrite imported user-defined operators or evaluate operand expressions twice.
4.5 Runtime refinement and explicit conversion
CS-CONVERT-001. is a tests runtime assignability and narrows the tested value on a true control-flow path where its identity remains safe. is exactly tests the concrete runtime type. Neither predicate invokes a parsing, numeric or user-defined value conversion.
if customer is a PremiumCustomer then
customer.ApplyVipRules()
end if
PremiumCustomer and ApplyVipRules are application declarations. Within the successful branch, the tested customer has the refined static type. Flow narrowing must not assume that an effectful expression yields the same object when evaluated again.
CS-CONVERT-002. convert source to Target selects a recognized conversion path. Runtime reference conversion returns the same object after compatibility checking; failure follows invalid-cast behavior. Composite source operands require parentheses: convert (subtotal + tax) to Integer. Conversion does not silently materialize an effective default from nothing.
CS-CONVERT-003. A String literal may be converted implicitly when one target type is supplied by context. Compiler-known numeric, Guid, date/time and TimeSpan conversions use invariant deterministic parsing; an invalid known literal is a compile-time error. Offset-free DateTime text is not silently interpreted as machine-local or UTC time.
let invoiceId as Guid = "8f14e45f-ea6d-4a4b-8f6f-8f65e0e5c123"
let dueDate as Global.System.DateOnly = "2026-10-13"
let retryDelay as Global.System.TimeSpan = "00:00:30"
CS-CONVERT-004. For a runtime String expression, direct parsing/construction is explicit: expression as Target. Assignment alone must not add hidden runtime parsing. A runtime conversion failure follows the chosen parse/constructor contract; the source expression is evaluated once.
let text = "1250.50"
let amount = text as Decimal
CS-CONVERT-005. If no compiler-known direct conversion applies, a target can participate through one accessible constructor taking exactly one String input. Selection is a single direct hop: no optional additional inputs, needs completion, DI lookup or recursive conversion chain. User constructors execute normally at runtime even when the input is a literal; the compiler must not execute application code to validate that literal.
CS-CONVERT-006. Native exact argument matches outrank contextual String conversions. A String overload is selected over a Guid overload for an ordinary String literal. An explicit as Guid selects the target deliberately. Equally viable non-String targets are ambiguous; being a framework type does not grant one priority. When an exact String overload wins but the literal is also valid for another target, a non-blocking actionable warning can suggest explicit target selection.
For a bound Guid target, representative C# lowering uses Guid.Parse or an equivalent invariant compiler-generated constant/conversion. A user one-String constructor lowers to ordinary new Target(text). Their validation timing is intentionally different.
4.6 Default and preset declarations
CS-DEFAULT-006. A preset's identity is its target type and name. Different names can coexist for one type. Competing visible definitions for the same identity are errors; registration/import order does not select a winner. A preset does not become a static member of an imported CLR type.
public class ExportOptions
public IncludeHeaders as Boolean = true
public Format as String = "csv"
default
return new ExportOptions()
end default
end class
preset for ExportOptions compact
let options = new ExportOptions()
options.IncludeHeaders = false
return options
end preset
let normal as ExportOptions = default
let compact = preset ExportOptions.compact
The final two lines are action-body declarations. These factories produce independent mutable results and use ordinary construction rather than an object-initializer meaning for with. Defaults/presets must not introduce accidental shared mutable singleton state or dependency cycles.
4.7 Real nullable values and declaration inference
CS-NULL-006. Nullable <of T> denotes actual CLR System.Nullable<T> for an eligible nonnullable CLR value type. It is valid in locals, members, parameters and action results, subject to ordinary CLR generic constraints; it is not a nullable-reference spelling or an Optional runtime wrapper. Preserve imported Nullable<T> signatures and values. A compatible value or contextually typed nothing can be passed to an imported nullable parameter without an adapter or mandatory HasValue syntax. Neither an imported nullable result nor a missing reference is replaced by its effective default.
CS-NULL-007. A local declared let amount as Integer = nothing infers real Nullable<Integer> at declaration; the same initialization rule applies to eligible value types. Its type remains nullable after later value assignments. A local initialized as a nonnullable value, such as let count as Integer = 0, cannot subsequently receive nothing merely by changing its runtime value. Declaration inference does not rewrite a fixed parameter, action result or imported generic signature to admit absence. Use an explicit compatible nullable type where that contract must contain absence. Every let still requires an initializer under CS-DECL-001.
CS-NULL-008. Ordinary references, including String and class references, can contain actual nothing without an optional keyword or mandatory nullable-reference annotation. Default initialization is not a permanent non-null guarantee. Preserve null from external arguments, results, properties and fields; a reference-typed argument may hold nothing when passed to an ordinary imported API, subject to the selected parameter's normal passing rules and the callee's runtime contract. Imported C# nullable-reference annotations alone do not impose a mandatory error or default warning; an optional low-priority analyzer suggestion may describe the native expectation. Required type/value contracts, explicit presence checks and required receiver operations retain their own rules.
These are action-body declarations. The first local is nullable from its declaration, and remains so after both assignments:
let amount as Integer = nothing
amount = 125
amount = nothing
let limit as Global.System.Nullable <of Decimal> = nothing
Reference C# for these known CLR types:
int? amount = null;
amount = 125;
amount = null;
decimal? limit = null;
Nullable <of String> and Nullable <of Nullable <of Integer>> violate the underlying CLR constraint. A String reference can already contain nothing; an existing nullable Integer needs no second nullable layer. A public result that may return an absent Integer needs a compatible nullable result contract. C# unconstrained T? is an annotation rather than a universal Nullable<T> constructor; the selection binding account shows concrete value/reference cases and the remaining generic boundary.
4.8 Generic declarations and constraints
CS-GENERIC-001. Generic parameter and argument lists use the delimited <of ...> form under CS-TYPE-002, including declarations of classes, interfaces, data, actions and compatible class kinds. Multiple parameters/arguments are comma-separated; nested constructed types retain their own visible delimiters. The formatter's space before the opening angle bracket is non-semantic. Preserve ordinary CLR generic type/method identity and argument-based inference under CS-CALL-001.
CS-GENERIC-002. A where restriction constrains a declared generic parameter: where T inherits BaseType requires the compatible CLR base class, where T implements InterfaceType requires the actual interface relationship, and where T has constructor requires the ordinary CLR/C# public parameterless constructor capability. Combine applicable restrictions as a legal CLR constraint set; reject restrictions that cannot be represented legally. A matching member name alone does not establish an interface or inheritance relationship. A constructor requiring arguments, even optional arguments, is not the parameterless constructor required by that constraint. An abstract type does not satisfy ordinary C# new-constrained construction. Constraint checking does not execute application constructors or complete needs through DI.
CS-GENERIC-003. Check generic declarations and constructed uses against their effective constraints, including imported CLR constraints. The constrained body may use the members established by those contracts; it does not gain arbitrary members through runtime shape matching. Constraint/access failures are compile-time errors, not deferred constructor probes. Normal invocation of a valid body retains its evaluation, construction and failure contracts. Constraints do not introduce return-target inference, substitute default values for absence or create a new generic runtime wrapper.
CS-GENERIC-004. A legal constructed generic dependency type may appear in needs with the same CLR type identity as an ordinary constructor parameter, including nested generic dependencies and types involving the declaring class's own parameters. Preserve its constraints and the existing borrowed dependency semantics. The generic declaration/dependency alone creates no registration, lifetime, scope, open-generic registration or bootstrap policy. Those belong to the separate service composition contract.
The generic import-draft example combines a base class, interface and constructor restriction. In C# the corresponding fragments are where T : BaseType, InterfaceType, new(); the backend may combine source restrictions into that legal constraint order. Where in a generic header is distinct from a loop filter or a feature's declared query operation.
Specification contents · Previous: 3. Names, namespaces and imports · Next: 5. Declarations and scope