1. Async Main
In C# 7.1 and later, the Main method, which serves as the entry point for console applications, can be asynchronous. This allows you to use the async and await keywords directly within Main, making it easier to handle asynchronous operations like I/O tasks.
Example: Async Main
using System;
using System.Threading.Tasks;
class Program
{
// Async Main method with Task return type
static async Task Main(string[] args)
{
Console.WriteLine("Starting async Main...");
// Asynchronously wait for 2 seconds
await Task.Delay(2000);
Console.WriteLine("Completed after 2 seconds.");
}
}
- Explanation: The
Mainmethod is marked asasyncand returns aTask. Inside the method, we can useawaitto asynchronously perform operations, such as waiting for 2 seconds withTask.Delay.
2. Default Literals
Default literals were introduced in C# 7.1 and simplify the syntax for specifying default values for types. Previously, when using the default keyword, you had to specify the type (e.g., default(int)), but with default literals, you can simply use default.
Example: Default Literals
class Program
{
static void PrintDefaultValues<T>()
{
// Using default literal
T defaultValue = default;
Console.WriteLine($"Default value of {typeof(T)} is: {defaultValue}");
}
static void Main()
{
PrintDefaultValues<int>(); // Output: Default value of System.Int32 is: 0
PrintDefaultValues<string>(); // Output: Default value of System.String is:
PrintDefaultValues<bool>(); // Output: Default value of System.Boolean is: False
}
}
- Explanation: The
defaultliteral allows you to initialize variables with their default values in a much cleaner way. For example,defaultforintis0, forboolisfalse, and for reference types likestring, it'snull.
3. In Parameters
The in keyword in C# 7.2 and later allows you to pass parameters by reference while guaranteeing that they will not be modified. This is useful when you want to avoid the overhead of copying large value types (like structs) but still ensure that the original data remains unchanged.
Example: In Parameters
using System;
struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
// Method accepting an in parameter
public static int CalculateDistance(in Point point)
{
// point.X and point.Y are read-only inside this method
return (int)Math.Sqrt(point.X * point.X + point.Y * point.Y);
}
}
class Program
{
static void Main()
{
Point p = new Point(3, 4);
// Passing p by reference without allowing modification
int distance = Point.CalculateDistance(in p);
Console.WriteLine($"Distance: {distance}"); // Output: Distance: 5
}
}
- Explanation: The
inparameter passes thePointstruct by reference, avoiding the overhead of copying it. The method can read the data but cannot modify it. This guarantees both performance and immutability for the passed data.
4. Ref Structs and ReadOnly Structs
C# introduces ref structs and readonly structs to improve value type safety and efficiency.
- Ref Structs: These structs are allocated on the stack instead of the heap and can only be passed by reference, ensuring they don't escape the stack (i.e., they can't be boxed or used in async methods).
- Readonly Structs: These structs are immutable, meaning their fields cannot be modified after initialization.
Example: Ref Structs
ref struct SpanExample
{
private readonly Span<int> _span;
public SpanExample(Span<int> span)
{
_span = span;
}
public void SetValue(int index, int value)
{
_span[index] = value;
}
public int GetValue(int index) => _span[index];
}
class Program
{
static void Main()
{
Span<int> numbers = stackalloc int[3] { 1, 2, 3 };
SpanExample example = new SpanExample(numbers);
example.SetValue(1, 42);
Console.WriteLine(example.GetValue(1)); // Output: 42
}
}
- Explanation: The
SpanExamplestruct is aref struct, meaning it cannot be allocated on the heap or passed to async methods. It wraps around aSpan<int>, a stack-allocated memory slice, and provides methods to interact with it.
Example: ReadOnly Structs
readonly struct ReadOnlyPoint
{
public int X { get; }
public int Y { get; }
public ReadOnlyPoint(int x, int y) => (X, Y) = (x, y);
// Read-only method
public int CalculateSquareDistance() => X * X + Y * Y;
}
class Program
{
static void Main()
{
ReadOnlyPoint point = new ReadOnlyPoint(3, 4);
int distanceSquared = point.CalculateSquareDistance();
Console.WriteLine($"Distance squared: {distanceSquared}"); // Output: Distance squared: 25
}
}
- Explanation: The
ReadOnlyPointstruct is marked asreadonly, ensuring that its fields cannot be modified once they are set. This guarantees immutability, making the struct safer and more predictable to use.
Summary
- Async Main: Allows the
Mainmethod to be asynchronous, supporting the use ofasyncandawaitfor easier handling of asynchronous tasks. - Default Literals: Simplifies the usage of the
defaultkeyword by eliminating the need to specify the type. - In Parameters: Enables passing parameters by reference without allowing the method to modify them, optimizing performance for large structs while ensuring immutability.
- Ref Structs and ReadOnly Structs: Improve safety and performance by ensuring that
ref structsare stack-allocated and cannot be used in async or heap-based contexts, and by ensuringreadonly structsare immutable.