Hello World & Structure
Hello, World
program HelloWorld;
begin
writeln('Hello, World!');
end. Console.WriteLine("Hello, World!"); C# 9 introduced top-level statements, so a complete program can be a single line. Pascal has always required a
program Name; header and an explicit begin...end. block. The trailing period after end is Pascal's traditional program terminator — a full-stop that marks the end of the source file.Comments
program CommentsDemo;
begin
// Single-line comment (Free Pascal extension)
{ Brace comment — traditional Pascal style }
(* Parenthesis-star comment — also traditional *)
writeln('done');
end. // Single-line comment
/* Multi-line
block comment */
/// <summary>XML documentation comment</summary>
Console.WriteLine("done"); Pascal offers three comment syntaxes. The
{ } brace style is the most traditional; (* *) is equally standard and predates brace comments; // is a Free Pascal extension from C++. C# uses the same // and /* */ as C, plus /// triple-slash XML doc comments that generate documentation automatically.Unit / Namespace Imports
program ImportsDemo;
uses SysUtils, Math;
begin
writeln(Format('Pi = %.4f', [Pi]));
writeln(Max(10, 20));
end. using static System.Math;
Console.WriteLine($"Pi = {PI:F4}");
Console.WriteLine(Max(10, 20)); Pascal's
uses clause imports entire units; the identifiers they export become directly available. C# uses using directives for namespaces, and using static to import static class members without qualification — the closest equivalent to Pascal's unit import model. The runner already includes using System;, so most standard library types are available without any directive.Declaration Order
program StructureDemo;
uses SysUtils;
const
Limit = 10;
type
Count = Integer;
var
result: Count;
function Double(value: Integer): Integer;
begin
Result := value * 2;
end;
begin
result := Double(5);
writeln(result);
end. const int limit = 10;
int Double(int value) => value * 2;
int result = Double(5);
Console.WriteLine(result); Pascal enforces a strict top-down declaration order:
uses → const → type → var → subprogram definitions → main begin...end.. Every identifier must be declared before use. C# imposes no such ordering; local functions, constants, and variables can appear in any order in a top-level program, and the compiler resolves references across the file.Variables & Types
Variable Declaration
program VarDemo;
var
count: Integer;
message: string;
price: Double;
active: Boolean;
begin
count := 10;
message := 'hello';
price := 9.99;
active := True;
writeln(count, ' ', message, ' ', price:0:2, ' ', active);
end. int count = 10;
string message = "hello";
double price = 9.99;
bool active = true;
Console.WriteLine($"{count} {message} {price:F2} {active}"); Pascal requires every variable to be declared in a
var block before any code executes, with an explicit type. C# allows inline declarations anywhere a statement is legal. Pascal uses := for assignment and = for comparison; C# flips this — = assigns and == compares. Pascal's Boolean literals are True/False (capitalised); C#'s are lowercase true/false.Type Inference
program TypesDemo;
var
temperature: Double;
greeting: string;
items: Integer;
begin
temperature := 36.6;
greeting := 'Pascal requires explicit types';
items := 42;
writeln(temperature:0:1);
writeln(greeting);
writeln(items);
end. var temperature = 36.6; // inferred as double
var greeting = "C# can infer types"; // inferred as string
var items = new List<int> { 1, 2, 3 }; // inferred as List<int>
Console.WriteLine(temperature);
Console.WriteLine(greeting);
Console.WriteLine(items.Count); Pascal requires an explicit type for every variable — there is no inference. C#'s
var keyword lets the compiler infer the type from the right-hand side of the initialiser. The type is still fixed at compile time; var is not dynamic typing. Use explicit types in C# for clarity in public APIs and var for local variables where the type is obvious from context.Constants
program ConstantsDemo;
const
MaxItems = 100;
Version = '3.2';
Gravity = 9.81;
begin
writeln(MaxItems);
writeln(Version);
writeln(Gravity:0:2);
end. const int maxItems = 100;
const string version = "13";
const double gravity = 9.81;
Console.WriteLine(maxItems);
Console.WriteLine(version);
Console.WriteLine($"{gravity:F2}"); Both languages have true compile-time constants. Pascal groups them in a
const block before the main body; C# uses the const keyword inline. Pascal constants are untyped by default — the compiler infers the type from the literal. C# const requires an explicit type. Both reject any attempt to assign to a constant after declaration.Integer Types
program IntegerTypes;
var
small: ShortInt; { -128..127 }
medium: Integer; { -2147483648..2147483647 }
large: Int64; { 64-bit signed }
unsigned: Cardinal; { 0..4294967295 }
begin
small := 127;
medium := 2147483647;
large := 9223372036854775807;
unsigned := 4294967295;
writeln(small);
writeln(medium);
writeln(large);
writeln(unsigned);
end. sbyte small = 127; // -128..127
int medium = 2_147_483_647; // 32-bit signed
long large = 9_223_372_036_854_775_807; // 64-bit signed
uint unsigned = 4_294_967_295; // 0..4294967295
Console.WriteLine(small);
Console.WriteLine(medium);
Console.WriteLine(large);
Console.WriteLine(unsigned); Both languages offer the same set of fixed-width integer types; the names just differ. Pascal's
ShortInt becomes C#'s sbyte; Integer becomes int; Int64 becomes long; Cardinal becomes uint. C# supports digit separators (2_147_483_647) to improve readability of large literals; Pascal does not.Float Types
program FloatTypes;
uses SysUtils;
var
temperature: Single; { 32-bit }
ratio: Double; { 64-bit — preferred }
begin
temperature := 36.6;
ratio := 1.0 / 3.0;
writeln(temperature:0:1);
writeln(Format('%.6f', [ratio]));
end. float temperature = 36.6f; // 32-bit — note the 'f' suffix
double ratio = 1.0 / 3.0; // 64-bit — preferred
Console.WriteLine($"{temperature:F1}");
Console.WriteLine($"{ratio:F6}"); Pascal's
Double maps to C#'s double; Single maps to float. In C#, a floating-point literal without a suffix is double by default — adding f makes it float. Pascal uses value:width:decimals format specifiers inside writeln; C# uses composite format strings like {value:F2} inside interpolated strings or Console.WriteLine.Type Conversion
program TypeConversion;
uses SysUtils;
var
number: Integer;
text: string;
value: Double;
begin
number := 42;
text := IntToStr(number);
writeln(text + ' items');
writeln(StrToInt('99') * 2);
value := 3.14;
writeln(Trunc(value));
writeln(Round(value));
end. int number = 42;
string text = number.ToString();
Console.WriteLine(text + " items");
Console.WriteLine(int.Parse("99") * 2);
double value = 3.14;
Console.WriteLine((int)value); // truncate toward zero
Console.WriteLine((int)Math.Round(value)); // round Pascal uses procedural conversion functions from
SysUtils: IntToStr, StrToInt, Trunc, Round. C# uses method calls (.ToString(), int.Parse()) and explicit cast syntax (int)value. Both languages reject implicit numeric-to-string coercion — you must convert explicitly. C#'s int.TryParse is the safe alternative to int.Parse when the input might be invalid.Strings
Concatenation
program StringConcat;
uses SysUtils;
var
first: string;
second: string;
count: Integer;
begin
first := 'Hello';
second := 'World';
count := 42;
writeln(first + ', ' + second + '!');
writeln('Count: ' + IntToStr(count));
end. string first = "Hello";
string second = "World";
int count = 42;
Console.WriteLine(first + ", " + second + "!");
Console.WriteLine("Count: " + count); // int auto-converts to string Both languages use
+ for string concatenation. Unlike Pascal, C# automatically converts numbers and other types to strings when they appear in a concatenation expression with a string — no explicit IntToStr call needed. For more complex formatting, C# string interpolation ($"...") is generally preferred over concatenation.String Formatting / Interpolation
program StringFormat;
uses SysUtils;
var
name: string;
age: Integer;
price: Double;
begin
name := 'Alice';
age := 30;
price := 9.99;
writeln(Format('%s is %d years old', [name, age]));
writeln(Format('Price: $%.2f', [price]));
end. string name = "Alice";
int age = 30;
double price = 9.99;
Console.WriteLine($"{name} is {age} years old");
Console.WriteLine($"Price: {price:F2}"); Pascal has no string interpolation syntax; building formatted strings requires the
Format function (from SysUtils) with printf-style placeholders. C#'s interpolated string syntax ($"...{expression}...") embeds expressions directly in string literals. Format specifiers like :F2 or :N0 inside the braces control number formatting.String Methods
program StringMethods;
uses SysUtils;
var
text: string;
begin
text := ' Hello, World! ';
writeln(Trim(text));
writeln(UpperCase(text));
writeln(LowerCase(text));
writeln(Length(Trim(text)));
end. string text = " Hello, World! ";
Console.WriteLine(text.Trim());
Console.WriteLine(text.ToUpper());
Console.WriteLine(text.ToLower());
Console.WriteLine(text.Trim().Length); Pascal string functions are free-standing procedures and functions (from
SysUtils): Trim(s), UpperCase(s), Length(s). C# follows the object-oriented model: strings have methods called with dot notation — s.Trim(), s.ToUpper(), s.Length (a property, not a method). C#'s string type also has Contains, StartsWith, EndsWith, Replace, Split, and many more.Search & Substring
program StringSearch;
var
text: string;
position: Integer;
begin
text := 'Hello, World!';
position := Pos('World', text);
writeln(position); { 8 — 1-indexed }
writeln(position > 0); { TRUE if found }
writeln(Copy(text, 1, 5)); { "Hello" }
writeln(Copy(text, 8, 6)); { "World!" }
end. string text = "Hello, World!";
int position = text.IndexOf("World");
Console.WriteLine(position); // 7 — 0-indexed
Console.WriteLine(position >= 0); // true if found
Console.WriteLine(text.Substring(0, 5)); // "Hello"
Console.WriteLine(text[7..]); // "World!" — C# 8 range syntax Pascal's
Pos returns a 1-based position (0 means not found); C#'s IndexOf returns a 0-based position (-1 means not found). Pascal's Copy(str, start, count) extracts a substring with a 1-based start; C# offers Substring(start, length) (0-based) and the modern range syntax str[start..end]. The off-by-one difference between 1-based and 0-based indexing is the most common source of bugs when translating Pascal logic to C#.Split & Join
{$H+}
program StringSplit;
uses SysUtils, StrUtils, Types;
var
sentence: string;
words: TStringDynArray;
index: Integer;
begin
sentence := 'one two three';
words := SplitString(sentence, ' ');
for index := 0 to Length(words) - 1 do
writeln(words[index]);
end. string sentence = "one two three";
string[] words = sentence.Split(' ');
foreach (string word in words)
Console.WriteLine(word);
string joined = string.Join(", ", words);
Console.WriteLine(joined); Pascal's
SplitString (from StrUtils) returns a TStringDynArray, a zero-indexed dynamic array of strings. C#'s string.Split returns a string[] and integrates naturally with foreach and LINQ. C# also provides string.Join to re-combine an array with a separator — Pascal has no direct equivalent and requires a manual loop.Collections
Fixed-Size Array
program FixedArray;
var
numbers: array[1..5] of Integer;
index: Integer;
begin
numbers[1] := 10;
numbers[2] := 20;
numbers[3] := 30;
numbers[4] := 40;
numbers[5] := 50;
for index := 1 to 5 do
writeln(numbers[index]);
end. int[] numbers = { 10, 20, 30, 40, 50 };
foreach (int number in numbers)
Console.WriteLine(number);
Console.WriteLine(numbers.Length); Pascal fixed arrays use a 1-based index by default (
array[1..5]) though you can start at any ordinal value. C# arrays are always 0-based. Pascal array declarations specify the index range; C# array declarations specify only the element type and the size is implicit from the initialiser. C# arrays have a Length property; Pascal uses the global Length(arr) function.Dynamic Array / List
program DynamicArray;
var
numbers: array of Integer;
begin
SetLength(numbers, 3);
numbers[0] := 10;
numbers[1] := 20;
numbers[2] := 30;
SetLength(numbers, 4);
numbers[3] := 40;
writeln(Length(numbers));
writeln(numbers[2]);
end. var numbers = new List<int> { 10, 20, 30 };
numbers.Add(40);
Console.WriteLine(numbers.Count);
Console.WriteLine(numbers[2]);
numbers.Remove(20);
Console.WriteLine(numbers.Count); Pascal's dynamic array (
array of T) is resized with SetLength — there is no built-in Add method. C# List<T> is the standard growable collection with Add, Remove, Insert, Count, and full LINQ support. Anders Hejlsberg brought the concept of type-safe generic collections from Delphi to C# — Pascal programmers will find List<T> immediately intuitive despite the different syntax.Dictionary / Map
program DictionaryDemo;
type
TEntry = record
key: string;
value: Integer;
end;
var
scores: array[1..3] of TEntry;
index: Integer;
begin
scores[1].key := 'Alice'; scores[1].value := 95;
scores[2].key := 'Bob'; scores[2].value := 87;
scores[3].key := 'Carol'; scores[3].value := 92;
for index := 1 to 3 do
writeln(scores[index].key, ': ', scores[index].value);
end. var scores = new Dictionary<string, int>
{
{ "Alice", 95 },
{ "Bob", 87 },
{ "Carol", 92 },
};
Console.WriteLine(scores["Alice"]);
scores["Dave"] = 78;
foreach (var entry in scores)
Console.WriteLine($"{entry.Key}: {entry.Value}"); Standard Pascal has no built-in associative map. The idiomatic workaround is an array of records with key and value fields, searched linearly. C#'s
Dictionary<TKey, TValue> provides O(1) hash-map lookups and is one of the most frequently used collection types. Free Pascal's TFPGMap from the fgl unit is the closest equivalent, but it sorts by key and uses a binary search rather than hashing.Iteration Patterns
program Iteration;
var
fruits: array[1..4] of string;
index: Integer;
begin
fruits[1] := 'apple';
fruits[2] := 'banana';
fruits[3] := 'cherry';
fruits[4] := 'date';
for index := 1 to Length(fruits) do
writeln(index, ': ', fruits[index]);
end. var fruits = new List<string> { "apple", "banana", "cherry", "date" };
foreach (string fruit in fruits)
Console.WriteLine(fruit);
for (int i = 0; i < fruits.Count; i++)
Console.WriteLine($"{i + 1}: {fruits[i]}"); Pascal has no foreach — iterating a collection always uses a numeric
for loop with an explicit index. C# provides foreach for element-at-a-time iteration over any IEnumerable, which is more expressive for the common case where the index is not needed. The traditional indexed for loop (for (int i = 0; ...)) is still available when the index matters.Control Flow
If / Else
program IfElse;
var
score: Integer;
begin
score := 85;
if score >= 90 then
writeln('A')
else if score >= 80 then
writeln('B')
else if score >= 70 then
writeln('C')
else
writeln('Below C');
end. int score = 85;
if (score >= 90)
Console.WriteLine("A");
else if (score >= 80)
Console.WriteLine("B");
else if (score >= 70)
Console.WriteLine("C");
else
Console.WriteLine("Below C"); The if/else structure is nearly identical between the two languages. The key differences are: Pascal uses
then after the condition; C# uses parentheses around the condition and curly braces (or single-statement bodies without braces). Pascal places a semicolon before else only when there is no begin...end block — a common source of Pascal syntax errors.Case / Switch Expression
program CaseDemo;
var
dayNumber: Integer;
dayName: string;
begin
dayNumber := 3;
case dayNumber of
1: dayName := 'Monday';
2: dayName := 'Tuesday';
3: dayName := 'Wednesday';
4, 5: dayName := 'Thursday or Friday';
6, 7: dayName := 'Weekend';
else dayName := 'Unknown';
end;
writeln(dayName);
end. int dayNumber = 3;
string dayName = dayNumber switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 or 5 => "Thursday or Friday",
6 or 7 => "Weekend",
_ => "Unknown",
};
Console.WriteLine(dayName); Pascal's
case...of is a statement matching ordinal values. C# 8 introduced switch expressions, which are expressions that return a value — the entire construct can be used on the right side of an assignment. Multiple values share a branch in Pascal with a comma list; C# uses the or pattern (4 or 5 =>). Neither language falls through between branches by default.Pattern Matching
program PatternDemo;
var
value: Integer;
begin
value := 42;
if value > 0 then
writeln('positive')
else if value = 0 then
writeln('zero')
else
writeln('negative');
end. object value = 42;
string description = value switch
{
int n when n > 100 => "large number",
int n when n > 0 => "positive number",
0 => "zero",
string s => $"string: {s}",
_ => "something else",
};
Console.WriteLine(description); C# has substantially richer pattern matching than Pascal. C# switch expressions can match on type (
int n), value, and guard conditions (when n > 0) in a single construct, and can also discriminate on the runtime type of an object. Pascal's case statement matches only on ordinal values — there is no type-testing pattern. C#'s pattern matching is one of the features that has no Pascal equivalent.Loops
For Loop
program ForLoops;
var
counter: Integer;
begin
for counter := 1 to 5 do
writeln(counter);
writeln('---');
for counter := 5 downto 1 do
writeln(counter);
end. for (int counter = 1; counter <= 5; counter++)
Console.WriteLine(counter);
Console.WriteLine("---");
for (int counter = 5; counter >= 1; counter--)
Console.WriteLine(counter); Pascal's
for counter := 1 to 5 do always steps by exactly 1; downto steps by -1. There is no step-size option. C#'s for loop is more general: any initialiser, condition, and increment expression are allowed, so any step size is possible. C# also has foreach for iterating collections without an index.While Loop
program WhileLoop;
var
count: Integer;
begin
count := 1;
while count <= 5 do begin
writeln(count);
count := count + 1;
end;
end. int count = 1;
while (count <= 5)
{
Console.WriteLine(count);
count++;
} The while loop is structurally identical in both languages. Pascal wraps a multi-statement body in
begin...end; C# uses braces. Pascal has no ++ or += operators — increment is written as count := count + 1 or using the built-in Inc(count) procedure. C# supports count++, count += 1, and ++count.Repeat-Until / Do-While
program RepeatUntil;
var
number: Integer;
begin
number := 0;
repeat
number := number + 1;
writeln(number);
until number >= 5;
end. int number = 0;
do
{
number++;
Console.WriteLine(number);
} while (number < 5); Both languages provide a post-test loop that always executes the body at least once. Pascal's
repeat...until condition exits when the condition becomes true. C#'s do...while (condition) exits when the condition becomes false — the exit condition is the inverse. This is a subtle but important difference: converting a repeat...until to do...while requires negating the condition.Break & Continue
program BreakContinue;
var
number: Integer;
begin
for number := 1 to 10 do begin
if number mod 2 = 0 then Continue;
if number > 7 then Break;
writeln(number);
end;
end. for (int number = 1; number <= 10; number++)
{
if (number % 2 == 0) continue;
if (number > 7) break;
Console.WriteLine(number);
} Pascal uses
Break and Continue (capitalised, as they are built-in procedures); C# uses lowercase break and continue (reserved keywords). The semantics are identical: break exits the innermost loop; continue skips the rest of the current iteration and begins the next. Pascal's modulo operator is mod; C# uses the % operator.Procedures & Methods
Procedure (Void Method)
program ProcedureDemo;
procedure Greet(name: string);
begin
writeln('Hello, ', name, '!');
end;
begin
Greet('World');
Greet('Pascal');
end. void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
Greet("World");
Greet("C#"); Pascal distinguishes between procedures (which return nothing) and functions (which return a value). C# unifies them: a method that returns nothing uses the
void Function (Return Value)
program FunctionDemo;
function Square(number: Integer): Integer;
begin
Result := number * number;
end;
function Greet(name: string): string;
begin
Result := 'Hello, ' + name + '!';
end;
begin
writeln(Square(7));
writeln(Greet('World'));
end. int Square(int number) => number * number;
string Greet(string name) => $"Hello, {name}!";
Console.WriteLine(Square(7));
Console.WriteLine(Greet("World")); Pascal functions use
Result := value to set the return value (a special built-in variable in Free Pascal). Standard Pascal used the function's own name as the return assignment target (Square := n * n). C# uses return value or, for single-expression methods, expression-body syntax (=> expression). There is no separate procedure/function keyword in C# — the return type alone makes the distinction.Pass by Reference
program VarParameter;
procedure Swap(var first: Integer; var second: Integer);
var
temporary: Integer;
begin
temporary := first;
first := second;
second := temporary;
end;
var
alpha, beta: Integer;
begin
alpha := 10;
beta := 20;
Swap(alpha, beta);
writeln(alpha, ' ', beta);
end. void Swap(ref int first, ref int second)
{
int temporary = first;
first = second;
second = temporary;
}
int alpha = 10, beta = 20;
Swap(ref alpha, ref beta);
Console.WriteLine($"{alpha} {beta}"); Pascal's
var parameter modifier passes arguments by reference; changes inside the procedure affect the caller's variable. C# uses the ref keyword at both the declaration site and the call site (Swap(ref alpha, ref beta)). C# also has out for parameters that are only assigned (not read) inside the method, which Pascal approximates with uninitialized var parameters.Default / Optional Parameters
program DefaultParams;
procedure Log(message: string; prefix: string = 'INFO');
begin
writeln('[' + prefix + '] ' + message);
end;
begin
Log('Server started');
Log('Something failed', 'ERROR');
end. void Log(string message, string prefix = "INFO")
{
Console.WriteLine($"[{prefix}] {message}");
}
Log("Server started");
Log("Something failed", "ERROR"); Both languages support optional parameters with default values using identical syntax — the default value follows a
= sign in the parameter list. Parameters with defaults must appear after required parameters in both languages. C# additionally supports named arguments at the call site (Log(message: "text", prefix: "WARN")), which Pascal does not.Overloading
program Overloading;
uses SysUtils;
function Describe(value: Integer): string; overload;
begin
Result := 'Integer: ' + IntToStr(value);
end;
function Describe(value: Double): string; overload;
begin
Result := 'Double: ' + FloatToStr(value);
end;
function Describe(value: string): string; overload;
begin
Result := 'String: ' + value;
end;
begin
writeln(Describe(42));
writeln(Describe(3.14));
writeln(Describe('hello'));
end. Console.WriteLine(Formatter.Describe(42));
Console.WriteLine(Formatter.Describe(3.14));
Console.WriteLine(Formatter.Describe("hello"));
// Local functions cannot be overloaded — use a static helper class
static class Formatter
{
public static string Describe(int value) => $"Integer: {value}";
public static string Describe(double value) => $"Double: {value}";
public static string Describe(string value) => $"String: {value}";
} Both languages support method overloading — multiple definitions with the same name but different parameter types. Free Pascal requires the
overload directive on each overloaded variant. C# infers overloading automatically from the parameter signatures, with no keyword needed. The compiler selects the best matching overload at the call site in both languages.Classes & OOP
Class Definition
program ClassDemo;
uses SysUtils;
type
TAnimal = class
private
FName: string;
public
constructor Create(aName: string);
function Speak: string;
property Name: string read FName;
end;
constructor TAnimal.Create(aName: string);
begin
FName := aName;
end;
function TAnimal.Speak: string;
begin
Result := FName + ' makes a sound';
end;
var
animal: TAnimal;
begin
animal := TAnimal.Create('Dog');
writeln(animal.Speak);
writeln(animal.Name);
animal.Free;
end. var animal = new Animal("Dog");
Console.WriteLine(animal.Speak());
Console.WriteLine(animal.Name);
class Animal
{
public string Name { get; }
public Animal(string name) => Name = name;
public virtual string Speak() => $"{Name} makes a sound";
} Pascal class bodies separate declaration (in the
type block) from implementation (the method bodies written after the type block). C# defines declaration and implementation together inside the class braces. Pascal private fields use the F prefix by convention (FName). Pascal requires manual memory management with Free; C# has garbage collection. Properties in Pascal use explicit read/write field references; C# auto-properties do this in one line.Inheritance
program InheritanceDemo;
type
TShape = class
public
function Area: Double; virtual; abstract;
procedure Describe;
end;
TCircle = class(TShape)
private
FRadius: Double;
public
constructor Create(radius: Double);
function Area: Double; override;
end;
procedure TShape.Describe;
begin
writeln('Area = ', Area:0:2);
end;
constructor TCircle.Create(radius: Double);
begin
FRadius := radius;
end;
function TCircle.Area: Double;
begin
Result := 3.14159 * FRadius * FRadius;
end;
var
circle: TCircle;
begin
circle := TCircle.Create(5.0);
circle.Describe;
circle.Free;
end. var circle = new Circle(5.0);
circle.Describe();
abstract class Shape
{
public abstract double Area();
public void Describe() => Console.WriteLine($"Area = {Area():F2}");
}
class Circle(double radius) : Shape
{
public override double Area() => Math.PI * radius * radius;
} Inheritance syntax differs significantly. Pascal uses
class(ParentClass) for the base class and the virtual; abstract; directive pair for abstract methods; overrides require override. C# uses : ParentClass, abstract on the method, and override on the implementation. C# 12 primary constructors (class Circle(double radius)) eliminate the constructor boilerplate entirely.Interfaces
program InterfaceDemo;
type
IGreeter = interface
function Greet(name: string): string;
end;
TFormalGreeter = class(TInterfacedObject, IGreeter)
public
function Greet(name: string): string;
end;
function TFormalGreeter.Greet(name: string): string;
begin
Result := 'Good day, ' + name + '.';
end;
var
greeter: IGreeter;
begin
greeter := TFormalGreeter.Create;
writeln(greeter.Greet('Alice'));
end. IGreeter greeter = new FormalGreeter();
Console.WriteLine(greeter.Greet("Alice"));
interface IGreeter
{
string Greet(string name);
}
class FormalGreeter : IGreeter
{
public string Greet(string name) => $"Good day, {name}.";
} Interfaces work the same in both languages. Pascal requires class implementations to inherit from
TInterfacedObject (which provides COM-compatible reference counting) when implementing an interface. C# classes implement interfaces simply by listing them after :. C# interfaces can also define default implementations for methods (a C# 8 feature), which Pascal interfaces cannot.Records
Record Type
program RecordDemo;
type
TPoint = record
X: Double;
Y: Double;
end;
var
point1, point2: TPoint;
begin
point1.X := 3.0;
point1.Y := 4.0;
point2 := point1; { value copy }
point2.X := 10.0;
writeln(point1.X:0:1, ', ', point1.Y:0:1); { 3.0, 4.0 — unchanged }
writeln(point2.X:0:1, ', ', point2.Y:0:1); { 10.0, 4.0 }
end. var point1 = new Point(3.0, 4.0);
var point2 = point1 with { X = 10.0 }; // non-destructive update
Console.WriteLine($"{point1.X}, {point1.Y}"); // 3, 4 — unchanged
Console.WriteLine($"{point2.X}, {point2.Y}"); // 10, 4
record Point(double X, double Y); Pascal records are value types: assigning one record to another copies all fields, and modifying the copy does not affect the original. C# records (introduced in C# 9) are also value-semantic by default and support non-destructive mutation via the
with expression. C# positional records (record Point(double X, double Y)) declare both the type and a primary constructor in one line. C# record structs exist too, explicitly stack-allocated like Pascal records.Value Equality
program RecordEquality;
type
TPoint = record
X, Y: Integer;
end;
var
alpha, beta: TPoint;
begin
alpha.X := 1; alpha.Y := 2;
beta.X := 1; beta.Y := 2;
writeln(alpha.X = beta.X); { field-by-field comparison }
writeln(alpha.Y = beta.Y);
{ Pascal has no built-in record equality operator }
end. var alpha = new Point(1, 2);
var beta = new Point(1, 2);
Console.WriteLine(alpha == beta); // True — value equality
Console.WriteLine(alpha.Equals(beta)); // True
record Point(int X, int Y); Pascal has no built-in equality operator for records — you must compare each field individually. C# records automatically synthesise
== and Equals based on all properties, providing structural equality out of the box. This is one of the most significant quality-of-life improvements C# records offer over Pascal records for immutable data modelling.Error Handling
Try / Except (Catch)
program TryExcept;
uses SysUtils;
var
result: Integer;
begin
try
result := StrToInt('not a number');
writeln(result);
except
on error: EConvertError do
writeln('Conversion error: ', error.Message);
on error: Exception do
writeln('Unknown error: ', error.Message);
end;
end. try
{
int result = int.Parse("not a number");
Console.WriteLine(result);
}
catch (FormatException error)
{
Console.WriteLine($"Conversion error: {error.Message}");
}
catch (Exception error)
{
Console.WriteLine($"Unknown error: {error.Message}");
} The structure is nearly identical. Pascal uses
try...except with on ErrorVariable: ExceptionType do for typed catches; C# uses try...catch (ExceptionType variable). Pascal's except can also contain an untyped else clause for a catch-all. Exception class names in Pascal typically start with E (e.g., EConvertError); C# uses a Exception suffix (e.g., FormatException). The inheritance hierarchy is structurally the same.Finally Block
program TryFinally;
uses SysUtils;
var
resource: string;
begin
resource := 'open';
try
try
writeln('Using resource');
raise Exception.Create('Something went wrong');
finally
resource := 'closed';
writeln('Resource: ', resource);
end;
except
on error: Exception do
writeln('Caught: ', error.Message);
end;
end. string resource = "open";
try
{
Console.WriteLine("Using resource");
throw new Exception("Something went wrong");
}
catch (Exception error)
{
Console.WriteLine($"Caught: {error.Message}");
}
finally
{
resource = "closed";
Console.WriteLine($"Resource: {resource}");
} Both languages support
finally blocks that execute regardless of whether an exception was raised. Pascal uses raise ExceptionClass.Create(message) to throw; C# uses throw new ExceptionClass(message). Pascal's try...finally is separate from try...except — you cannot combine them in one block as C# allows with catch + finally together.Custom Exception
program CustomException;
uses SysUtils;
type
EValidationError = class(Exception)
private
FFieldName: string;
public
constructor Create(fieldName, messageText: string);
property FieldName: string read FFieldName;
end;
constructor EValidationError.Create(fieldName, messageText: string);
begin
inherited Create(messageText);
FFieldName := fieldName;
end;
begin
try
raise EValidationError.Create('email', 'Invalid email address');
except
on error: EValidationError do
writeln('Field "', error.FieldName, '": ', error.Message);
end;
end. try
{
throw new ValidationException("email", "Invalid email address");
}
catch (ValidationException error)
{
Console.WriteLine($"Field '{error.FieldName}': {error.Message}");
}
class ValidationException(string fieldName, string message)
: Exception(message)
{
public string FieldName { get; } = fieldName;
} Custom exceptions follow the same pattern in both languages: inherit from the base exception class and add properties. Pascal convention names exception classes with an
E prefix; C# convention uses an Exception suffix. C# 12 primary constructors make the declaration much more compact. Both languages call the parent constructor to set the Message property.LINQ & Functional
Filtering a Collection
program FilterDemo;
var
numbers: array[1..8] of Integer;
evens: array of Integer;
count: Integer;
index: Integer;
begin
numbers[1] := 1; numbers[2] := 2; numbers[3] := 3;
numbers[4] := 4; numbers[5] := 5; numbers[6] := 6;
numbers[7] := 7; numbers[8] := 8;
count := 0;
for index := 1 to 8 do
if numbers[index] mod 2 = 0 then
Inc(count);
SetLength(evens, count);
count := 0;
for index := 1 to 8 do
if numbers[index] mod 2 = 0 then begin
evens[count] := numbers[index];
Inc(count);
end;
for index := 0 to Length(evens) - 1 do
writeln(evens[index]);
end. var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
var evens = numbers.Where(n => n % 2 == 0).ToList();
foreach (int number in evens)
Console.WriteLine(number); Pascal has no built-in functional collection operations. Filtering requires a manual loop with an accumulator. C#'s LINQ
Where method accepts a lambda predicate and returns an IEnumerable<T> — a one-liner that replaces a dozen lines of Pascal. LINQ was directly influenced by Hejlsberg's earlier work on database query integration in Delphi (ClientDataSet, DataSnap) and is one of the most cited features C# programmers miss in Pascal.Mapping / Transforming
program MapDemo;
uses SysUtils;
var
names: array[1..3] of string;
greetings: array[1..3] of string;
index: Integer;
begin
names[1] := 'Alice';
names[2] := 'Bob';
names[3] := 'Carol';
for index := 1 to 3 do
greetings[index] := 'Hello, ' + names[index] + '!';
for index := 1 to 3 do
writeln(greetings[index]);
end. var names = new List<string> { "Alice", "Bob", "Carol" };
var greetings = names.Select(name => $"Hello, {name}!").ToList();
foreach (string greeting in greetings)
Console.WriteLine(greeting); LINQ's
Select applies a transformation function to every element — equivalent to map/collect in other languages. Pascal requires a manual loop. LINQ operations are composable and lazy: Where and Select can be chained before calling ToList(), and the actual iteration happens only once at the end.Lambda Expressions
program LambdaDemo;
type
TIntOp = function(value: Integer): Integer;
function ApplyTwice(operation: TIntOp; value: Integer): Integer;
begin
Result := operation(operation(value));
end;
function Double(value: Integer): Integer;
begin
Result := value * 2;
end;
begin
writeln(ApplyTwice(@Double, 3)); { 12 }
end. Func<int, int> doubleIt = n => n * 2;
Func<int, int> square = n => n * n;
int ApplyTwice(Func<int, int> operation, int value) =>
operation(operation(value));
Console.WriteLine(ApplyTwice(doubleIt, 3)); // 12
Console.WriteLine(ApplyTwice(square, 3)); // 81 Pascal supports passing functions as parameters using function-type variables (
TIntOp = function(...): ...). Named functions are referenced with @. C# uses Func<T, TResult> and Action<T> delegate types, and supports inline lambda expressions (n => n * 2) — anonymous functions defined at the point of use. C# lambdas can capture variables from the enclosing scope (closures); Pascal function-type variables cannot.LINQ Chaining
program ChainDemo;
uses SysUtils;
var
scores: array[1..5] of Integer;
total: Integer;
count: Integer;
index: Integer;
begin
scores[1] := 45; scores[2] := 82;
scores[3] := 91; scores[4] := 63;
scores[5] := 77;
total := 0;
count := 0;
for index := 1 to 5 do
if scores[index] >= 70 then begin
total := total + scores[index];
Inc(count);
end;
if count > 0 then
writeln(Format('Average of passing scores: %.1f', [total / count]));
end. var scores = new List<int> { 45, 82, 91, 63, 77 };
double average = scores
.Where(score => score >= 70)
.Average();
Console.WriteLine($"Average of passing scores: {average:F1}"); LINQ operations chain together fluently:
Where filters, then Average computes the mean of the remaining elements — what would be a 10-line Pascal loop becomes two readable lines. Other useful LINQ aggregates include Sum, Min, Max, Count, Any, All, and First/FirstOrDefault. The entire LINQ library is one of the most celebrated features in modern C#.Type System
Nullable Types
program NullableDemo;
var
value: Integer;
hasValue: Boolean;
begin
{ Pascal has no null for value types;
use a flag variable as a sentinel }
hasValue := False;
value := 0;
if hasValue then
writeln(value)
else
writeln('no value');
hasValue := True;
value := 42;
if hasValue then
writeln(value);
end. int? value = null; // nullable int
Console.WriteLine(value.HasValue); // False
Console.WriteLine(value ?? -1); // -1 (null-coalescing)
value = 42;
Console.WriteLine(value.HasValue); // True
Console.WriteLine(value.Value); // 42
Console.WriteLine(value ?? -1); // 42 Pascal value types (integers, booleans, records) cannot be null — the programmer must use a Boolean sentinel flag to represent "no value." C#'s
int? (equivalent to Nullable<int>) gives any value type a null state, accessed via .HasValue and .Value. The null-coalescing operator (??) returns the right operand when the left is null, making null handling concise.Enumerations
program EnumDemo;
type
TDirection = (North, South, East, West);
var
direction: TDirection;
begin
direction := North;
writeln(Ord(direction)); { 0 }
writeln(direction = North); { TRUE }
if direction = North then
writeln('Heading north');
for direction := North to West do
writeln(Ord(direction));
end. Direction direction = Direction.North;
Console.WriteLine((int)direction); // 0
Console.WriteLine(direction == Direction.North); // True
Console.WriteLine(direction); // North — name printed
if (direction == Direction.North)
Console.WriteLine("Heading north");
enum Direction { North, South, East, West } Both languages have first-class enumeration types. Pascal enumerations print their ordinal value; C# enumerations print their name by default. Pascal enum members are accessed without qualification (
North); C# requires the type name (Direction.North). C# also supports enum types with explicit underlying integer values (enum Status { Active = 1, Inactive = 2 }) and "flags enums" using [Flags].Generics
program GenericsDemo;
uses SysUtils;
generic function MaxOf<T>(first, second: T): T;
begin
{ In practice, Pascal generics need constraints }
Result := first;
end;
type
generic TStack<T> = class
private
FItems: array of T;
public
procedure Push(item: T);
function Pop: T;
function Count: Integer;
end;
procedure TStack.Push(item: T);
begin
SetLength(FItems, Length(FItems) + 1);
FItems[High(FItems)] := item;
end;
function TStack.Pop: T;
begin
Result := FItems[High(FItems)];
SetLength(FItems, Length(FItems) - 1);
end;
function TStack.Count: Integer;
begin
Result := Length(FItems);
end;
var
stack: specialize TStack<Integer>;
begin
stack := specialize TStack<Integer>.Create;
stack.Push(10);
stack.Push(20);
writeln(stack.Pop);
writeln(stack.Count);
stack.Free;
end. var stack = new Stack<int>();
stack.Push(10);
stack.Push(20);
Console.WriteLine(stack.Pop()); // 20
Console.WriteLine(stack.Count); // 1
class Stack<T>
{
private readonly List<T> items = [];
public void Push(T item) => items.Add(item);
public T Pop()
{
T item = items[^1];
items.RemoveAt(items.Count - 1);
return item;
}
public int Count => items.Count;
} Both languages support generics, but the syntax differs substantially. Free Pascal uses
generic and specialize keywords; C# uses angle-bracket notation consistently. C# generics were designed by Hejlsberg based on his experience with Delphi templates — they are arguably cleaner because the constraint system (where T : IComparable<T>) is more expressive. C#'s items[^1] uses the index-from-end operator introduced in C# 8.