DEV Community

mohamed Tayel
mohamed Tayel

Posted on • Updated on

c# advance:Exploring Type Patterns in C#

Meta Description:Explore the power of type patterns in C# with this in-depth guide. Learn how to use Type, Declaration, and Var patterns for flexible type-checking and efficient code. Includes full examples, detailed explanations, and assignments at beginner, intermediate, and advanced levels to boost your coding skills.

In C#, patterns offer a flexible and powerful way to check and handle different types at runtime. Whether working with various subclasses, types, or complex objects, patterns allow for clean, type-safe code. In this article, we’ll explore three essential type patterns in C#: the Type Pattern, Declaration Pattern, and Var Pattern. Each section includes examples, detailed explanations, and assignments for practice at beginner, intermediate, and advanced levels.


1. Type Pattern

The Type Pattern allows us to perform a runtime type check on an object. This can be very useful when you need to identify a specific type or subclass and perform actions based on that type.

Example: Checking Object Types with the Type Pattern

Let’s consider a scenario where we have an object type that could hold different values, such as int, string, or decimal. The type pattern helps us check the actual type and act accordingly.

using System;

public class TypePatternExample
{
    public static void Main()
    {
        object value = 42; // This could be any type, like int, string, or decimal.

        switch (value)
        {
            case int i:
                Console.WriteLine($"It's an integer: {i}");
                break;
            case string s:
                Console.WriteLine($"It's a string: {s}");
                break;
            case decimal d:
                Console.WriteLine($"It's a decimal: {d}");
                break;
            default:
                Console.WriteLine("It's another type.");
                break;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Here, the switch statement uses the type pattern to check the actual type of value.
  • If value is of type int, string, or decimal, the respective case is executed, displaying the type and value.
  • If value doesn’t match any specific type, the default case executes, handling all other types.

2. Declaration Pattern

The Declaration Pattern allows you to check an object’s type and simultaneously create a local variable of that type. This is particularly helpful when you want to work with properties or methods specific to that type within a case block.

Example: Calculating Shipping Cost Based on Type

In this example, we’re calculating the shipping cost based on a specific type of ShippingProvider. The pattern checks if the provider is of a specific type and applies different costs based on properties unique to each provider.

using System;

public abstract class ShippingProvider
{
    public abstract decimal FreightCost { get; }
}

public class SwedishPostalServiceShippingProvider : ShippingProvider
{
    public override decimal FreightCost => 50m;
    public bool NextDayDelivery { get; set; }
}

public class FedexShippingProvider : ShippingProvider
{
    public override decimal FreightCost => 30m;
    public bool Priority { get; set; }
}

public class DeclarationPatternExample
{
    public static decimal CalculateShippingCost(ShippingProvider provider) =>
        provider switch
        {
            SwedishPostalServiceShippingProvider s when s.NextDayDelivery => 
                s.FreightCost + 10m, // Extra cost for next-day delivery
            SwedishPostalServiceShippingProvider s =>
                s.FreightCost - 5m, // Discount for standard delivery
            _ => 15m // Default cost for other providers
        };

    public static void Main()
    {
        var swedishProvider = new SwedishPostalServiceShippingProvider { NextDayDelivery = true };
        var cost = CalculateShippingCost(swedishProvider);
        Console.WriteLine($"Shipping cost: {cost}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • CalculateShippingCost uses the declaration pattern to match against specific types and capture them as local variables.
  • When the provider is SwedishPostalServiceShippingProvider and NextDayDelivery is true, an additional charge is applied.
  • If NextDayDelivery is false, a discount is applied to the cost.
  • The default case handles all other providers by setting a standard cost of 15.

3. Var Pattern

The Var Pattern matches any type, similar to a default case. However, it also allows you to capture the matched variable for use, making it handy for handling unmatched cases without discarding the variable.

Example: Using the Var Pattern

The following example demonstrates using the var pattern as a catch-all that still allows us to interact with unmatched types by capturing them in a variable.

using System;

public class VarPatternExample
{
    public static string IdentifyShippingProvider(ShippingProvider provider) =>
        provider switch
        {
            SwedishPostalServiceShippingProvider s when s.NextDayDelivery => 
                "Next-day delivery with Swedish Postal Service",
            FedexShippingProvider f when f.Priority => 
                "Priority shipping with FedEx",
            var other => 
                $"Default shipping provider: {other.GetType().Name}"
        };

    public static void Main()
    {
        var fedexProvider = new FedexShippingProvider { Priority = true };
        var result = IdentifyShippingProvider(fedexProvider);
        Console.WriteLine(result);

        var defaultProvider = new ShippingProvider();
        result = IdentifyShippingProvider(defaultProvider);
        Console.WriteLine(result);
    }
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • This example uses the var pattern to capture unmatched cases in a variable.
  • The first case handles SwedishPostalServiceShippingProvider with NextDayDelivery.
  • The second case matches FedexShippingProvider with Priority.
  • The var other pattern captures any other ShippingProvider type, allowing us to print the provider’s type name as the default response.

Assignments

Beginner Level

  1. Type Pattern: Modify the TypePatternExample to include a case for bool. Add logic to print "It's a boolean" if the value is a boolean type.

  2. Declaration Pattern: Update the CalculateShippingCost function to add a discount if the provider is FedexShippingProvider and the Priority property is false.

  3. Var Pattern: Modify the IdentifyShippingProvider method to add a check for an unsupported provider, returning “Unsupported provider type” for any unmatched cases.


Intermediate Level

  1. Type Pattern: Extend the TypePatternExample to handle a custom Product class with properties like Name and Price. In the case Product p, print out the product’s name and price.

  2. Declaration Pattern: Create a CalculateTax method that uses the declaration pattern to calculate tax based on the specific type of TaxableItem (e.g., FoodItem, ElectronicsItem), with different tax rates for each type.

  3. Var Pattern: Use the var pattern to capture any unhandled cases in the IdentifyShippingProvider example. Print the provider type name in uppercase in the default case.


Advanced Level

  1. Type Pattern: Modify the TypePatternExample to handle collections. If the value is a List<int>, calculate and print the sum of all integers. If it’s a List<string>, print all strings concatenated together.

  2. Declaration Pattern: Update CalculateShippingCost to add a new provider, DHLShippingProvider, that provides free shipping if the NextDayDelivery property is true but applies a standard cost otherwise. Write additional logic for calculating the final cost.

  3. Var Pattern: In IdentifyShippingProvider, modify the var case to check if the captured provider has a method GetShippingRate. If it does, invoke that method and return the rate; otherwise, return "Provider rate not available."

Top comments (0)