Coming from C# or VB.NET
DirectSharp keeps ordinary .NET types and libraries while making application intent visible through words, named blocks and explicit contracts.
Bring your knowledge of .NET classes, interfaces, generics, delegates and libraries. DirectSharp uses those same contracts. Learn the source conventions first, then pay particular attention to names, defaults and asynchronous ownership: familiar-looking code can express a different policy.
This page uses the Customer application. Its Customer has Id, Name, Email and IsActive properties. Registration validates input and creates an inactive customer. Snippets below are independent comparisons or explicitly identified fragments, not extra declarations to paste into that application.
action / constructor / self: translate familiar .NET structure
| C# | VB.NET | DirectSharp |
|---|---|---|
| Method returning a value | Function | action with returns |
| Method returning void | Sub | action without returns |
| this | Me | self |
| Constructor named after its class | Sub New | constructor … end constructor |
List<T> |
List(Of T) |
List <of T> |
| if and braces | If … End If | if … then … end if |
| foreach | For Each | for_each … end for_each |
| null | Nothing | nothing for actual absence |
| using / await using | Using; explicit async cleanup where needed | using selects cleanup from the static contract and execution context |
An action's returns clause describes its logical result. An async action returning String exposes a task-returning CLR operation, not a synchronous String method. Interfaces, overrides and imported APIs retain their fixed signatures.
Imports bring names into scope; project references supply assemblies and NuGet dependencies. Importing FluentValidation does not install it. DirectSharp's automatic import covers the DirectSharp root namespace, not every child namespace or the whole .NET framework. Use Global.System when an absolute namespace root matters.
require / ensure / contextual if: make the rule visible
This pricing action uses the walkthrough Customer's IsActive property. An active customer receives ten percent off; everyone else pays the original amount. The input and output must both be nonnegative.
public static action PriceAfterDiscount(customer as Customer, amount as Decimal)
returns Decimal
require amount >= 0
ensure result >= 0
if customer is not nothing and .IsActive then
return amount * 0.90
end if
return amount
end action
The entry requirement and exit guarantee are visible before the calculation. Inside the condition, .IsActive refers to the customer already being checked. The ensure covers both returns; it does not need to be repeated on each branch. No custom error text is needed to restate either condition.
A C# version makes the same policy explicit by sharing the final result check:
public static decimal PriceAfterDiscount(Customer? customer, decimal amount)
{
if (amount < 0)
throw new ArgumentException("Requirement failed: amount >= 0", nameof(amount));
decimal result;
if (customer is not null && customer.IsActive)
result = amount * 0.90m;
else
result = amount;
if (result < 0)
throw new InvalidOperationException("Postcondition failed: result >= 0");
return result;
}
The C# checks are ordinary application code. DirectSharp expresses them as contracts, so adding another normal return does not require remembering to copy the output check. Its condition also reads without repeating the receiver, and end if/end action identify the blocks being closed. This is the reading difference to look for in larger examples—not a promise that every small operation needs fewer lines.
nothing / is empty: normalize a display label
These standalone declarations implement the same small display policy. An absent or whitespace-only name is shown as “Unnamed customer”; other names are trimmed. They do not validate registration or modify a Customer.
DirectSharp:
public class CustomerLabels
public static action Display(name as String)
returns String
if name is nothing then
return "Unnamed customer"
end if
let label = name.Trim()
if label is empty then
return "Unnamed customer"
end if
return label
end action
end class
C#:
public static class CustomerLabels
{
public static string Display(string? name)
{
if (string.IsNullOrWhiteSpace(name)) return "Unnamed customer";
return name.Trim();
}
}
VB.NET:
Public Class CustomerLabels
Public Shared Function Display(name As String) As String
If String.IsNullOrWhiteSpace(name) Then Return "Unnamed customer"
Return name.Trim()
End Function
End Class
All three return “Ana” for “ Ana ” and the fallback for absence or spaces. DirectSharp checks absence before calling Trim, then asks whether the normalized label is empty. The same policy uses String.IsNullOrWhiteSpace in the .NET examples; that API call is not a DirectSharp predicate. Named endings expose the decision and action boundaries.
self / arguments: qualify case-insensitive names
A rose is a Rose is a ROSE. Like VB.NET, DirectSharp binds identifiers without case distinctions. Unlike C#, changing capitalization cannot select between a parameter and a member.
This DirectSharp action replaces a Customer's existing Name after checking its input. Put it inside the walkthrough's Customer only if you choose to add that operation:
public action Rename(name as String)
require arguments.name is not nothing
require arguments.name.Trim() is not empty
self.Name = arguments.name.Trim()
end action
The explicit arguments.name selects the input; self.Name selects the property. Bare name would be ambiguous here. This is static name binding, not a runtime argument dictionary. A port that uses name and Name to distinguish symbols needs qualification, not different capitalization. Imported CLR members that collide after case folding can also require a diagnostic.
Name identity does not determine data identity. A Dictionary constructed with StringComparer.Ordinal still distinguishes the keys “Ana” and “ana”. Authentication subject identifiers, service keys and protocol values retain their own comparison policies. See names and collisions.
default / nothing / equality: check semantic differences
These fragments assume the named application values are in scope. The results concern DirectSharp-authored expressions; imported APIs retain their documented contracts.
| Question | Example | Result or consequence |
|---|---|---|
| Does case select a different local? | Declare customer, then try to declare CUSTOMER in the same scope | Duplicate-name diagnostic |
| Does identifier casing rewrite stored data? | Read an Ordinal dictionary with a differently cased key | The dictionary's comparer decides; the language does not rewrite the key |
| Is default the same as nothing? | let label as String = default | Empty String; an explicit nothing remains absent |
| Does default construct an ordinary class? | let customer as Customer = default | nothing, unless a visible custom default supplies something else |
| Is String equality the C# operator? | "Ana" = "ana" | true under DirectSharp's ordinal case-insensitive String comparison |
| Does an imported comparison change too? | Global.System.String.Equals("Ana", "ana", Global.System.StringComparison.Ordinal) | false; explicit .NET contract |
| Is readonly deep immutability? | let readonly customer = existingCustomer; then assign customer.Name | Allowed for a present object with a writable property; rebinding customer is forbidden |
| Does a nullable operand mutate into its default? | Add an absent Nullable <of Decimal> to a Decimal in a supported authored arithmetic operation |
Uses the effective numeric default locally; the stored operand is still absent |
| Does try_get mean only “key missing”? | try_get customers[index] | Normally catchable receiver/index/accessor failures become nothing; inspect LastAttempt or choose a narrower API |
VB.NET's Nothing can also denote a value type's default in a typed context. Do not mechanically substitute it for DirectSharp nothing. C# default(string) is null, while DirectSharp's effective String default is empty. Targetless default is invalid: the compiler needs a type. See defaults and CLR boundaries.
Ordinary C# String equality is case-sensitive. VB.NET String comparison depends on its applicable comparison rules, including Option Compare. When a rule must match across all three languages, state the comparer explicitly. For credentials or protocol data, choose the required security/protocol comparison rather than assuming a language default is appropriate.
RuleFor(.Name): pass a selector to FluentValidation
The CustomerValidator constructor contains this DirectSharp rule:
RuleFor(.Name).NotEmpty().Length(2, 100)
The corresponding C# and VB.NET constructor statements are:
RuleFor(customer => customer.Name).NotEmpty().Length(2, 100);
RuleFor(Function(customer) customer.Name).NotEmpty().Length(2, 100)
These are FluentValidation calls in a validator inheriting AbstractValidator<Customer>, with its namespace imported and package referenced. The selector targets the library's expression-tree parameter; it is not the current Name value. The same DirectSharp receiver expression can target a delegate when that is what a library requests. A query provider still decides which expression trees it can translate.
The optional Validator class kind reuses the base/interface header. It adds no runtime base class and no validation language. Follow ordinary validation, then the optional kind.
await / task_of / run: choose asynchronous intent
Ordinary awaits are explicit. The compiler can infer async implementation for an ordinary action that awaits, including a public action. Here is a complete DirectSharp declaration; it has no implicit cancellation parameter:
public class CustomerNameFiles
public static action ReadAsync(path as String)
returns String
return await Global.System.IO.File.ReadAllTextAsync(path)
end action
end class
The corresponding task-returning methods in C# and VB.NET are:
public static class CustomerNameFiles
{
public static async System.Threading.Tasks.Task<string> ReadAsync(string path)
{
return await System.IO.File.ReadAllTextAsync(path);
}
}
Public Class CustomerNameFiles
Public Shared Async Function ReadAsync(path As String) As System.Threading.Tasks.Task(Of String)
Return Await System.IO.File.ReadAllTextAsync(path)
End Function
End Class
In DirectSharp, explicit async action fixes an asynchronous contract and supplies its standard cancellation context. Inference alone supplies neither a token parameter nor Cancellation. This distinction matters in public libraries: changing an inferred action's body can change its emitted return contract. Fixed interface/override signatures and synchronous boundaries remain enforced; the compiler does not invent async void.
Inside an ordinary action, with a supplied path:
let pending = task_of(Global.System.IO.File.ReadAllTextAsync(path))
try
let name = await pending
Global.System.Console.WriteLine(name)
catch error as Global.System.Exception
Global.System.Console.WriteLine(error.Message)
end try
Task_of invokes once and keeps the actual operation. Await observes its result or stored failure. The handler above protects the await; a synchronous failure obtaining the operation occurs before try, so move the capture inside try if it needs the same handler. Capturing does not schedule a thread or automatically join work. Native Task/ValueTask consumption rules remain in force.
| DirectSharp form | Intent |
|---|---|
| let name = await CustomerNameFiles.ReadAsync(path) | Invoke, wait, then use the String |
| let pending = task_of(CustomerNameFiles.ReadAsync(path)) | Invoke and retain the task; someone must observe it |
| let other = pending | Copy an existing task value; no fresh invocation |
| let pending = CustomerNameFiles.ReadAsync(path) | Compile-time error: choose await, task_of or an owned run |
| run inside concurrent all/first/any | Register owned work; scope policy controls joining and result publication |
The same fresh-invocation rule applies in arguments, returns and discarded statements, even when a Task target type is expected. The compiler warns about locally detectable forgotten captures; it cannot prove observation through every external library.
Two successive awaits remain sequential. Task_of can retain independently started operations, but concurrent scopes are the usual application choice when all work must settle before leaving. Start with the dashboard; then read first/any selection and failure rules. Ordinary await retains .NET continuation behavior. ConfigureAwait(false) is a library opt-out from context capture, not UI dispatch or a request for a background thread.
using / dispose: let scopes perform cleanup
Use using for an owned resource. In an already asynchronous context, a statically available IAsyncDisposable contract is preferred; a synchronous context uses IDisposable when available. Async-only cleanup can require permitted async inference. Dual-interface cleanup alone does not make an otherwise synchronous action asynchronous. A static Object view cannot discover a hidden disposable interface dynamically.
There is no separate await using spelling. Selection happens from the static contract and execution context, and the generated cleanup waits before control leaves. Constructors, synchronous callbacks and other fixed synchronous boundaries cannot silently block on async-only disposal. Imported objects retain native lifecycle behavior; DirectSharp-authored dispose/async dispose declarations have their own shared lifecycle rules. See the selection table.
Dispose what this operation owns. The report worker owns its DI scope; its renderer borrows shared Gates and a scoped source. Leaving a concurrent scope joins owned work before a containing resource scope can end. Capturing an unowned task does not create that protection.
Take / TryTake: choose strict or tolerant selection
DirectSharp's opt-in DirectSharp.Collections.SequenceExtensions supplies the strict/Try helpers; import Global.DirectSharp.Collections or qualify the static class. Counts/positions are Integer. If LINQ extensions are equally applicable, the compiler diagnoses ambiguity rather than giving either family priority. The library Take and TryTake contracts differ from System.Linq.Enumerable.Take. With items containing integers 0 through 6:
| DirectSharp library call | When work happens | Result |
|---|---|---|
| items.Take(5, 10) | Deferred traversal | Can yield 5 and 6, then throws ArgumentOutOfRangeException when traversal discovers shortage |
| items.TryTake(5, 10) | One traversal at the call | Collected 5, 6; reuse does not reread the source |
| items.TryTake(20, 30) | At the call, using available metadata or traversal | Actual nothing because nothing was selected |
| items.Take(-1, 2) | Numeric validation at the call | ArgumentOutOfRangeException without source traversal |
Both positions are inclusive. Try normalizes negative positions to zero; an adjusted reversed interval returns nothing. Source reading and cleanup failures still propagate. A collected result preserves references to its elements, not deep copies. Count-taking Take, Skip and TakeLast have their own strict/Try contracts. Explicit imported LINQ calls keep LINQ's behavior; a migration must choose the intended operation rather than merely copying its name. See sequence contracts.
with name / break / continue: target an enclosing loop
C# break and continue act on the innermost loop. DirectSharp retains those short forms and lets a loop carry an explicit name. This fragment skips the remaining orders for an invalid customer order, or stops customer processing on a critical order:
for_each customer in customers with name: customers_loop
for_each order in customer.Orders
if order.IsInvalid then
continue customers_loop
end if
if order.IsCritical then
break customers_loop
end if
ProcessOrder(order)
end for_each
end for_each
An illustrative C# implementation uses explicit destinations to express the same outer-loop transfers:
foreach (var customer in customers)
{
foreach (var order in customer.Orders)
{
if (order.IsInvalid)
goto nextCustomer;
if (order.IsCritical)
goto customersFinished;
ProcessOrder(order);
}
nextCustomer:
; // Continue through the outer foreach acquisition.
}
customersFinished:
;
The application supplies customers and ProcessOrder. Native foreach cleanup occurs when control leaves its enumeration scope; a translation must preserve it. A while target must resume its condition, and a post-test repeat target must resume its trailing test. Moving a continue destination to the start of an arbitrary body can change behavior.
DirectSharp also provides general statement destinations: mark finished corresponds to a C# label such as finished:, and goto finished supports forward or backward transfer. Mark names and loop names have different roles. Neither form relaxes definite assignment, resource cleanup, callable boundaries or concurrent iteration boundaries. See named loops and marks.
Continue with the application
Follow Customer registration through HTTP, then try the guided exercises and answers. For equivalent complete tasks in several languages, use the comparison pages. For precise diagnostics and C# lowering, use the reference.