C# Corner

Create Object Clones Through the Prototype Pattern in .NET

The Prototype Pattern involves cloning objects. Learn how to create both deep and shallow clones for instances in your program where creating a new object won't work.

The Prototype Pattern is a common creational design pattern whereby an object is cloned to create new objects. The pattern is traditionally used where creating a new instance of an object would be prohibitive. The pattern is also great when you want to make copies of an object with slight variations and don't want to be concerned with how the object has been created.

When you implement the Prototype Pattern, the clone may either be a shallow copy or a deep copy. A shallow copy clone contains only the top-level objects, and any lower level objects are references to the original object. A deep copy fully copies the entire object graph of the cloned object. Today I'll cover how to implement both deep and shallow copy clone strategies in a sample application.

To get started, create a new C# Windows Store blank application (note that all code in the sample can be used in either a WPF or ASP.NET Web App). Next, add a new directory named Models to the project.

Next, I define the IDeepCloneable<T> and IShallowCloenable<T> interfaces that define a contract for cloning an object using a deep or shallow copy, respectively. First, let's implement the IDeepCloneable<T> interface that has one method, named DeepClone, that returns an object instance of type T:

namespace VSMPrototypePattern.Models
{
    public interface IDeepCloneable<out T>
    {
        T DeepClone();
    }
}

Next I add the IShallowCloneable<T> interface that contains one method named ShallowClone, that returns a shallow copy of type T:

namespace VSMPrototypePattern.Models
{
    public interface IShallowCloneable<out T>
    {
        T ShallowClone();
    }
}

Then I add the generic BaseModel class that provides common shared functionality to the Person and Address model classes. Add a new class named BaseModel to the Models directory. Next, add using statements for the System.ComponentModel and System.Runtime.CompilerServices namespaces:

using System.ComponentModel;
using System.Runtime.CompilerServices;

The BaseModel<T> implements the INotifyPropertyChanged and IShallowCloneable<T> interfaces:

public abstract class BaseModel<T> : INotifyPropertyChanged, IShallowCloneable<T>
    where T: class

Next I implement the INotifyPropertChanged event as recommended by MSDN:

public event PropertyChangedEventHandler PropertyChanged;

protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

By making use of the CallerMemberName parameter attribute, the NotifyPropertyChanged method will insert the name of the property into the propertyName argument by default. Next I Implement the IShallowCloneable<T> interface by making use of the MemberwiseClone method:

public virtual T ShallowClone()
{
    return (T) MemberwiseClone();
}

See Listing 1 for the full BaseModel class implementation.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace VSMPrototypePattern.Models
{
    public abstract class BaseModel<T> : INotifyPropertyChanged, IShallowCloneable<T>
        where T: class
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public virtual T ShallowClone()
        {
            return (T) MemberwiseClone();
        }
    }
}

Listing 1: BaseModel Class Implementation

Now that the BaseModel class is complete, it's time to add the Address class that will be used by the Person class. The Address class inherits from the BaseModel class, using itself as the generic type Address. The Address class contains string typed properties named Street, StateOProvince, Country, and PostalCode. Each property setter calls the NotifyPropertyChanged method without any arguments, as seen in Listing 2:

namespace VSMPrototypePattern.Models
{
    public class Address : BaseModel<Address>
    {
        private string _street;
        private string _stateOrProvince;
        private string _country;
        private string _postalCode;

        public string Street
        {
            get { return _street; }
            set { _street = value; NotifyPropertyChanged(); }
        }

        public string StateOrProvince
        {
            get { return _stateOrProvince; }
            set { _stateOrProvince = value; NotifyPropertyChanged(); }
        }

        public string Country
        {
            get { return _country; }
            set { _country = value; NotifyPropertyChanged(); }
        }

        public string PostalCode
        {
            get { return _postalCode; }
            set { _postalCode = value; NotifyPropertyChanged(); }
        }
    }
}

Listing 2: Address Class Implementation

[Click on image for larger view.] Figure 1. The initial clone of Person with address.

Now it's time to implement the Person class that implements the IDeepCloneable interface and inherits from BaseModel. Add the Person class under the Models directory; start by adding string typed properties named FirstName and LastName to the class:

private string _firstName;
private string _lastName;
private Address _address;

public string FirstName
{
    get { return _firstName; }
    set { _firstName = value; NotifyPropertyChanged(); }
}

public string LastName
{
    get { return _lastName; }
    set { _lastName = value; NotifyPropertyChanged(); }
}

Next, add an Address type property named Address:

public Address Address
{
    get { return _address; }
    set { _address = value; NotifyPropertyChanged(); }
}

Finally, I implement the DeepClone method that creates a deep copy of the Person class by performing a shallow copy of the Person object and then creating a shallow copy of the nested Address class:

public Person DeepClone()
{
    Person deepClone = (Person) MemberwiseClone();
    deepClone.Address = Address.ShallowClone();
    return deepClone;
}

See Listing 3 for the full Person class implementation.

namespace VSMPrototypePattern.Models
{
    public class Person : BaseModel<Person>, IDeepCloneable<Person>
    {
        private string _firstName;
        private string _lastName;
        private Address _address;

        public string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; NotifyPropertyChanged(); }
        }

        public string LastName
        {
            get { return _lastName; }
            set { _lastName = value; NotifyPropertyChanged(); }
        }

        public Address Address
        {
            get { return _address; }
            set { _address = value; NotifyPropertyChanged(); }
        }

        public Person DeepClone()
        {
            Person deepClone = (Person) MemberwiseClone();
            deepClone.Address = Address.ShallowClone();
            return deepClone;
        }
    }
}

Listing 3: Person Class Implementation

The next step is to implement the UI for the application. Open up the MainPage.xaml file and copy the XAML from the root Grid element in Listing 4.

<Page
    x:Class="VSMPrototypePattern.MainPage"
    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"
    mc:Ignorable="d">

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="20">
            <StackPanel Name="SourcePerson">
                <TextBlock FontSize="20">Source</TextBlock>
                <TextBlock>First Name</TextBlock>
                <TextBox Text="{Binding FirstName, Mode=TwoWay}"></TextBox>
                <TextBlock>Last Name</TextBlock>
                <TextBox Text="{Binding LastName, Mode=TwoWay}"></TextBox>
                <StackPanel Name="SourcePersonAddress">
                    <TextBlock FontSize="16">Address</TextBlock>
                    <TextBlock>Street</TextBlock>
                    <TextBox Text="{Binding Street, Mode=TwoWay}"></TextBox>
                    <TextBlock>State/Province</TextBlock>
                    <TextBox Text="{Binding StateOrProvince, Mode=TwoWay}"></TextBox>
                    <TextBlock>Country</TextBlock>
                    <TextBox Text="{Binding Country, Mode=TwoWay}"></TextBox>
                    <TextBlock>Postal Code</TextBlock>
                    <TextBox Text="{Binding PostalCode, Mode=TwoWay}"></TextBox>
                </StackPanel>
                <Button Name="ClonePerson" Click="ClonePerson_Click">Clone</Button>
            </StackPanel>
            <StackPanel Name="ShallowClone">
                <TextBlock FontSize="20">Shallow Clone</TextBlock>
                <TextBlock>First Name</TextBlock>
                <TextBlock Text="{Binding FirstName, Mode=TwoWay}"></TextBlock>
                <TextBlock>Last Name</TextBlock>
                <TextBlock Text="{Binding LastName, Mode=TwoWay}"></TextBlock>
                <StackPanel Name="ShallowPersonAddress">
                    <TextBlock FontSize="16">Address</TextBlock>
                    <TextBlock>Street</TextBlock>
                    <TextBlock Text="{Binding Street, Mode=TwoWay}"></TextBlock>
                    <TextBlock>State/Province</TextBlock>
                    <TextBlock Text="{Binding StateOrProvince, Mode=TwoWay}"></TextBlock>
                    <TextBlock>Country</TextBlock>
                    <TextBlock Text="{Binding Country, Mode=TwoWay}"></TextBlock>
                    <TextBlock>Postal Code</TextBlock>
                    <TextBlock Text="{Binding PostalCode, Mode=TwoWay}"></TextBlock>
                </StackPanel>
            </StackPanel>
            <StackPanel Name="DeepClone">
                <TextBlock FontSize="20">Deep Clone</TextBlock>
                <TextBlock>Shallow Clone</TextBlock>
                <TextBlock>First Name</TextBlock>
                <TextBlock Text="{Binding FirstName, Mode=TwoWay}"></TextBlock>
                <TextBlock>Last Name</TextBlock>
                <TextBlock Text="{Binding LastName, Mode=TwoWay}"></TextBlock>
                <StackPanel Name="DeepPersonAddress">
                    <TextBlock FontSize="16">Address</TextBlock>
                    <TextBlock>Street</TextBlock>
                    <TextBlock Text="{Binding Street, Mode=TwoWay}"></TextBlock>
                    <TextBlock>State/Province</TextBlock>
                    <TextBlock Text="{Binding StateOrProvince, Mode=TwoWay}"></TextBlock>
                    <TextBlock>Country</TextBlock>
                    <TextBlock Text="{Binding Country, Mode=TwoWay}"></TextBlock>
                    <TextBlock>Postal Code</TextBlock>
                    <TextBlock Text="{Binding PostalCode, Mode=TwoWay}"></TextBlock>
                </StackPanel>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

Listing 4: MainPage.xaml

The final step is to implement the code-behind for the MainPage class. Open up the MainPage.xaml.cs file and add a using statement for your Models namespace:

using VSMPrototypePattern.Models;

Next, add a private readonly Person class variable named _sourcePerson to the MainPage class:

private readonly Person _sourcePerson;

In the MainPage constructor, I initialize the _sourcePerson class and bind it to the SourcePerson StackPanel control. I also bind the _sourcePerson.Address property to the SourcePersonAddress StackPanel:

public MainPage()
{
    this.InitializeComponent();
    _sourcePerson = new Person();
    _sourcePerson.Address = new Address();
    SourcePerson.DataContext = _sourcePerson;
    SourcePersonAddress.DataContext = _sourcePerson.Address;
}

Next I implement the ClonePerson_Click method, which handles the Click event on the ClonePerson Button element. First I create a shallow clone of the _sourcePerson and bind it and its address to the page:

var shallowClone = _sourcePerson.ShallowClone();
ShallowClone.DataContext = shallowClone;
ShallowPersonAddress.DataContext = shallowClone.Address;

Last, I create a deep clone copy of the _sourcePerson object and bind it to the form:

var deepClone = _sourcePerson.DeepClone();
 DeepClone.DataContext = deepClone;
 DeepPersonAddress.DataContext = deepClone.Address;

Here's the completed ClonePerson_Click method implementation:

private void ClonePerson_Click(object sender, RoutedEventArgs e)
{
    var shallowClone = _sourcePerson.ShallowClone();
    ShallowClone.DataContext = shallowClone;
    ShallowPersonAddress.DataContext = shallowClone.Address;

    var deepClone = _sourcePerson.DeepClone();
    DeepClone.DataContext = deepClone;
    DeepPersonAddress.DataContext = deepClone.Address;
}

See Listing 5 for the completed MainPage class implementation.

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using VSMPrototypePattern.Models;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace VSMPrototypePattern
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private readonly Person _sourcePerson;

        public MainPage()
        {
            this.InitializeComponent();
            _sourcePerson = new Person();
            _sourcePerson.Address = new Address();
            SourcePerson.DataContext = _sourcePerson;
            SourcePersonAddress.DataContext = _sourcePerson.Address;
        }

        private void ClonePerson_Click(object sender, RoutedEventArgs e)
        {
            var shallowClone = _sourcePerson.ShallowClone();
            ShallowClone.DataContext = shallowClone;
            ShallowPersonAddress.DataContext = shallowClone.Address;

            var deepClone = _sourcePerson.DeepClone();
            DeepClone.DataContext = deepClone;
            DeepPersonAddress.DataContext = deepClone.Address;
        }
    }
}

Listing 5: The complete MainPage class implementation.

The application is now finished, and you can now perform both a deep and shallow copy of an entered person and address record, as seen in Figure 1.

[Click on image for larger view.] Figure 2. An address change on Person.

If you update the person's address, only the shallow copy is immediately updated because it contains a reference copy of the address, instead of a value copy like the deep copy shown in Figure 2.

Although the Prototype Pattern may not be as prevalent in practice as the Factory and Builder patterns, it's still good to have in your repertoire when you need to create variations of an object at runtime without being concerned with how to create the object.

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