DEV Community

mohamed Tayel
mohamed Tayel

Posted on

c# advanced: Logical and Relational Patterns in C#

Meta Description:
Learn how to use logical and relational patterns in C# through a real-world logistics example. Discover how patterns like not, and, or, and relational comparisons simplify complex order processing, making code more readable and aligned with business requirements.

In an e-commerce logistics system, we often need to process orders with various statuses, priorities, and special handling requirements. C#'s logical and relational patterns help simplify these complex checks by allowing us to use not, and, or, and relational comparisons directly in conditions. This article will walk you through using these patterns in a logistics scenario, complete with full code examples.

Setting Up Our Classes

For this example, we’ll use an Order class with properties that represent logistics details, such as Status, IsExpress, and TotalAmount. We’ll also define two specialized subclasses—ExpressOrder for orders needing expedited processing and CancelledOrder for tracking canceled orders.

public class Order
{
    public int Status { get; set; }
    public bool IsExpress { get; set; }
    public decimal TotalAmount { get; set; }
}

public class ExpressOrder : Order
{
    public DateTime ExpeditedProcessingDate { get; set; }
    public bool RequiresSignature { get; set; }

    public void ScheduleExpeditedProcessing()
    {
        ExpeditedProcessingDate = DateTime.Now.AddHours(2); // Process within 2 hours
        Console.WriteLine($"Express order scheduled for expedited processing at {ExpeditedProcessingDate}");
    }

    public override string ToString()
    {
        return $"ExpressOrder [TotalAmount: {TotalAmount}, Status: {Status}, " +
               $"IsReady: {IsExpress}, RequiresSignature: {RequiresSignature}]";
    }
}

public class CancelledOrder : Order
{
    public DateTime CancelledDate { get; set; }
    public string CancellationReason { get; set; }

    public void RecordCancellation(string reason)
    {
        CancelledDate = DateTime.Now;
        CancellationReason = reason;
        Console.WriteLine($"Order cancelled on {CancelledDate}: {CancellationReason}");
    }

    public override string ToString()
    {
        return $"CancelledOrder [TotalAmount: {TotalAmount}, Status: {Status}, " +
               $"IsReady: {IsExpress}, CancelledDate: {CancelledDate}, Reason: {CancellationReason}]";
    }
}
Enter fullscreen mode Exit fullscreen mode

With these classes, we can now explore how to use logical and relational patterns to handle various order scenarios.

1. The not Pattern: Filtering Out Cancelled Orders

When processing active orders, it’s essential to exclude any that are cancelled. Using the not pattern, we can quickly check if an order isn’t a CancelledOrder type:

Order order = new Order { Status = 50, IsExpress = true, TotalAmount = 250.00M };

if (order is not CancelledOrder)
{
    Console.WriteLine("Order is active and can proceed.");
}
Enter fullscreen mode Exit fullscreen mode

In this example, only non-cancelled orders are processed.

2. The and Pattern: Processing Express Orders in a Specific Range

For express orders, we might prioritize orders based on TotalAmount. Let’s say orders between $100 and $500 should be expedited:

if (order is { IsExpress: true, TotalAmount: > 100M and <= 500M })
{
    Console.WriteLine("This express order qualifies for urgent processing.");
}
Enter fullscreen mode Exit fullscreen mode

With and, both conditions (express shipping and specific amount range) must be true to match, helping to prioritize orders easily.

3. The or Pattern: Flexible Handling for Special Orders

If we want to handle any ExpressOrder or orders with a TotalAmount over $1000 as priority, the or pattern provides the flexibility we need:

if (order is ExpressOrder or { TotalAmount: > 1000M })
{
    Console.WriteLine("This order is a priority (either express or high-value).");
}
Enter fullscreen mode Exit fullscreen mode

This pattern matches either condition, allowing us to identify high-priority orders efficiently.

4. Relational Patterns: Setting Specific Amount Thresholds

Relational patterns allow for comparing properties against constant values. For instance, if we consider any order over $750 to be “high-value”:

if (order.TotalAmount is > 750M)
{
    Console.WriteLine("This is a high-value order.");
}
Enter fullscreen mode Exit fullscreen mode

This pattern makes threshold-based checks clear and straightforward.

5. Combining Patterns with and for Express Orders with Medium Value

Suppose express orders between $200 and $500 qualify for fast-track handling. We can use and to set both conditions in a single check:

if (order is { IsExpress: true, TotalAmount: > 200M and <= 500M })
{
    Console.WriteLine("This express order qualifies for fast-track handling.");
}
Enter fullscreen mode Exit fullscreen mode

This approach provides precise filtering for orders based on multiple properties.

6. Excluding Specific Values with not

To exclude specific values within a range, the not pattern can help. For instance, if TotalAmount should be between $200 and $500 but not exactly $400:

if (order is { TotalAmount: > 200M and <= 500M and not 400M })
{
    Console.WriteLine("Order is eligible for processing, excluding those with a total of $400.");
}
Enter fullscreen mode Exit fullscreen mode

This approach allows for refined processing by excluding exact values.

7. Combining Type Patterns and Logical Patterns for Complex Conditions

We can use type patterns with and and or to manage express orders more effectively. Let’s say express orders above $1000 are “VIP” orders, while others are regular express orders:

if (order is ExpressOrder { TotalAmount: > 1000M })
{
    Console.WriteLine("This is a VIP express order.");
}
else if (order is ExpressOrder)
{
    Console.WriteLine("This is a standard express order.");
}
Enter fullscreen mode Exit fullscreen mode

With this pattern, you can differentiate express orders based on TotalAmount.


Full Code Example: Processing Orders with Logical and Relational Patterns

Here’s a complete code example that demonstrates handling different order types in our logistics system:

public static void ProcessOrder(Order order)
{
    switch (order)
    {
        case CancelledOrder cancelled:
            Console.WriteLine($"Order is cancelled. Reason: {cancelled.CancellationReason}");
            break;
        case ExpressOrder { TotalAmount: > 1000M }:
            Console.WriteLine("This is a VIP express order.");
            break;
        case ExpressOrder { TotalAmount: > 200M and <= 500M }:
            Console.WriteLine("Express order qualifies for fast-track handling.");
            break;
        case { TotalAmount: > 750M }:
            Console.WriteLine("High-value order for special handling.");
            break;
        case Order { IsExpress: true, TotalAmount: > 100M and <= 500M }:
            Console.WriteLine("Express order qualifies for urgent processing.");
            break;
        case Order { TotalAmount: > 200M and <= 500M and not 400M }:
            Console.WriteLine("Order is eligible for processing, excluding total of $400.");
            break;
        default:
            Console.WriteLine("Standard processing for order.");
            break;
    }
}

// Example Usage
Order regularOrder = new Order { Status = 50, IsExpress = false, TotalAmount = 300M };
ExpressOrder vipExpressOrder = new ExpressOrder { Status = 50, IsExpress = true, TotalAmount = 1200M };
CancelledOrder cancelledOrder = new CancelledOrder { Status = 0, IsExpress = false, TotalAmount = 0M, CancellationReason = "Out of stock" };

ProcessOrder(regularOrder);       // Output: Standard processing for order.
ProcessOrder(vipExpressOrder);    // Output: This is a VIP express order.
ProcessOrder(cancelledOrder);     // Output: Order is cancelled. Reason: Out of stock
Enter fullscreen mode Exit fullscreen mode

Summary

Logical and relational patterns in C# provide clear and concise ways to handle complex business logic. Here’s a recap:

  • not: Filters out specific conditions, like excluding cancelled orders.
  • and: Allows precise conditions, like checking if an order is express and within a price range.
  • or: Matches one of multiple conditions, helpful for flexible priority checks.
  • Relational Patterns: Simplifies comparisons against constant values, useful for thresholds.

These patterns help make your code more readable and maintainable while aligning with business rules. Try using these patterns to see how they can simplify your logic and improve your application!

Top comments (0)