C# -

Syntax

C# is a versatile and evolving programming language. This tutorial provides a comprehensive overview of C# syntax, covering various features and their evolution through different versions up to C# 12. This guide includes detailed explanations, examples, and best practices to help you understand the syntax and use it effectively.


C# has a rich set of keywords that serve as the building blocks for the language. These keywords represent predefined, reserved identifiers that have special meanings in the language. Below is a table of C# keywords, including a brief description and the version in which each keyword was introduced.


Keyword Description Introduced in Version
abstract Indicates that a class or method is abstract and must be implemented by derived classes. C# 1.0
as Performs a type conversion between compatible reference types. C# 1.0
base Accesses members of the base class from within a derived class. C# 1.0
bool Defines a boolean variable, which can hold true or false. C# 1.0
break Exits a loop or switch statement. C# 1.0
byte Defines an 8-bit unsigned integer. C# 1.0
case Defines a branch in a switch statement. C# 1.0
catch Handles exceptions thrown by try blocks. C# 1.0
char Defines a 16-bit Unicode character. C# 1.0
checked Enables overflow checking for integral-type arithmetic operations. C# 1.0
class Declares a class. C# 1.0
const Declares a constant field or local variable. C# 1.0
continue Passes control to the next iteration of a loop. C# 1.0
decimal Defines a 128-bit precise decimal value with 28-29 significant digits. C# 1.0
default Specifies the default label in a switch statement or the default value of a type. C# 1.0
delegate Declares a delegate type. C# 1.0
do Starts a do-while loop. C# 1.0
double Defines a double-precision floating point number. C# 1.0
else Defines an alternative branch in an if statement. C# 1.0
enum Declares an enumeration, a distinct type consisting of a set of named constants called the enumerator list. C# 1.0
event Declares an event. C# 1.0
explicit Declares an explicit conversion operator. C# 1.0
extern Declares a method implemented externally. C# 1.0
false Represents the boolean value false. C# 1.0
finally Defines a block of code that always runs after try/catch blocks. C# 1.0
fixed Fixes a variable so its address can be obtained. C# 1.0
float Defines a single-precision floating point number. C# 1.0
for Starts a for loop. C# 1.0
foreach Iterates through the elements of a collection. C# 1.0
goto Transfers control to a labeled statement. C# 1.0
if Starts an if statement. C# 1.0
implicit Declares an implicit conversion operator. C# 1.0
in Specifies that a parameter is passed by reference but not modified by the called method. C# 1.0
int Defines a 32-bit signed integer. C# 1.0
interface Declares an interface. C# 1.0
internal Specifies that a member is accessible only within its own assembly. C# 1.0
is Checks if an object is compatible with a given type. C# 1.0
lock Marks a statement block as a critical section by obtaining the mutual-exclusion lock for a given object. C# 1.0
long Defines a 64-bit signed integer. C# 1.0
namespace Declares a namespace. C# 1.0
new Creates objects and invokes constructors. C# 1.0
null Represents a null reference. C# 1.0
object Defines the base class of all types. C# 1.0
operator Overloads a built-in operator. C# 1.0
out Specifies that a parameter is passed by reference and can be modified by the called method. C# 1.0
override Overrides a base class method in a derived class. C# 1.0
params Specifies a parameter that takes a variable number of arguments. C# 1.0
private Specifies that a member is accessible only within its class or struct. C# 1.0
protected Specifies that a member is accessible within its class and derived classes. C# 1.0
public Specifies that a member is accessible from any other code. C# 1.0
readonly Declares a field that can only be assigned values as part of its declaration or in a constructor in the same class. C# 1.0
ref Indicates a value passed by reference. C# 1.0
return Exits a method and optionally returns a value. C# 1.0
sbyte Defines an 8-bit signed integer. C# 1.0
sealed Specifies that a class cannot be inherited. C# 1.0
short Defines a 16-bit signed integer. C# 1.0
sizeof Returns the size in bytes of a value type. C# 1.0
stackalloc Allocates a block of memory on the stack. C# 1.0
static Declares a static member, which belongs to the type itself rather than to a specific object. C# 1.0
string Defines a sequence of characters. C# 1.0
struct Declares a value type. C# 1.0
switch Selects a statement to execute from a list of candidates. C# 1.0
this Refers to the current instance of a class. C# 1.0
throw Throws an exception. C# 1.0
true Represents the boolean value true. C# 1.0
try Starts a block of code that will be checked for exceptions. C# 1.0
typeof Gets the System.Type object for a type. C# 1.0
uint Defines a 32-bit unsigned integer. C# 1.0
ulong Defines a 64-bit unsigned integer. C# 1.0
unchecked Disables overflow checking for integral-type arithmetic operations. C# 1.0
unsafe Declares an unsafe context, which allows pointers to be used. C# 1.0
ushort Defines a 16-bit unsigned integer. C# 1.0
using Defines a scope for an object that is disposed of when the scope ends. C# 1.0
virtual Declares a method or property that can be overridden in a derived class. C# 1.0
void Specifies that a method does not return a value. C# 1.0
volatile Indicates that a field might be modified by multiple threads. C# 1.0
while Starts a while loop. C# 1.0
yield Returns an iterator. C# 2.0
add Adds an event handler to an event. C# 2.0
alias Defines an alias for a namespace or type. C# 2.0
partial Defines partial classes, methods, and interfaces. C# 2.0
remove Removes an event handler from an event. C# 2.0
where Specifies a constraint on a generic type parameter. C# 2.0
ascending Specifies ascending order in a LINQ query. C# 3.0
descending Specifies descending order in a LINQ query. C# 3.0
from Specifies the data source in a LINQ query. C# 3.0
get Defines an accessor for getting the value of a property or indexer. C# 3.0
group Groups query results by a specified key in a LINQ query. C# 3.0
into Introduces a new identifier in a query. C# 3.0
join Joins two sequences based on a key in a LINQ query. C# 3.0
let Introduces a new range variable and initializes it with a value. C# 3.0
orderby Sorts query results in a LINQ query. C# 3.0
select Specifies the result of a LINQ query. C# 3.0
set Defines an accessor for setting the value of a property or indexer. C# 3.0
value Refers to the value of an object being assigned by a set accessor. C# 3.0
var Defines an implicitly typed local variable. C# 3.0
dynamic Defines a type that bypasses static type checking. C# 4.0
await Suspends the execution of an async method until the awaited task completes. C# 5.0
async Marks a method as asynchronous. C# 5.0
nameof Returns the name of a variable, type, or member as a string. C# 6.0
when Introduces a condition in a catch block or a case in a switch statement. C# 6.0
add Adds an event handler to an event. C# 6.0
remove Removes an event handler from an event. C# 6.0
default Specifies the default value of a type. C# 7.0
is Checks if an object is compatible with a given type. C# 7.0
in Specifies that a parameter is passed by reference but not modified by the called method. C# 7.2
readonly Declares a field that can only be assigned values as part of its declaration or in a constructor in the same class. C# 7.2
ref Indicates a value passed by reference. C# 7.2
using Defines a scope for an object that is disposed of when the scope ends. C# 8.0
private protected Specifies that a member is accessible only within its class or struct and derived classes within the same assembly. C# 8.0
protected internal Specifies that a member is accessible within its class and derived classes, and within its own assembly. C# 8.0
notnull Specifies that a generic type parameter must be a non-nullable value type. C# 8.0
record Declares a record type. C# 9.0
init Declares an init-only setter for a property or indexer. C# 9.0
global Defines a global alias. C# 10.0
file Defines a file-scoped namespace. C# 10.0
required Indicates that a member must be initialized during object creation. C# 11.0
u8 Specifies a UTF-8 string literal. C# 11.0
raw string literals Defines multi-line and embedded quote string literals. C# 11.0
primary constructors Allows declaration of primary constructors for non-record types. C# 12.0
collection literals Enables collection literals for supported types. C# 12.0

This table provides a quick reference to the keywords in C#, their descriptions, and the versions in which they were introduced. Understanding these keywords and their uses is essential for writing efficient and effective C# code.


1. Basic Syntax

The basic syntax of C# includes variables, data types, control statements, loops, and methods. Here are the foundational elements of C# syntax.


1.1 Variables and Data Types

Variables are used to store data, and data types define the kind of data a variable can hold. C# supports various data types, including primitive types like int, float, and bool, and complex types like classes and arrays.

Example:
        
            using System;

namespace VariablesAndDataTypes
{
    class Program
    {
        static void Main(string[] args)
        {
            int age = 30;
            string name = "John";
            bool isStudent = false;
            double salary = 3000.50;

            Console.WriteLine($"Name: {name}, Age: {age}, Student: {isStudent}, Salary: {salary}");
        }
    }
}
        
    

1.2 Control Statements

Control statements allow you to control the flow of execution in your programs. These include conditional statements (if, else, switch) and loops (for, while, do-while).

Example:
        
            using System;

namespace ControlStatements
{
    class Program
    {
        static void Main(string[] args)
        {
            int age = 20;

            if (age < 18)
            {
                Console.WriteLine("You are a minor.");
            }
            else if (age >= 18 && age < 60)
            {
                Console.WriteLine("You are an adult.");
            }
            else
            {
                Console.WriteLine("You are a senior.");
            }

            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"For loop iteration: {i}");
            }

            int j = 0;
            while (j < 5)
            {
                Console.WriteLine($"While loop iteration: {j}");
                j++;
            }
        }
    }
}
        
    

1.3 Methods

Methods are blocks of code that perform a specific task. They can take parameters and return values.

Example:
        
            using System;

namespace MethodsExample
{
    class Program
    {
        static void Main(string[] args)
        {
            PrintGreeting("John");
            int sum = Add(5, 3);
            Console.WriteLine($"Sum: {sum}");
        }

        static void PrintGreeting(string name)
        {
            Console.WriteLine($"Hello, {name}!");
        }

        static int Add(int a, int b)
        {
            return a + b;
        }
    }
}
        
    

2. Object-Oriented Programming (OOP) in C#

C# is an object-oriented language, supporting encapsulation, inheritance, polymorphism, and abstraction. These concepts help you create modular and reusable code.


2.1 Classes and Objects

Classes define the blueprint for objects. An object is an instance of a class.

Example:
        
            using System;

namespace ClassesAndObjects
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public void Introduce()
        {
            Console.WriteLine($"Hi, my name is {Name} and I am {Age} years old.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person
            {
                Name = "John",
                Age = 30
            };
            person.Introduce();
        }
    }
}
        
    

2.2 Inheritance

Inheritance allows you to create new classes based on existing ones, promoting code reuse.

Example:
        
            using System;

namespace Inheritance
{
    class Animal
    {
        public string Name { get; set; }

        public void Eat()
        {
            Console.WriteLine($"{Name} is eating.");
        }
    }

    class Dog : Animal
    {
        public void Bark()
        {
            Console.WriteLine($"{Name} is barking.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Dog dog = new Dog
            {
                Name = "Buddy"
            };
            dog.Eat();
            dog.Bark();
        }
    }
}
        
    

2.3 Polymorphism

Polymorphism allows methods to do different things based on the object they are acting upon.

Example:
        
            using System;

namespace Polymorphism
{
    class Animal
    {
        public virtual void MakeSound()
        {
            Console.WriteLine("Some generic animal sound.");
        }
    }

    class Dog : Animal
    {
        public override void MakeSound()
        {
            Console.WriteLine("Bark!");
        }
    }

    class Cat : Animal
    {
        public override void MakeSound()
        {
            Console.WriteLine("Meow!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Animal myDog = new Dog();
            Animal myCat = new Cat();

            myDog.MakeSound();
            myCat.MakeSound();
        }
    }
}
        
    

2.4 Abstraction

Abstraction hides the implementation details and shows only the essential features of an object.

Example:
        
            using System;

namespace Abstraction
{
    abstract class Shape
    {
        public abstract double GetArea();
    }

    class Circle : Shape
    {
        public double Radius { get; set; }

        public Circle(double radius)
        {
            Radius = radius;
        }

        public override double GetArea()
        {
            return Math.PI * Math.Pow(Radius, 2);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Shape circle = new Circle(5);
            Console.WriteLine($"The area of the circle is {circle.GetArea()}");
        }
    }
}
        
    

3. Advanced C# Syntax Features

Over the years, C# has introduced many advanced features that make coding more efficient and expressive.


3.1 Generics (C# 2.0)

Generics allow you to define classes, methods, and data structures with a placeholder for the type of data they store or use.

Example:
        
            using System;
using System.Collections.Generic;

namespace Generics
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
            DisplayElements(numbers);

            List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
            DisplayElements(names);
        }

        static void DisplayElements<T>(List<T> elements)
        {
            foreach (var element in elements)
            {
                Console.WriteLine(element);
            }
        }
    }
}
        
    

3.2 LINQ (Language Integrated Query, C# 3.0)

LINQ provides a consistent way to query various data sources (arrays, collections, databases) using query syntax similar to SQL.

Example:
        
            using System;
using System.Linq;

namespace LINQExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            var evenNumbers = from number in numbers
                              where number % 2 == 0
                              select number;

            Console.WriteLine("Even numbers:");
            foreach (var number in evenNumbers)
            {
                Console.WriteLine(number);
            }
        }
    }
}
        
    

3.3 Async/Await (C# 5.0)

Asynchronous programming allows you to write code that performs tasks asynchronously, improving the responsiveness of your applications.

Example:
        
            using System;
using System.Threading.Tasks;

namespace AsyncProgramming
{
    class Program
    {
        static async Task Main(string[] args)
        {
            await FetchDataAsync();
            Console.WriteLine("Data fetched successfully.");
        }

        static async Task FetchDataAsync()
        {
            // Simulate an asynchronous operation
            await Task.Delay(2000);
            Console.WriteLine("Data fetching...");
        }
    }
}
        
    

3.4 Null-conditional Operators (C# 6.0)

Null-conditional operators provide a concise way to handle null references in your code.

Example:
        
            using System;

namespace NullConditionalOperators
{
    class Program
    {
        static void Main(string[] args)
        {
            Person person = null;
            string name = person?.Name ?? "Unknown";
            Console.WriteLine($"Name: {name}");
        }
    }

    class Person
    {
        public string Name { get; set; }
    }
}
        
    

3.5 Pattern Matching (C# 7.0)

Pattern matching allows you to test a value against a pattern and extract values from complex data types in a more readable way.

Example:
        
            using System;

namespace PatternMatching
{
    class Program
    {
        static void Main(string[] args)
        {
            object[] data = { 42, "Hello", null, 3.14 };

            foreach (var item in data)
            {
                string message = item switch
                {
                    int i => $"Integer: {i}",
                    string s => $"String: {s}",
                    null => "Null",
                    _ => "Unknown type"
                };
                Console.WriteLine(message);
            }
        }
    }
}
        
    

3.6 Records (C# 9.0)

Records provide a concise way to define immutable reference types with value-based equality semantics.

Example:
        
            using System;

namespace Records
{
    record Person(string Name, int Age);

    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person("John", 30);
            Console.WriteLine(person);

            var (name, age) = person;
            Console.WriteLine($"Name: {name}, Age: {age}");
        }
    }
}
        
    

3.7 Init-only Setters (C# 9.0)

Init-only setters allow you to create immutable objects with properties that can only be set during object initialization.

Example:
        
            using System;

namespace InitOnlySetters
{
    class Person
    {
        public string Name { get; init; }
        public int Age { get; init; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person
            {
                Name = "John",
                Age = 30
            };
            Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
        }
    }
}
        
    

3.8 Top-level Statements (C# 9.0)

Top-level statements simplify the creation of simple programs by allowing you to write code without explicitly defining a class or Main method.

Example:
        
            using System;

Console.WriteLine("Hello, World!");

var person = new Person { Name = "John", Age = 30 };
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
        
    

3.9 File-scoped Namespaces (C# 10.0)

File-scoped namespaces reduce indentation by allowing you to declare a namespace that applies to all the code in a file.

Example:
        
            namespace FileScopedNamespace;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("File-scoped namespaces make your code more concise.");
    }
}
        
    

3.10 Global Usings (C# 10.0)

Global usings allow you to specify using directives that apply to all files in a project, reducing boilerplate code.

Example:
        
            // In a separate file, e.g., GlobalUsings.cs
global using System;
global using System.Collections.Generic;

namespace GlobalUsingDirectives
{
    class Program
    {
        static void Main(string[] args)
        {
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
            Console.WriteLine("Numbers:");
            numbers.ForEach(n => Console.WriteLine(n));
        }
    }
}
        
    

4. New Features in C# 11

C# 11 introduces several new features that enhance the language's expressiveness and efficiency.


4.1 Interpolated String Handlers

Interpolated string handlers allow more control over the creation of interpolated strings, optimizing performance for complex scenarios.

Example:
        
            using System;
using System.Runtime.CompilerServices;

namespace InterpolatedStringHandlersExample
{
    public class CustomLogger
    {
        public void Log(string message) => Console.WriteLine(message);

        public void Log(LogLevel level, [InterpolatedStringHandlerArgument("level")] CustomInterpolatedStringHandler handler)
        {
            if (level == LogLevel.Error)
            {
                Console.WriteLine($"[Error] {handler.GetFormattedText()}");
            }
            else
            {
                Console.WriteLine(handler.GetFormattedText());
            }
        }
    }

    [InterpolatedStringHandler]
    public ref struct CustomInterpolatedStringHandler
    {
        private readonly LogLevel _level;
        private readonly DefaultInterpolatedStringHandler _handler;

        public CustomInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel level)
        {
            _level = level;
            _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount);
        }

        public void AppendLiteral(string s) => _handler.AppendLiteral(s);
        public void AppendFormatted<T>(T value) => _handler.AppendFormatted(value);

        public string GetFormattedText() => _handler.ToString();
    }

    public enum LogLevel { Info, Error }

    class Program
    {
        static void Main(string[] args)
        {
            var logger = new CustomLogger();
            logger.Log(LogLevel.Info, $"This is an info message at {DateTime.Now}");
            logger.Log(LogLevel.Error, $"This is an error message at {DateTime.Now}");
        }
    }
}
        
    

4.2 Required Members

Required members ensure that certain properties must be set during object initialization, improving object consistency.

Example:
        
            using System;

namespace RequiredMembersExample
{
    public class Person
    {
        public required string Name { get; init; }
        public required int Age { get; init; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person { Name = "John", Age = 30 };
            Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
        }
    }
}
        
    

4.3 Raw String Literals

Raw string literals simplify working with multi-line strings and strings containing quotes.

Example:
        
            using System;

namespace RawStringLiteralsExample
{
    class Program
    {
        static void Main(string[] args)
        {
            string rawString = """
            This is a raw string literal
            that spans multiple lines
            and can include "quotes" without escaping.
            """;

            Console.WriteLine(rawString);
        }
    }
}
        
    

4.4 List Patterns

List patterns provide more expressive patterns for working with collections.

Example:
        
            using System;

namespace ListPatternsExample
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] numbers = { 1, 2, 3, 4, 5 };

            if (numbers is [1, 2, .., 5])
            {
                Console.WriteLine("The list starts with 1, 2 and ends with 5.");
            }

            if (numbers is [.., 4, 5])
            {
                Console.WriteLine("The list ends with 4, 5.");
            }
        }
    }
}
        
    

4.5 UTF-8 String Literals

UTF-8 string literals enhance support for UTF-8 encoding.

Example:
        
            using System;

namespace UTF8StringLiteralsExample
{
    class Program
    {
        static void Main(string[] args)
        {
            ReadOnlySpan<byte> utf8String = "Hello, World!"u8;
            Console.WriteLine(System.Text.Encoding.UTF8.GetString(utf8String));
        }
    }
}
        
    

5. Best Practices


6. Conclusion

C# is a powerful and versatile programming language that has evolved significantly since its inception. By understanding its syntax and leveraging the latest features, you can write efficient, maintainable, and robust applications. Follow best practices to ensure your code is clean, readable, and easy to maintain.