94. partial methods
Partial methods allow a method to be declared but not necessarily implemented in a partial class or struct. If an implementation is provided, it gets compiled; if not, the method is completely removed at compile time. This provides a mechanism for extensibility without incurring runtime overhead.
Example: Declaring a Partial Method
File 1: Employee1.cs
public partial class Employee
{
partial void LogAction(string action); // Declaration
}
File 2: Employee2.cs
public partial class Employee
{
partial void LogAction(string action) // Implementation (optional)
{
Console.WriteLine($"Action: {action}");
}
}
✔ Key Features:
- If no implementation is provided, the method disappears from the compiled code.
- Always private, meaning they cannot be public, protected, or internal.
- Cannot return a value (void only).
✔ Use Cases:
- Extensibility points in code generation (e.g., allowing customization without modifying generated code).
- Providing optional behaviors without unnecessary overhead.
⚠ Limitations:
- Must be used inside partial types.
- Cannot have out parameters.
- Limited use outside of auto-generated code scenarios.
95. pattern matching
Pattern matching allows checking values against patterns while deconstructing them into meaningful components. Introduced in C# 7+, it enhances switch statements and if conditions, making type and property checks more expressive and concise.
Example: Type Pattern Matching
object obj = "Hello, World!";
if (obj is string s)
{
Console.WriteLine(s.Length); // Output: 13
}
Here, is checks if obj is a string and assigns it to s.
Example: Property Pattern Matching
class Person { public string Name { get; set; } public int Age { get; set; } }
Person p = new Person { Name = "John", Age = 30 };
if (p is { Age: >= 18 })
{
Console.WriteLine($"{p.Name} is an adult.");
}
✔ Use Cases:
- Simplifying switch statements with pattern-based conditions.
- Writing safer, more readable type checks.
- Improving null-checking and type casting.
96. is keyword
The is keyword in C# checks whether an object is of a specific type and can also perform pattern matching. It simplifies type checking and conversions while improving readability.
Basic is Example:
object value = 42;
if (value is int)
{
Console.WriteLine("Value is an integer.");
}
Type Checking + Declaration (C# 7.0+)
object obj = "Hello";
if (obj is string text)
{
Console.WriteLine(text.ToUpper()); // Output: HELLO
}
Here, is checks and casts obj in one step, eliminating the need for manual casting.
✔ Use Cases:
- Safe type checking without as or explicit casts.
- Pattern matching for cleaner, more expressive code.
- Ensuring type safety in APIs where multiple object types are used.
97. switch expression
The switch expression, introduced in C# 8.0, offers a more concise and expressive alternative to the traditional switch statement. It allows for pattern matching, inline evaluation, and a simplified syntax that enhances code readability and maintainability. Unlike the switch statement, which requires multiple case blocks and explicit break statements, the switch expression is an expression, meaning it directly returns a value and can be used within assignments or method calls.
Example:
var result = dayOfWeek switch
{
DayOfWeek.Monday => "Start of the work week",
DayOfWeek.Friday => "Almost the weekend!",
DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend!",
_ => "Just another day"
};
The switch expression supports pattern matching, including property patterns, tuple patterns, and positional patterns, enabling powerful and concise conditional logic. It also allows for more compact code with fewer boilerplate constructs, improving both performance and readability in many scenarios. Additionally, switch expressions can be used in scenarios requiring a return value directly within lambda expressions, enhancing the functional programming aspects of C#.
98. switch statement
The switch statement in C# is a control flow structure that facilitates multi-branch decision-making. It evaluates a single expression and executes a corresponding case block based on a match. Starting with C# 7.0, the switch statement also supports pattern matching, allowing for more complex and expressive conditions beyond simple constant comparisons.
Example:
switch (shape)
{
case Circle c when c.Radius > 10:
Console.WriteLine("Large circle");
break;
case Rectangle r:
Console.WriteLine($"Rectangle with area {r.Width * r.Height}");
break;
default:
Console.WriteLine("Unknown shape");
break;
}
Pattern matching within switch statements improves flexibility by allowing conditions based on type and property evaluations. It also offers additional safety through exhaustiveness checks in some scenarios, reducing the risk of unhandled cases in decision-making structures. With the introduction of pattern matching, switch statements became a powerful tool not only for primitive type comparisons but also for complex object validation.
99. property pattern
A property pattern allows pattern matching against the properties of an object in C#. It is particularly useful when you need to match objects with specific property values or nested properties. Property patterns enhance the expressiveness of the switch statement or expression and reduce the need for manual property checks.
Example:
if (person is { Age: >= 18, Name: "John" })
{
Console.WriteLine("John is an adult.");
}
Property patterns can be combined with other patterns such as positional and recursive patterns, offering powerful matching capabilities for complex data structures. They provide a declarative approach to data validation and extraction, contributing to more maintainable and testable code. Property patterns are particularly effective when working with complex JSON or XML data models, as they allow for more intuitive mapping and validation of data structures.
100. tuple pattern
The tuple pattern in C# enables pattern matching against multiple values grouped as a tuple. This is useful when evaluating combined conditions or comparing multiple variables simultaneously. Tuple patterns can be employed in switch expressions or in is expressions.
Example:
(string fruit, string color) = ("Apple", "Red");
var description = (fruit, color) switch
{
("Apple", "Red") => "Red Apple",
("Banana", "Yellow") => "Yellow Banana",
_ => "Unknown Fruit"
};
Tuple patterns allow for concise and readable code when dealing with multiple correlated values. They are particularly effective in scenarios involving complex decision-making based on combinations of input values, leading to clearer and more maintainable branching logic. Tuple patterns also enable quick prototyping and testing of multi-variable conditions, particularly in algorithms and data processing workflows.
101. deconstruction
Deconstruction in C# is a feature that allows you to break down complex objects or data structures into individual variables. This is especially useful with tuples and types that implement the Deconstruct method. Deconstruction provides a cleaner and more readable syntax for extracting values.
Example:
var (x, y) = (10, 20);
Console.WriteLine($"X: {x}, Y: {y}");
Deconstruction also works with custom types by defining a Deconstruct method, enabling seamless unpacking of data within methods or assignments. It simplifies the manipulation of data within complex structures and enhances code maintainability by reducing manual extraction code. Additionally, deconstruction can improve performance by reducing the need for intermediate variables and simplifying data access patterns, particularly in LINQ queries and asynchronous methods.
102. discard (_)
The discard symbol (_) in C# is a placeholder that explicitly ignores a value or variable. It is often used in deconstruction, pattern matching, and method calls where a return value is not needed. The discard improves code clarity by indicating that certain values are intentionally unused.
Example:
(int _, int y) = (10, 20);
Console.WriteLine($"Y: {y}");
Discards are particularly helpful when only part of a returned data set is of interest, promoting cleaner and more focused code. They also contribute to efficient memory usage by avoiding unnecessary variable allocations, particularly in large or complex method calls. Discards enhance scenarios such as event handling, asynchronous programming, and interactions with APIs where only specific outcomes or values are relevant.