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.