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

  • Uno Platform Wants Microsoft to Improve .NET WebAssembly in Two Ways

    Uno Platform, a third-party dev tooling specialist that caters to .NET developers, published a report on the state of WebAssembly, addressing some shortcomings in the .NET implementation it would like to see Microsoft address.

  • Random Neighborhoods Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the random neighborhoods regression technique, where the goal is to predict a single numeric value. Compared to other ML regression techniques, advantages are that it can handle both large and small datasets, and the results are highly interpretable.

  • As Some Orgs Restrict DeepSeek AI Usage, Microsoft Offers Models and Dev Guidance

    While some organizations are restricting employee usage of the new open source DeepSeek AI from a Chinese company due to data collection concerns, Microsoft has taken a different approach.

  • Useful New-ish Features in .NET/C#

    We often hear about the big new features in .NET or C#, but what about all of those lesser known, but useful new features? How exactly do you use constructs like collection indices and ranges, date features, and pattern matching?

  • TypeScript 5.8 Beta Speeds Program Loads, Updates

    "TypeScript 5.8 introduces a number of optimizations that can both improve the time to build up a program, and also to update a program based on a file change in either --watch mode or editor scenarios."

Subscribe on YouTube