Website | Source | Specification
The fastest path is the Gsharp.Templates package. It scaffolds a console app that uses Gsharp.NET.Sdk and can be built and run like any other .NET project.
dotnet new install Gsharp.Templatesdotnet new gsharp-console -n MyAppcd MyApp && dotnet build && dotnet run# -> Hello from GSharp!
The generated project includes a .gsproj, a starter Program.gs, a NuGet.config that enables optional local SDK side-loading, and a README.
A minimal project file looks like this:
HelloWorld.gsproj
<Project Sdk="Gsharp.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework> <RootNamespace>HelloWorld</RootNamespace> </PropertyGroup></Project>
The SDK automatically includes .gs files in the project directory, forwards references and build settings to gsc, and supports regular commands such as:
dotnet builddotnet run
If you are developing the SDK itself and want to side-load a locally packed build, copy the package into the project's configured package source before building:
mkdir -p packagescp /path/to/Gsharp.NET.Sdk.VERSION.nupkg packages/dotnet build
See SDK projects for the full project-system walkthrough.
The G# VS Code extension is published on the Visual Studio Marketplace. It adds syntax highlighting, language-server features, build/run commands, and debugger configuration for .gs and .gsproj files. Install it from within VS Code (search for "G#" in the Extensions view) or from the command line:
code --install-extension gsharplang.vscode-gsharp
See the VS Code extension reference for the full feature list and settings.
G# programs are organized into packages, can import CLR namespaces, and use func for executable code.
HelloWorld.gs
package HelloWorldimport Systemfunc Main() {
Console.WriteLine("Hello, world!")
}
Hello, world!
Console comes from the .NET System namespace. The compiler also has an implicit System import by default; /noimplicitimports disables it.
G# has var, let, and const declarations. var is mutable and may be declared with an explicit type and no initializer, which gives it the type's zero value. let is for values that are initialized once, including deconstruction forms. const is for compile-time constants.
package Tour.Basics.Variables
import System
func Main() {
var total = 0
let name = "G#"
const answer = 42
total = total + answer
Console.WriteLine(name)
Console.WriteLine(total)
}
Zero values are useful when a variable will be assigned later:
ZeroValues.gs
package GSharp.Example.ZeroValues
import System
func Main() {
var x int32
var flag bool
var text string
Console.WriteLine("x=${x} flag=${flag} text=[${text}]")
x = 42
flag = true
text = "set"
Console.WriteLine("x=${x} flag=${flag} text=[${text}]")
}
x=0 flag=False text=[]
x=42 flag=True text=[set]
Functions begin with func. Parameters are named, their types follow the names, and an optional return type follows the parameter list.
Arithmetic.gs
package GSharp.Example.Arithmetic
import System
func add(num1 int32, num2 int32) int32 {
return num1 + num2}
func Main() {
var sum = 0
for i in 1 ... 5 {
sum = sum + i
}
Console.WriteLine(add(2, 3))
Console.WriteLine(sum)
}
510
A parameter declared with an ellipsis between its name and its element type — name ...T — accepts any number of trailing arguments. Inside the body the parameter is a slice ([]T); the call site can pass either positional arguments or a single []T value.
Variadic.gs
package GSharp.Example.Variadicimport Systemfunc sum(nums ...int32) int32 { var total = 0 for v in nums { total = total + v } return total}func Main() { Console.WriteLine(sum(1, 2, 3, 4, 5)) Console.WriteLine(sum()) let arr = []int32{10, 20, 30} Console.WriteLine(sum(arr))}
15060
The emitted method carries [System.ParamArrayAttribute] so it is consumable from C# / F# / VB as if it had been declared with params T[].
The primitive names are explicit about width: bool, int32, uint32, int64, uint64, float32, float64, decimal, char, string, and object are common examples. Friendly aliases such as int, long, byte, float, and double resolve to their canonical width-bearing types.
Strings support sigil-free interpolation with $name and braced ${expr} holes inside ordinary string literals. Holes may add an alignment and format clause, ${expr,alignment:format}:
InterpolatedString.gs
package InterpolatedStringimport Systemfunc Main() { let name = "world" let n = 6 Console.WriteLine("Hello, $name!") Console.WriteLine("answer = ${n * 7}") Console.WriteLine("$$ stays literal")}
Hello, world!answer = 42$ stays literal
??The null literal is nil. Nullable types use ?, and the null-coalescing operator is ??:
NullCoalescing.gs
package GSharp.Example.NullCoalescingimport Systemfunc displayName(name string?) string { return name ?? "friend"}func Main() { let given string? = "Ada" let missing string? = nil Console.WriteLine(displayName(given)) Console.WriteLine(displayName(missing))}
Use ?? for null-coalescing reads. ??= remains the compound assignment form for writable nullable values.
G# has value-oriented structs, reference-oriented classes, data structs, data classes, arrays, slices, maps, tuples, sequences, channels, and function types. This chapter focuses on everyday aggregate and collection shapes.
A struct is value-like. Assigning one struct variable to another copies the value.
Struct.gs
package GSharp.Example.Structimport Systemstruct Point { var X int32 var Y int32}func Main() { var p = Point{X: 3, Y: 4} Console.WriteLine(p.X + p.Y) p.X = 10 Console.WriteLine(p.X) var q = p q.X = 99 Console.WriteLine(p.X) Console.WriteLine(q.X) var origin = Point{} Console.WriteLine(origin.X + origin.Y)}
71010990
A class is reference-like. Assigning a class value copies the reference, so both variables observe later field changes.
Class.gs
package Tour.Types.Classimport Systemclass Point { var X int32 var Y int32}func Main() { var p = Point{X: 3, Y: 4} var q = p q.X = 99 Console.WriteLine(p.X)}
data struct and data class add ergonomic value-record behavior: structural equality, with-copy, and deconstruction. data struct is value-typed; data class is reference-typed.
DataStruct.gs
package GSharp.Example.DataStructimport Systemdata struct Point { var X int32 var Y int32}func Main() { var p = Point{X: 3, Y: 4} var q = Point{X: 3, Y: 4} var r = Point{X: 3, Y: 5} Console.WriteLine(p == q) Console.WriteLine(p != r) Console.WriteLine(q == r)}
TrueTrueFalse
The longer data-struct sample also shows copy, with, and deconstruction:
DataStructErgonomics.gs
package GSharp.Example.DataStructErgonomicsimport Systemdata struct Point { var x int32 var y int32}func Main() { let p = Point{x: 3, y: 4} let same = p.copy() let movedX = p.copy(x: 10) let viaWith = p with { x = 10 } let (px, py) = p Console.WriteLine(p == same) Console.WriteLine(movedX == viaWith) Console.WriteLine(px + py)}
Fixed-size literals use [N]T{...}. Slice literals use []T{...}. The 0.3 runtime allocation form [n]T creates a zero-initialized []T of length n.
ArraysAndSlices.gs
package GSharp.Example.ArraysAndSlicesimport Systemfunc zeros(n int32) []int32 { return [n]int32}func Main() { let fixed = [3]int32{10, 20, 30} let slice = []int32{1, 2, 3} let runtime = zeros(4) Console.WriteLine(fixed[0]) Console.WriteLine(slice.Length) Console.WriteLine(runtime.Length) Console.WriteLine(runtime[0])}
CLR collections can be created with List[T]{...}, HashSet[T]{...}, and Dictionary[K,V]{...} initializers. Dictionary entries use key: value; use [key] = value when the key is an identifier expression.
CollectionInitializers.gs
package GSharp.Example.CollectionInitializersimport Systemimport System.Collections.Genericfunc Main() { var primes = List[int32]{2, 3, 5, 7} var seen = HashSet[string]{"red", "green", "blue"} var counts = Dictionary[string, int32]{"gsharp": 1, "dotnet": 2} primes.Add(11) counts["gsharp"] = counts["gsharp"] + 1 Console.WriteLine(primes.Count) Console.WriteLine(seen.Contains("red")) Console.WriteLine(counts["gsharp"])}
Maps use map[K,V] for G# map literals, and CLR collections such as Dictionary[string, int32] are available through imports.
Maps.gs
package Tour.Types.Mapsimport Systemfunc Main() { var counts = map[string,int32]{"gsharp": 1} counts["gsharp"] = counts["gsharp"] + 1 Console.WriteLine(counts["gsharp"])}
A field-only anonymous object is written object { ... }. Field types may be inferred, and fields are available through properties on the resulting value.
AnonymousObject.gs
package GSharp.Example.AnonymousObjectimport Systemfunc Main() { let profile = object { let Name = "Ada" let Language = "G#" let Score int32 = 99 } Console.WriteLine(profile.Name) Console.WriteLine(profile.Score)}
Use data object { ... } when you want value-style equality, ToString, deconstruction, and with-copy support.
A composite literal with no fields uses the zero value for each field. A var declaration with an explicit type and no initializer also starts at the type's zero value: 0 for numeric types, False for bool, nil for reference types (including string), and nil for nullable values.
The same zero value can be spelled directly as default(T) for any type T. The bare default literal is accepted wherever the target type is known from context.
nil vs nullThe null literal in G# is spelled nil, not null. Typing null in a value position reports GS0273 and the binder treats it as nil so the rest of the expression still typechecks.
Last modified 11 September 2026