Introduction

Polymorphism and inheritance tend to be heavy subjects for people that start to find their way in Object Oriented Programming. There are many, many tutorials and blogposts about this subject. Most of them have one thing in common: they use “what if”, “imagine that”, or “you work for a major company with a customer database” scenarios, or they use simplified models that you will probably never use in real-life – e.g. examples with animals, cats and dogs.

In this post I am going to describe a real-life usable example on how to implement inheritance and polymorphism. When you go through this post, you will have basic understanding of the concept. And you will have some code that you can easily reuse in your own development projects.

What are we building?

We will build a simple file browser. The application mainly uses the System.IO.FileInfo and System.IO.DirectoryInfo objects. The idea is as followed:

We will show a filebrowser list on screen. This list contains direcories and files. When an item in that list is selected, information about that item is shown. The clue here is that when that item is a directory, it will show different information then when it is a file. Also, when you double click an item, the action taken will depend on the type of item: if it is a directory, that directory will open in the list. If it is a file, nothing will happen (feel free to take action there as well.)

So, in the list we have a collection of an object that can act as a file or as a directory: that’s polymorphism. We’ll get this working by first building a base class, and inherit two derived classes from that base class. Let’s get started!

1. Setup the GUI

We are going to use a standard WPF project for this. Open Visual Studio and start a new C# WPF Project. Call it “FileBrowser”. On the mainwindow, place the following:

  • A Label at the top left. Name it lblCurrentDirectory
  • A Listbox at the left, just below the label. Name it FileListBox
  • A Textbox at the right. Name it txtDetails

We will give the Lisbox a template, so we can show more than just a single value. As you can see in the XAML below, in the listbox, one label is bound to a “DisplayType” field, another one is bound to a “Name” field.

Also in the listbox, two events are added: SelectionChanged and MouseDoubleClick. When entering them, make sure the handler methods are created in the code-behind (MainWindow.xaml.cs) as well.

The XAML should look something like this:


<Window x:Class="FileBrowser.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:FileBrowser"
        mc:Ignorable="d"
        Title="MainWindow" Height="550" Width="525">
    <Grid>
        <ListBox x:Name="fileListBox" HorizontalAlignment="Left" Height="459" Margin="10,52,0,0" VerticalAlignment="Top" Width="240" BorderBrush="#FF002698" SelectionChanged="fileListBox_SelectionChanged" MouseDoubleClick="fileListBox_MouseDoubleClick">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Label Content="{Binding DisplayType}"/>
                        <Label Content=" - "/>
                        <Label Content="{Binding Name}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <TextBox x:Name="txtDetails" HorizontalAlignment="Left" Height="501" Margin="269,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="240" Background="#FFD0FFD5"/>
        <Label x:Name="lblCurrentDir" Content="" HorizontalAlignment="Left" Margin="10,11,0,0" VerticalAlignment="Top" Width="240"/>
    </Grid>
</Window>


2. Create the Base Class

The base class will be the parent of two other classes: the class that represents a file and the class that represents a directory.

In your project, add a new class named “FSBaseObject”. Code the class as in the code below:


using System;

namespace FileBrowser
{
    public abstract class FSBaseObject
    {
        public string Name { get; set; }
        public string Path { get; set; }
 
        public virtual string DisplayType { get {return "";}}

        public abstract string Details();

    }
}

As you can see, here we have the Name and DisplayType fields that we bound in the XAML. Further, the class is declared as abstract. This means that it is not possible to instantiate this class. If you want to use it, you have to use it via a derived class.

The properties Name and Path are straight forward. I have decided to make the property DisplayType a read/only virtual one, meaning that you can not set its value dynamically, and that you can override it (however, you do not have to. If you do not override it, it will retun an empty string).

The method Details() is abstract, meaning you have to override it in your derived classes.

This way, all derived classes will “share” the properties Name and Path. They can implement the property DisplayType, and they all have their “own” method Details.

3. Create the Derived Classes

We will create two derived classes: one for handling files, one for handling directories. Let’s start with the one for files. Create a new class, call it FSFileObject. Code is as below:

using System;
using System.Text;
using System.IO;

namespace FileBrowser
{
    public class FSFileObject : FSBaseObject
    {
        public FileInfo mFile { get; set; }
        public string FileSize{
            get {
                if (mFile != null)
                {
                    if (mFile.Length < 1024)
                    {
                        return string.Format("{0} B", mFile.Length);
                    } else if  (mFile.Length < 1024 * 1024)
                        {
                        return string.Format("{0:0.00} KB", (double)mFile.Length/1024);
                        }
                        else
                        {
                        return string.Format("{0:0.00} MB", (double)mFile.Length / (1024*1024));
                    }
                }
                else
                {
                    return "";
                }
            }
        }

        public override string DisplayType
        {
            get
            {
                return "File";
            }
        }

        public FSFileObject(string path)
        {
            this.Path = path;

            if (File.Exists(path))
            {
                mFile = new FileInfo(path);
                Name = mFile.Name;
            }
        }

        public override string Details()
        {
            StringBuilder sb = new StringBuilder();
            
            sb.AppendLine("Type:  file");
            sb.AppendLine("Name: " + Name);
            sb.AppendLine("Size: " + FileSize);
            
            return sb.ToString();
        }
    }
}

Line 7 shows how to declare this class as being inherited from our base class.

At line 9 we declare a property of type FileInfo. This is a System.IO object that handles file information and maipulation.

At line 10 I’ve added a property FileSize that shows the size of the file as Bytes, Kilobytes or Megabytes, depending on its size. This is just for fun.

Line 33 shows the overriden property DisplayType. This Readonly property simply returns “File”.

Line 41 show an overloaded constructor. This constructor is called when you instantiate the class with the “new” keyword, and provide a string as parameter. In code that would look like (example):


FSFileObject obj = new FSFileObject("C:\\temp\text.txt");

When instantiating the class like this, the property mFile is instantiated as well, containing information about the text.txt file.

Line 52 shows the overriden method Details. When called, this method will return some text that describes the file.

In as similar way, we’ll create a class for directories. Add a new class file to your project, and call it “FSDirectoryObject”. Code is as below:


using System;
using System.Text;
using System.IO;

namespace FileBrowser
{
    class FSDirectoryObject:FSBaseObject
    {
        public DirectoryInfo mDirectory { get; set; }

        public override string DisplayType
        {
            get
            {
                return "Directory";
            }
        }

        public FSDirectoryObject(string path)
        {
            this.Path = path;
            if (Directory.Exists(path))
            {
                mDirectory = new DirectoryInfo(path);
                Name = mDirectory.Name;
            }
        }

        public override string Details()
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("Type:  directory");
            sb.AppendLine("Name: " + Name);
            return sb.ToString();
        }
    }
}

As you can see, this class looks a lot like the FSFileObject. However, it is slighty different.

Next, we’ll actually start using these classes by adding code to MainWindow.xaml.cs.

4. MainWindow.xaml.cs

This is where it al happens. Let’s start with the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;

namespace FileBrowser
{
    /// 

    /// Interaction logic for MainWindow.xaml
    /// 
public partial class MainWindow : Window {
 private List lst; 
public MainWindow() { 
InitializeComponent(); 
lst = new List(); 
fillBox("c:\\"); 
} 
private void fillBox(string mainPath) { 
lst.Clear(); 
fileListBox.ItemsSource = null; 
lblCurrentDir.Content = mainPath;
 DirectoryInfo parentInfo = Directory.GetParent(mainPath); 
if (parentInfo != null) { 
FSDirectoryObject dirParent = new FSDirectoryObject(parentInfo.FullName); 
dirParent.Name = ".."; 
lst.Add(dirParent);
 } 
string[] dirs = Directory.GetDirectories(mainPath); 
string[] files = Directory.GetFiles(mainPath); 
foreach (string s in dirs) { 
FSDirectoryObject dir = new FSDirectoryObject(s); 
lst.Add(dir); 
} 
foreach (string s in files) {
 FSFileObject file = new FSFileObject(s); 
lst.Add(file); 
} 
fileListBox.ItemsSource = lst; 
fileListBox.SelectedIndex = 0;
 } 
private void fileListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { 
if (fileListBox.SelectedItem != null) { 
txtDetails.Text = ((FSBaseObject)fileListBox.SelectedItem).Details(); 
} 
} 
private void fileListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e) { 
if (fileListBox.SelectedItem != null) {
 var d = fileListBox.SelectedItem as FSDirectoryObject;
 if (d != null) { 
fillBox(d.Path);
 } 
}
 }
 }
 }

Let’s look what’s happening here.

At line 24 we add a List of type FSBaseObject. That is a list that can be filled with instances of our base object. Wait, that base class was declared abstract, so we can’t make any instances, right? Correct. However, this is a post about polymorphism.. we can add instances of FSFileObject and FSDirectoryObject. In fact, we could add instances of any object, as long as it inhertis from FSBaseObject. Read on.

At line 32 we call a method and pass it the path to the root directory. This method starts at line 36. First, let’s jump to line 51.

Here we use the static build-in System.IO class Directory. It gives us a quick wat to retreive all sub-directories and files in a given directory. We fill two string arrays with the path to all sub-driectories and files within the root path that we defined in line 32 (c:\). Next we iterate through these arrays. First to create an instance of FSDirectoryObject for every subdirectory we have found, next we create an instance of FSFileDirectory for every file we have found. Every instance is added to the list from line 24.

The result is a list of type FSBaseObject, that is filled with instances of FSDirectoryObject and FSFileObject.

The code at line 42 to 49 adds another FSDirectoryObject, which point to the parent directory of directory that is currently shown.

When an item in the list is selected, the SelectionChanged handler at line 72 is called. It first converts the selected list item to an instance of FSBaseObject, next it calls the Details method. And that is where the magic starts! We take a FSBaseObject, but when we call the Details method, that method is invoked at the instance of the inherited class, which can be a FSDirectoryObject or FSFileDirectoryObject.

When you doubleclick an item in the list, the MouseDoubleClick event handler is called. At line 84 this method tries to cast the selected item as a FSDirectoryObject. If the selected item is not a FSDirectoryObject, but a FSFileObect, the cast will return null. No action taken then. However, if the selected item is a FSDirectoryObject, the path of that directory is used to refill the listbox.

The result? A simple file browser that uses inheritance and polymorphism to handle both files and directories!