Understanding latest C# Features with code example - Part 2 - Pattern matching

One of the exciting new features of C# is pattern matching. This feature will play a key role in future project implementations. Here I've implemented an example to depict how to use and understand the advantage of this new feature. 

Imagine a common scenario when you have to implement separate logic based on object type. So, here is one base class implementing an interface and multiple objects derived from such a base class and each might have its own logic:



Let's imagine three types of customers:

1. Membership Customer: Highest Discount offered to members

2. Regular Customer: They are repeat customers but don't have membership. They get a lesser percent of discount

3. First-time Customer: They don't get any discount since its their first visit! How nice I'm at running a business!

So, we have an ICustomer interface and a Customer base class implementing that interface + a set of child classes for the Customer base class.

With that overview, let us build the classes, properties and methods involved:

My Interface looks like this:


Note that it is incorrect for the ClassifyCustomer logic to sit in the Customer base class since there is a high probability for changes. So, it is better to move it to the client side or its own layer. As this example is to depict the importance of pattern matching, let's get ahead with what is on hand!

With that background, here is some code:

Customer Interface: ICustomer.cs

namespace OfferLogic
{
    public interface ICustomer
    {
        double BillTotal { get; set; }

        double CalculateDiscount(int percent);
        double ClassifyCustomer(ICustomer customer);
    }
}

Customer Base Class: Customer.cs

namespace OfferLogic
{
    public class Customer : ICustomer
    {
        public double BillTotal { get; set; }

        public double CalculateDiscount(int percent)
        {
            return BillTotal * percent / 100;
        }

        public double ClassifyCustomer(ICustomer customer) => customer switch
        {
            Regular => customer.CalculateDiscount(1),
            Membership => customer.CalculateDiscount(3),
            _ => customer.CalculateDiscount(0)
        };
    }
}


The most important logic, the one that depicts the beauty of type or pattern matching is within the ClassifyCustomer method. To me, this looks like a hybrid between a switch case and lambda expression but then the best part is you can directly use the class to compare the object passed to the method and then the end-result is channel to whatever logic you want to!

In the above case, the _ within the switch is called a discard pattern. It is like a default within a switch case, if the object does not match any of the types/patterns above in the list within the switch, it falls here. So, its called as discard pattern!


Child Class 1 - FirstTime.cs

namespace OfferLogic
{
    public class FirstTime : Customer { };
}

Child Class 2 - Membership.cs

namespace OfferLogic
{
    public class Membership : Customer { };
}

Child Class 3 - Regular.cs

namespace OfferLogic
{
    public class Regular : Customer { };
}

Here is my console client logic:

Program.cs

using OfferLogic;
namespace MyOfferClient
{
    class Program
    {
        static void Main(string[] args)
        {
            ICustomer regular = new Regular
            {
                BillTotal = 700
            };
            var discountAmount=regular.ClassifyCustomer(regular);
            Console.WriteLine($"Regular Customer Discount for Bill Amount {regular.BillTotal} = ${discountAmount}");

            ICustomer firstTime = new FirstTime
            {
                BillTotal = 1000
            };
            discountAmount = regular.ClassifyCustomer(firstTime);
            Console.WriteLine($"First-time Customer Discount for Bill Amount {firstTime.BillTotal} = ${discountAmount}");

            ICustomer memberShipCustomer = new Membership
            {
                BillTotal = 400
            };
            discountAmount = regular.ClassifyCustomer(memberShipCustomer);
            Console.WriteLine($"Membership Customer Discount for Bill Amount {memberShipCustomer.BillTotal} = ${discountAmount}");

            Console.ReadLine();

        }
    }
}



Then, this is what is in _GlobalUsings.cs under the client project:

global using System;

This is my client project file with langversion pointing to 10.0:


My solution looks like this with separation of concerns in place:


Now, when I execute my console application, I can see different logic picked up as desired:



Comments

Popular Posts