A tour of DirectSharp
Read the code first, then follow the features that interest you. This tour moves from a customer operation to contracts, objects, patterns, concurrency and resource ownership. Each section is a short introduction with a link to the details.
The opening example uses the Customer application's model and service. Later examples are independent declarations or action fragments; their inputs and application helpers come from the surrounding application.
action / returns: follow a customer operation
A DirectSharp action is an operation with typed inputs and an optional result. Here an application creates a customer, applies business rules and looks the customer up again:
import System
public class Program
static action Run()
returns String
let service = new CustomerService()
let customer = new Customer(1, "Ana", "ana@example.com")
service.Register(customer)
customer.ApproveLimit(250)
customer.Rename("Ana Petrova")
let found = service.FindByEmail("ANA@EXAMPLE.COM")
require found is not nothing
ensure result = "Ana Petrova: 1 customer"
let summary = found.Name + ": " + service.Count() + " customer"
Console.WriteLine(summary)
return summary
end action
end class
Expected output:
Ana Petrova: 1 customer
The model stores customer state; the service registers customers and finds them by email. require checks entry conditions, ensure checks successful completion, and ordinary .NET collections hold the data. Identifiers and language-level String equality ignore case. The rule is visible in the lookup for "ANA@EXAMPLE.COM".
require / ensure / select: express a delivery policy
public class DeliveryQuote
public static action Calculate(orderTotal as Decimal, destination as String)
returns Decimal
require orderTotal >= 0
require destination
ensure result >= 0
if orderTotal >= 100 then
return 0
end if
return select destination
case "MK"
take 3
case "DE", "AT"
take 8
else
take 15
end select
end action
end class
A bare String requirement checks presence and nonemptiness; a numeric requirement states its condition. Standard exceptions and messages are supplied automatically. ensure covers every normal exit, including the early return. Selection evaluates its subject once and uses the first matching case; take supplies a value to the selection, while return completes the action.
Contracts and errors · Conditions and selection
action calls: reuse and check a policy
let local = DeliveryQuote.Calculate(75, "mk") // 3: String matching ignores case.
let international = DeliveryQuote.Calculate(75, "FR") // 15
let free = DeliveryQuote.Calculate(100, "FR") // 0: the early-return guarantee still applies.
let / default / nothing: make values explicit
let quantity = 3 // Infer Integer.
let total as Decimal = default // The effective numeric default is zero.
let title as String = default // The effective String default is empty.
let customer as Customer = nothing // Actual absence.
let displayName = customer.Name ?? "Guest"
Property reads propagate absence through missing receivers; ?? supplies a fallback only for actual nothing. Defaults and absence remain different. Identifiers ignore case too: quantity and Quantity name the same local. Named endings such as end if and end action identify the block being closed.
if / contextual dot: follow one subject
if customer is not nothing and .IsActive and .CreditLimit >= 500 then
SendOffer(customer)
end if
The leading dot continues from customer without repeating its name. Short-circuit evaluation protects later tests. Inside a library selector, dot instead denotes the selector's typed parameter: customers.Where(.IsActive) is an ordinary LINQ call with its namespace imported.
Conditions · Selectors and callables
constructor / with properties / property: keep object rules together
public class ReportOptions
public constructor()
with properties
readonly Title as String
IncludeDetails as Boolean = true
end properties
require self.Title
end constructor
public property Heading as String
get
return "{{Title}} report"
end get
end property
end class
let options = new ReportOptions() with
title: "Daily orders"
end with
with properties declares stored members and promotes them into constructor inputs. The named with supplies those inputs before construction; it is not a sequence of later setter calls. Heading is a computed read-only property. {{Title}} interpolates the value into text.
Objects and constructor inputs · Properties and initialization
needs: declare injected dependencies
import Global.Microsoft.Extensions.Logging.*
public class ReportHandler
needs
logger as ILogger <of ReportHandler>
end needs
public action Handle(title as String)
require title
logger.LogInformation("Preparing {Title}", title)
end action
end class
The caller or service provider must supply the logger. It is a stable dependency across constructors and remains borrowed, rather than becoming owned by the handler. ILogger and LogInformation are ordinary .NET logging APIs; {Title} here is the logging library's structured message placeholder.
Dependencies and service scopes
data / of: carry typed messages and collections
public data ReportRequest
Title as String
IncludeDetails as Boolean = true
end data
public class Batch <of T>
public Items as new Global.System.Collections.Generic.List <of T>
end class
let request = new ReportRequest("Daily orders")
let batch = new Batch <of ReportRequest>()
batch.Items.Add(request)
Data members are initialized during construction and have record value semantics. The generic Batch keeps its actual item type: this batch accepts ReportRequest, rather than untyped Object values. A read-only data shape does not freeze mutable objects it references.
use / use_if: group operations on a receiver
use options
.IncludeDetails = false
Global.System.Console.WriteLine(.Heading)
end use
use_if customer.Account.Owner
.Notify()
end use_if
use captures one receiver and requires it to be present. use_if skips the body when any qualification segment is absent; getter and body exceptions still propagate. Neither form disposes the receiver. For a shallow copy instead, use use copy_of source.
is a / is like / is match: ask readable questions
if customer is a PremiumCustomer then
customer.ApplyVipRules() // Runtime type refinement in this branch.
end if
if filename is like "report-{{let period = *}}.csv" then
Global.System.Console.WriteLine("Report period: {{period}}")
end if
if invoiceReference is match "^INV-(?<invoiceNumber>[0-9]+)$" then
Global.System.Console.WriteLine("Invoice number: {{invoiceNumber}}")
end if
is a tests type compatibility. is like uses wildcard patterns; is match uses regular expressions. Known named captures belong to the successful branch. Ordinary text matching ignores case; explicit case-sensitive forms are available when a protocol requires them.
Types and refinement · Patterns and captures
lazy: compute only when a value is needed
let index = lazy BuildIndex(orders)
if needsLookup then
Show(index) // The first value use builds the index; later uses share it.
end if
The whole initializer waits for its first ordinary use. It does not start a background task or require .Value. Successful values and initialization failures are cached, including under concurrent first reads.
for_each / consume: visit items or acquire work
for_each order in orders
for_each line in order.Lines
PrintLine(line)
end for_each
end for_each
consume job from pendingJobs
Process(job)
end consume
Enumeration visits a sequence; consumption removes queued items or advances a supported forward-only source. Here pendingJobs is a Queue supplied by the application; it is empty after normal consumption. A supported live source can wait for arrivals until permanent completion. Named endings make the nested loop boundaries visible.
Loops and numeric ranges · Consumable sources
try / try_get / ??: make best-effort work explicit
let port = try_get Global.System.Int32.Parse(portText) ?? 443
try RecordPreviewOpened() // Optional telemetry must not prevent the preview.
A normally catchable parse failure becomes nothing, so the fallback supplies 443. Blockless try suppresses normally catchable statement failures and continues without a value. Use a typed catch when only a particular failure is acceptable. LastAttempt retains the most recently completed attempt's exception metadata in this block.
try / catch / retry: make another attempt deliberately
try
SendReport()
catch Global.System.TimeoutException
if LastAttempt.Number < 3 then
retry
end if
throw
finally
RecordDeliveryFinished()
end try
This permits three attempts on timeout and rethrows the final failure. Other failures propagate normally. Retry restarts the protected body; it does not run this finally between attempts or add backoff. Finally runs when the try is actually left. The application must make retrying SendReport safe for its delivery contract.
await / task_of: distinguish a result from an operation
let pending = task_of(client.GetStringAsync(summaryAddress))
PrepareLayout()
let summary = await pending
task_of invokes once and retains the operation. await observes its result or failure. An ordinary action containing await can infer its asynchronous implementation; async action explicitly fixes an asynchronous contract and supplies the standard cancellation context.
concurrent all: complete independent work together
concurrent all
returns summary as String, alerts as String
summary = run client.GetStringAsync(summaryAddress)
alerts = run client.GetStringAsync(alertsAddress)
end concurrent
ShowDashboard(summary, alerts)
Both operations can run together. Their results are published after the owned work settles, and success requires both. A run can also contain several application statements. These snippets belong in actions with a borrowed HttpClient and supplied addresses.
Concurrent scopes and run bodies
concurrent first: choose the first successful answer
concurrent first
returns body as String
body = run client.GetStringAsync(primaryAddress)
body = run client.GetStringAsync(backupAddress)
end concurrent
ShowDocument(body)
The first successful producer wins; a fast failure does not beat a later success. The scope requests cooperative cancellation of unfinished competitors where supported and joins them before leaving. concurrent any instead selects the first completion, including a failure.
Winner selection, cancellation and joining
concurrent for_each / Gate: bound work and protect a shared step
using writes = new Gate(2)
concurrent for_each item in items with limit: 4
let output = await BuildOutputAsync(item)
enter writes
await StoreOutputAsync(output)
leave
end for_each
end using
Up to four iterations run together; only two enter this write step at once. The Gate permit is released even when work fails. The loop joins its work before the owning using disposes the Gate. Share one Gate between operations when they must share the same capacity.
using / dispose / unmanaged: make ownership visible
using reader = new Global.System.IO.StreamReader(path)
ProcessText(reader.ReadToEnd())
end using
The reader is cleaned up on normal completion, return and exception. In an asynchronous context, using prefers a statically available async disposal contract and awaits it automatically. let using can instead own a resource until the containing scope ends.
A class owning raw native memory can declare its cleanup explicitly:
public class NativeBuffer
private address as Global.System.IntPtr = Global.System.IntPtr.Zero
public constructor(byteCount as Integer)
require byteCount > 0
address = Global.System.Runtime.InteropServices.Marshal.AllocHGlobal(byteCount)
end constructor
dispose
unmanaged
if address <> Global.System.IntPtr.Zero then
Global.System.Runtime.InteropServices.Marshal.FreeHGlobal(address)
address = Global.System.IntPtr.Zero
end if
end unmanaged
end dispose
end class
Ordinary statements in dispose perform managed cleanup; the unmanaged section also supplies the synchronous finalizer fallback. There is no separately authored destructor. A class can additionally declare async dispose for asynchronous managed cleanup. Prefer ordinary .NET safe-handle abstractions when they already express the required native ownership.
Resource lifetime and authored cleanup
class kind: name a reusable class header
import Global.FluentValidation.*
public class kind Validator <of T>
exposes IValidator <of T>
inherits AbstractValidator <of T>
end class kind
public class ReportRequestValidator is Validator <of ReportRequest>
public constructor()
RuleFor(.Title).NotEmpty().Length(2, 100)
end constructor
end class
The kind reuses an inheritance/interface header without adding another runtime base class. RuleFor and its selector remain ordinary FluentValidation API calls; the kind introduces no validation DSL.
wrap: apply a reusable source transformation
An application can attach a known wrapper to a suitable declaration:
wrap cached with engine: scriban
Here cached is an available application wrapper, not a built-in cache policy. A wrapper transforms DirectSharp into DirectSharp; the expanded program retains ordinary typing, contracts and resource rules. Most reuse still belongs in normal actions, classes and libraries.
Extensions, features and wrappers
project / import / Global: bring names into scope
project
root_namespace DeliveryQuotes
import Global.System.*
end project
The single project declaration supplies a root namespace and shared imports. File-level imports apply to their own file; Global. starts at the actual .NET namespace root. DirectSharp uses ordinary .NET classes, interfaces and libraries.
Continue with the language guide for chapter-by-chapter explanations, the Customer walkthrough for connected application code, or the reference for exact semantics.