Avoid Pattern Matching When Polymorphism Is Possible

Although the pattern matching capability is important, you should consider issues related to polymorphism prior to using pattern matching. Polymorphism supports the expansion of a behavior to other data types without requiring any modification of the implementation that defines the behavior. For example, placing a member like Name in the base class PdaItem and then working with values derived from PdaItem is preferable to using pattern matching with match expressions for each type. The former allows adding an additional type that derives from PdaItem (potentially even in a different assembly) without recompiling. In contrast, the latter requires additionally modifying the pattern matching code to address a newly introduced type. Regardless, polymorphism is not always possible, and when it isn’t, pattern matching, specifically something like property pattern matching, provides a good alternative.

One scenario where polymorphism fails is when no object hierarchy matches your goals—if you are working with classes that are part of unrelated systems, for example. Furthermore, it is assumed the code requiring polymorphism is out of your control and can’t be modified. Working with the dates in Listing 7.33 is one such example. A second scenario is when functionality you’re adding isn’t part of the core abstraction for these classes. For example, the toll paid by drivers changes for different types of vehicles traveling on a toll road, but the toll isn’t a core function of the vehicle.

AdVanced Topic
Conversion Using the as Operator

In addition to the is operator, C# has an as operator. Originally, the as operator offered an advantage over the is operator: It not only checks whether the operand is of a specific type but also attempts a conversion to the particular data type and assigns null if the source type is not inherently (within the inheritance chain) of the target type. Furthermore, it provides an advantage over casting because it won’t throw an exception. Listing 7.34 demonstrates the use of the as operator.

Listing 7.34: Data Conversion Using the as Operator
public class PdaItem
{
    protected Guid ObjectKey { get; }
    // ...
}
 
public class Contact : PdaItem
{
    // ...
    public Contact(string name) => Name = name;
 
    public static Contact Load(PdaItem pdaItem)
    {
        Contact? contact = pdaItem as Contact;
        if (contact is not null)
        {
            Console.WriteLine(
                $"ObjectKey: {contact.ObjectKey}");
            return (Contact)pdaItem;
        }
        else
        {
            throw new ArgumentException(
                $"{nameof(pdaItem)} was not of type {nameof(Contact)}");
        }
    }
    // ...
}

By using the as operator, you can avoid additional try/catch handling code if the conversion is invalid, because the as operator provides a way to attempt a cast without throwing an exception if the cast fails.

One advantage of the is operator over the as operator is that the latter cannot successfully determine the underlying type. The as operator may implicitly cast up or down an inheritance chain. Unlike the as operator, the is operator can determine the underlying type. Also, the as operator works mainly with reference types, whereas the is operator works with all types.

More important, the as operator generally requires the additional step of checking the assigned variable for null. Since the pattern matching is operator includes this conditional check automatically, it effectively eliminates the need for the as operator—assuming C# 7.0 or later is available.

{{ snackbarMessage }}
;