1. Tuples and Deconstruction
Tuples allow you to return multiple values from a method without needing to define a class or structure. You can use deconstruction to unpack tuple values into individual variables.
Example: Tuples and Deconstruction
class Program
{
// Method returning a tuple with multiple values
static (int sum, int product) Calculate(int a, int b)
{
return (a + b, a * b);
}
static void Main()
{
// Using tuple
var result = Calculate(3, 4);
Console.WriteLine($"Sum: {result.sum}, Product: {result.product}"); // Output: Sum: 7, Product: 12
// Deconstruction to separate variables
(int sum, int product) = Calculate(5, 6);
Console.WriteLine($"Sum: {sum}, Product: {product}"); // Output: Sum: 11, Product: 30
}
}
- Explanation: The
Calculate
method returns a tuple with two values: the sum and product. You can access these values using the tuple’s named fields, or you can deconstruct the tuple directly into separate variables.
2. Pattern Matching
Pattern matching enhances switch
statements and expressions by allowing you to match based on types and conditions, not just values. This makes the code more expressive and flexible.
Example: Pattern Matching with switch
class Program
{
static string CheckType(object obj)
{
switch (obj)
{
case int i:
return $"Integer: {i}";
case string s when s.Length > 5:
return $"Long string: {s}";
case null:
return "Null object";
default:
return "Unknown type";
}
}
static void Main()
{
Console.WriteLine(CheckType(42)); // Output: Integer: 42
Console.WriteLine(CheckType("HelloWorld")); // Output: Long string: HelloWorld
Console.WriteLine(CheckType(null)); // Output: Null object
Console.WriteLine(CheckType(3.14)); // Output: Unknown type
}
}
- Explanation: In this example, pattern matching is used in the
switch
statement to match objects based on their types (int
,string
,null
) and conditions (e.g., string length). This improves readability compared to traditional type checking and casting.
3. Local Functions
Local functions allow you to define functions inside methods. This helps in organizing code better, especially when the function is only relevant within a particular method.
Example: Local Functions
class Program
{
static void Main()
{
int AddNumbers(int x, int y)
{
return x + y;
}
int result = AddNumbers(10, 20);
Console.WriteLine(result); // Output: 30
}
}
- Explanation: The
AddNumbers
function is defined inside theMain
method as a local function. It is only accessible withinMain
, making the code more modular and self-contained.
4. Out Variables
C# now allows you to declare out variables inline, directly in the method call. This makes code more concise and reduces the need for separate variable declarations.
Example: Out Variables
class Program
{
static void ParseInteger(string input)
{
if (int.TryParse(input, out int result))
{
Console.WriteLine($"Parsed successfully: {result}");
}
else
{
Console.WriteLine("Failed to parse.");
}
}
static void Main()
{
ParseInteger("123"); // Output: Parsed successfully: 123
ParseInteger("abc"); // Output: Failed to parse.
}
}
- Explanation: The
int.TryParse
method uses an out variable (result
), declared inline in theif
statement. This avoids the need to declareresult
separately, simplifying the code.
5. Ref Locals and Returns
Ref locals and ref returns allow you to return and work with references to variables, instead of their values. This is useful for performance-sensitive scenarios where you need to modify the original data directly.
Example: Ref Locals and Returns
class Program
{
static ref int FindNumber(int[] numbers, int target)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == target)
{
return ref numbers[i]; // Return reference to the array element
}
}
throw new Exception("Number not found");
}
static void Main()
{
int[] numbers = { 1, 2, 3, 4, 5 };
ref int numberRef = ref FindNumber(numbers, 3);
numberRef = 10; // Modify the original array element through the reference
Console.WriteLine(string.Join(", ", numbers)); // Output: 1, 2, 10, 4, 5
}
}
- Explanation: The
FindNumber
method returns a reference to an element in thenumbers
array. By usingref
returns and locals, we can modify the original array element through the reference, not just a copy of the value.
6. Expression-Bodied Constructors and Destructors
The expression-bodied syntax, previously limited to methods and properties, can also be used for constructors, destructors, and finalizers.
Example: Expression-Bodied Constructors and Destructors
class Person
{
public string Name { get; }
// Expression-bodied constructor
public Person(string name) => Name = name;
// Destructor (Finalizer) using expression-bodied syntax
~Person() => Console.WriteLine($"{Name} is being finalized.");
}
class Program
{
static void Main()
{
var person = new Person("Alice");
Console.WriteLine(person.Name); // Output: Alice
// Destructor will be called when the object is collected by the garbage collector
}
}
- Explanation: The constructor is written as a single line using the expression-bodied syntax. The destructor (finalizer) also uses the
=>
syntax to simplify its implementation. This is useful for cases where the logic is simple and fits neatly into a single line.
Summary
- Tuples and Deconstruction: Allow methods to return multiple values using tuples, which can be deconstructed into individual variables.
- Pattern Matching: Provides more powerful control over switch statements and expressions by matching types and conditions.
- Local Functions: Allow functions to be defined within other methods, keeping the code modular and self-contained.
- Out Variables: Enable out parameters to be declared inline within method calls, making code more concise.
- Ref Locals and Returns: Allow methods to return and work with references, enabling direct modification of data rather than working with copies.
- Expression-Bodied Constructors and Destructors: Simplify constructors, destructors, and finalizers using concise expression-bodied syntax.