C#: A practical example of polymorphism for everyday projects


Every programming textbook and wiki is going to have a definition for polymorphism, so you don’t need me to explain the concept. In my mind, polymorphism is the OOP technique of defining one interface that allows different entities to do the work. The classic example is a printer, you hit a button to print a document. You don’t need to know nor do you care about the inner workings of the printer. All your program is concerned with is sending the document to a printer and the printer responding that it is printed.

In this post I’m going to provide a practical example of subtype polymorphism or subtyping. We will be using a base class (actually an abstract class) from which daughter classes (or subtypes) will inherit from.

Let’s give this context.

I have a program that takes a file and depending on the file extension will perform an action in different ways. I’ve called this action “DoSomething” but you can think of it as anything from “open in viewer” to “print”. All we’re concerned with is that the DoSomething returns a boolean.

Before we continue, yes, you can write a select-case or nested-if statement to handle this if your file types are limited. Let’s assume that the supported file types are likely not to be limited.

 

Let’s get started

 

1. Abstract Base Class

All of the supported file types classes inherit from this class. I typically don’t care if this is abstract but that is considered textbook.


using System.Collections.Generic;
using System.IO;

public abstract class SupportedFileBase
{
  public string Filename { get; set; }

  protected List<string> supportedExtensions = new List<string>();

  public SupportedFileBase(string filename)
  {
    this.Filename = filename;
  }

  public string FileExtension => Path.GetExtension(this.Filename).Trim('.').ToLower();

  public bool IsSupported => supportedExtensions.Contains(this.FileExtension);

  public virtual bool DoSomething() => false;

}

 

2. Daughter Classes

We’re supporting: “JPG”, “ZIP” and “PDF” in this example. The DoSomething is really not interesting. I’m primarily justifying my use of a list for supported extensions here.


using System.Collections.Generic;
using System.Windows.Forms;

public class JPGImageSupportedFile : SupportedFileBase
{
  public JPGImageSupportedFile(string filename) : base(filename)
  {
    supportedExtensions = new List<string>() { "jpg", "jpeg" };
  }

  public override bool DoSomething()
  {
    MessageBox.Show("JPG Done Something");

    return true;
  }

}

public class PDFDocumentSupportedFile : SupportedFileBase
{
  public PDFDocumentSupportedFile(string filename) : base(filename)
  {
    supportedExtensions = new List<string>() { "pdf" };
  }

  public override bool DoSomething()
  {
    MessageBox.Show("PDF Done Something");

    return true;
  }

}

public class ZIPArchiveSupportedFile : SupportedFileBase
{
  public ZIPArchiveSupportedFile (string filename) : base(filename)
  {
    supportedExtensions = new List<string>() { "zip" };
  }

  public override bool DoSomething()
  {
    MessageBox.Show("ZIP Done Something");

    return true;
  }

}

 

3. Reflection

You might have noticed that each supported-file class has code to check if it supports a file extension.

1. We’re going to use reflection to find all classes that inherit from the base class
2. Then we check each object to see if it supports the file extension
3. The first object to support the file extension is going to have it’s DoSomething run

I’m reading the filename from a textbox1 and running the code in a button.


private void button1_Click(object sender, EventArgs e)
{
  SupportedFileBase fileHandler = null;

  List<Type> typeList = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(SupportedFileBase))).ToList();
  foreach (Type t in typeList)
  {
    fileHandler = (SupportedFileBase)Activator.CreateInstance(t, textBox1.Text);
    if (fileHandler == null)
    {
      continue;
    }

    if (fileHandler.IsSupported)
    {
      break;
    }

    fileHandler = null;
  }

  if (fileHandler != null)
  {
    fileHandler.DoSomething();
  }

}

 

4. Why?

For a limited known list of extensions you can store them in a list or an enumeration with a select-case or nested-if. In my mind, with the subtype-polym, you’re not managing a potentially unwieldy list. All the new code you’re writing is towards pure functionality. If you add a class to support a new type you don’t have to find the supported-extensions list or enum’. Just think of the time you won’t spend on resolving merge conflicts!

Another point is that you don’t have to be the one writing the classes. The code I’ve shown finds classes in the current assembly but it could just as easily read from a library (*.dll) in a “plugins” folder, no rebuild required.

 

I hope someone finds this useful.

Social Media

 Share Tweet

Categories

Programming

Tags

.NET C#

Post Information

Posted on Sat 25th May 2019

Modified on Sun 13th Mar 2022