Code Focused

OOP Code Focus: Clone a Derived Instance from a Base Object

OOP is a special kind of animal, says Tim Patrick, who shows how to put one of its core principles, encapsulation, to work while coding constructors for base and derived classes.

One of the successes of object-oriented development is the base-derived relationship. Have a derived class called Dog? When needed, you can have it act as its base Animal version, complete with self-selecting virtual methods and overrides. But the reverse interaction isn't as convenient, because an Animal instance isn't necessarily a Dog instance.

For you cat lovers, let's move the conversation to a business situation. I have a class that contains some basic informational properties:

public class InfoItem
{
  public long ItemID { get; set; }
  public string ItemName { get; set; }
}

So simple and elegant, and a bunch of these instances bundled together make a great selection list for the user of my app. But as the class exists now, there's no way to indicate which items are selected and which aren't. I could add a flag to the class. But because the purpose of the class is only to track the raw information, and not its status (and because I'm too lazy to deal with interfaces), I decided to create a derived class:

public class TrackedItem : InfoItem
{
  // ----- Derived member joins the base members.
  public bool IsSelected { get; set; }
}

Now I have selectable items. But if I already have a bunch of InfoItem instances, how do I treat them as TrackedItem instances? I can't simply cast the objects, because an InfoItem isn't necessarily a TrackedItem. My first solution was, of course, to hand-shuttle data from the base instances to new derived instances:

trackedInstance = new TrackedItem()
{
  ItemID = baseInstance.ItemID,
  ItemName = baseInstance.ItemName,
  IsSelected = false
};

That's good code for a class as simple and as elegant as InfoItem, but it has several deficiencies. First, it leaves this block of property-copying code at the mercy of the original class designer, because any changes in property definitions must be replicated in the transfer code. Also, some base members might not be accessible due to their protection levels. If a base field is configured for "private set," for example, you can't assign its derived doppelganger from outside the body of the base or derived class.

That last issue was a showstopper for the first coding attempt. Because of the member access issue, any success will need to reside inside the base class definition. I tried playing around with the MemberwiseClone method that appears in every class, but it turned out to be no more useful than working with the original base instance.

The solution comes through custom constructors, specifically the ability of a derived class's constructor to leverage its initialization off of a related constructor in the base class. In essence, when creating a derived instance (in this case, an instance of TrackedItem), the constructor code can ask the base class to do its own initialization, and it's perfectly valid to pass that base constructor some data to use for its initialization.

First, add a default constructor to each class so that ordinary instances can be created apart from this special cloning situation:

// ----- Default constructor for base class.
public InfoItem()
{
}

// ----- Default constructor for derived class.
public TrackedItem()
{
}

Then, augment the base class with a new custom constructor that accepts an instance of itself:

// ----- Custom constructor for InfoItem.
public InfoItem(InfoItem source)
{
  // ----- Clones a source instance.
  this.ItemID = source.ItemID;
  this.ItemName = source.ItemName;
}

Finally, add a similar custom constructor to the derived class. Instead of accepting one of its own as an argument, it will accept a base class instance:

public TrackedItem(InfoItem source, bool selectionMode = false) : base(source)
{
  // ----- Custom constructor for derived class. Logic here
  //       executes after custom base constructor.
  this.IsSelected = selectionMode;
}

To take advantage of this solution, create a derived instance, passing it an existing base instance, plus any supporting arguments:

derivedInstance = new TrackedItem(baseInstance, false);

This statement builds a new derived instance via its custom constructor. Before the code in the body of that constructor executes, the base(source) declaration on the constructor triggers a bout of initialization through the base class's own custom constructor. By the time the assignment to TrackedItem's IsSelected property takes place, assignments to the other two properties have already occurred. And all without the external code knowing anything about the internals of either the base or derived classes.

What I originally thought was a deficiency in object-oriented declaration -- the difficulty of converting a base instance to a derived variant -- had its solution in encapsulation, one of the core principles of object-oriented programming (OOP). As you can tell, OOP is a special kind of animal.

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube