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.
The basic syntax of C# includes variables, data types, control statements, loops, and methods. Here are the foundational elements of C# syntax.
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}");
}
}
}
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++;
}
}
}
}
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;
}
}
}
C# is an object-oriented language, supporting encapsulation, inheritance, polymorphism, and abstraction. These concepts help you create modular and reusable code.
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();
}
}
}
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();
}
}
}
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();
}
}
}
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()}");
}
}
}
Over the years, C# has introduced many advanced features that make coding more efficient and expressive.
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);
}
}
}
}
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);
}
}
}
}
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...");
}
}
}
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; }
}
}
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);
}
}
}
}
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}");
}
}
}
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}");
}
}
}
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; }
}
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.");
}
}
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));
}
}
}
C# 11 introduces several new features that enhance the language's expressiveness and efficiency.
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}");
}
}
}
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}");
}
}
}
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);
}
}
}
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.");
}
}
}
}
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));
}
}
}
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.