Tip: Using XamlWriter and XamlReader to clone an object

There are multiple ways to clone objects, and multiple definitions of what “clone” should even mean.

 

The main issue is usually about cloning “deep” vs. “shallow”. For example, if you have a Customer object that points to an Address object, and you clone the Customer object, does the Address object get cloned too (this would be a deep clone)? Or does the new Customer reference the original Address object (a shallow clone)?

 

Another issue is whether you clone just the properties of the object, or the fields instead. And do you clone event listeners?

 

Yet another issue is that you might clone a property value on the object that is supposed to be unique, such as the Name property on WPF elements.

 

Anyway, here’s a trick to clone an object by writing it out to Xaml then reading it back in. As such, the cloning rules it follows matches the Xaml serialization rules (see here for some more info). That primarily means that it only clones public properties – no fields or events – and it clones deep.

 

So here’s what the code might look like:

 

private Object CloneUsingXaml(Object o)

{

    string xaml = XamlWriter.Save(o);

    return XamlReader.Load(new XmlTextReader(new StringReader(xaml)));

}

 

(XamlReader.Load doesn’t have an overload that takes a string, so you need to convert the string into an XmlReader.)

 

Here’s an example. Take this app:

 

<Window x:Class="WPFApplication1.Window1"

   xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

   Title="Window1" Height="300" Width="300" Loaded='OnLoaded'>

   

    <Grid Name='Grid1'>

            <Grid.ColumnDefinitions>

                  <ColumnDefinition Width="*"/>

                  <ColumnDefinition Width="*"/>

            </Grid.ColumnDefinitions>

   

            <Rectangle Name='_rectangle1' Fill='Red' Width='20' Height='20' />

       

    </Grid>

</Window>

 

… and clone the Rectangle during load with:

 

private void OnLoaded(object sender, RoutedEventArgs args)

{

    Rectangle rectangle2 = CloneUsingXaml(_rectangle1) as Rectangle;

    rectangle2.Name = null;

    Grid.SetColumn(rectangle2, 1);

    Grid1.Children.Add(rectangle2);

}

 

… and you get a Window with two rectangles.

 

Here’s what the intermediate Xaml looks like:

 

<Rectangle

  Fill="#FFFF0000"

  Name="_rectangle1"

  Width="20" Height="20"

  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" />

 

Note here that the Name property got cloned, which isn’t what we want, so I removed the Name after running the clone.