Skip to content

C# - Basic Syntax

C# is a modern, object-oriented, and type-safe programming language. In the Object-Oriented Programming (OOP) paradigm, a program is composed of various objects that interact with each other. These interactions are performed through methods (actions) and data is stored in properties or fields (attributes). Objects of the same kind belong to the same class.

Starting with .NET 6, C# introduced top-level statements, which simplify the code for new programs. You no longer need to manually declare a Program class or a Main method. Here is the simplest C# program:

// This single line is a complete, runnable C# program!
Console.WriteLine("Hello, Modern C# World!");

How to Run This:

  1. Install the .NET SDK.
  2. Create a file named Program.cs and add the code above.
  3. Open your terminal in the file’s directory and run dotnet run.

This modern approach is cleaner and more welcoming for beginners.

While top-level statements are great for simple programs, understanding the classic class structure is essential for building larger applications. Let’s look at a modernized example of a Rectangle class.

This example demonstrates key C# features like properties, constructors, and methods within a class. Notice the clear separation between the Rectangle’s logic and the program’s execution flow.

using System;
// Use file-scoped namespace for cleaner code (C# 10+)
namespace GeometryExample;
// The class definition for a Rectangle
public class Rectangle
{
// Auto-implemented properties are the modern way to define fields
public double Length { get; set; }
public double Width { get; set; }
// A constructor to initialize a new Rectangle object
public Rectangle(double length, double width)
{
// Basic validation
if (length <= 0 || width <= 0)
{
throw new ArgumentException("Length and width must be positive.");
}
Length = length;
Width = width;
}
// A method to calculate the area, using the properties
public double CalculateArea()
{
return Length * Width;
}
// Override ToString() for a useful string representation
public override string ToString()
{
// String interpolation ($) is the modern way to format strings
return $"Rectangle (Length: {Length}, Width: {Width})";
}
}
// Main execution logic using top-level statements
// Create an instance of the Rectangle class
var myRectangle = new Rectangle(4.5, 3.5);
// Display details using the overridden ToString() method
Console.WriteLine(myRectangle.ToString());
// Calculate and display the area
var area = myRectangle.CalculateArea();
Console.WriteLine($"Area: {area}");
// Example of handling potential errors
try
{
var invalidRectangle = new Rectangle(-1, 5);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Error creating rectangle: {ex.Message}");
}

When the above code is compiled and executed, it produces the following result:

Rectangle (Length: 4.5, Width: 3.5)
Area: 15.75
Error creating rectangle: Length and width must be positive.

C# is case-sensitive. myVariable and MyVariable are two different variables. The convention is to use camelCase for local variables (myVariable) and PascalCase for public members like classes, methods, and properties (MyRectangle).

Every statement in C# must end with a semicolon ;. It marks the end of a command.

Curly braces define a scope or code block. They are used for namespaces, classes, methods, and control flow statements (if, for, while).

Comments are ignored by the compiler and are used to explain code.

  • // for single-line comments.
  • /* ... */ for multi-line comments.
  • /// for XML documentation comments, which provide IntelliSense information in IDEs.
/// <summary>
/// This is an XML comment for the Rectangle class.
/// It appears in tooltips in Visual Studio.
/// </summary>
public class Rectangle { /*...*/ }

The using directive allows you to use types from a namespace without specifying their full name. For example, using System; lets you write Console.WriteLine() instead of System.Console.WriteLine(). Modern .NET projects often use implicit usings, where common namespaces are included automatically by the build process, reducing boilerplate code.

The class keyword is used to declare a class, which is a blueprint for creating objects.

An identifier is a name you give to a class, variable, method, etc. Rules for identifiers:

  • Must start with a letter or an underscore (_).
  • Can contain letters, numbers, and underscores.
  • Cannot be a C# keyword (unless prefixed with @, e.g., @class).

Keywords are reserved words with special meaning to the compiler. You cannot use them as identifiers without the @ prefix.

Reserved Keywords
abstract
catch
default
explicit
foreach
internal
object
public
sizeof
throw
unsafe
Contextual Keywords
add
descending
init
or
select
with

Conditional Statements (if, else if, else)

Section titled “Conditional Statements (if, else if, else)”

These statements control program flow based on boolean conditions.

int age = 20;
if (age >= 18)
{
Console.WriteLine("You are eligible to vote.");
}
else
{
Console.WriteLine("You are not eligible to vote.");
}
// Output: You are eligible to vote.

Loops execute a block of code repeatedly.

for (int i = 1; i <= 3; i++)
{
Console.WriteLine($"Iteration: {i}");
}
// Output:
// Iteration: 1
// Iteration: 2
// Iteration: 3
int count = 1;
while (count <= 3)
{
Console.WriteLine($"Count: {count}");
count++;
}
// Output:
// Count: 1
// Count: 2
// Count: 3

Use Console.WriteLine() to print text to the console. Modern C# favors string interpolation ($) for formatting.

string framework = "C#";
Console.WriteLine($"Welcome to modern {framework}!");
// Output: Welcome to modern C#!

Console.ReadLine() reads a line of text from the user. It always returns a string, which may be null.

Console.Write("Enter your name: ");
string? name = Console.ReadLine(); // string? indicates it can be null
Console.WriteLine($"Hello, {name ?? "stranger"}!");

Gracefully handle runtime errors using try-catch blocks. It’s best practice to catch specific exceptions like FormatException rather than the generic Exception.

Console.Write("Enter a number: ");
string? input = Console.ReadLine();
try
{
int number = Convert.ToInt32(input);
Console.WriteLine($"You entered: {number}");
}
catch (FormatException)
{
Console.WriteLine("Error: That was not a valid number.");
}
catch (Exception ex) // Catch other potential errors
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
  • Use Meaningful Names: Choose descriptive names for variables, methods, and classes (e.g., customerName instead of cn).
  • Follow Naming Conventions: Use camelCase for local variables and PascalCase for classes, methods, and properties.
  • Prefer Immutability: Where possible, make data read-only after creation to prevent bugs. Use readonly fields or init-only properties.
  • Use var for Readability: Use the var keyword for local variable declarations when the type is obvious from the right-hand side of the assignment (e.g., var user = new User();).
  • Embrace LINQ: Use Language-Integrated Query (LINQ) for powerful and readable data manipulation.
  • Write Small, Focused Methods: Each method should do one thing well. This improves testability and readability.
  • Handle Errors Gracefully: Don’t ignore exceptions. Use try-catch blocks to handle expected errors and ensure your application remains stable.