Sunday, March 22, 2009

Copy Constructor

It's strange, but I did not have to deal with copy constructors in C# until today. Here is the problem: in the application I'm working on, a user can retrieve customer's details from the database, change some values on several separate tabs, and then choose to save or discard changes. Now, discarding did not work properly till today. The problem is that the old values were not saved anywhere, so no way to access them was implemented.

Two approaches came to my mind: First, to reload data from the database, and second, to keep a 'backup' copy of the customer. Since an extra trip to the database takes several seconds, the 'backup' approach was chosen.

Now, when the customer details are retrieved from the database, they are immediately copied into the 'backup' customer object. Obviously, if you try to create a copy like this

Customer cust = SelectCustomerFromDB(id);
Customer cust2 = cust;

You will end up with two references to the same object. Now, if you change anything in 'cust' object, your 'cust2' is useless, cause it references the same object and will reflect the changes. This MSDN page gives a brief idea of the proper way to create a copy of the object

How to: Write a Copy Constructor (C# Programming Guide)

The above sample would now look more like this

class Customer
{
private string name;

// Copy constructor.
public Customer(Customer previousCustomer)
{
name = previousCustomer.name;
}

...
}

...

Customer cust = SelectCustomerFromDB(id);
Customer cust2 = new Customer(cust);

What if your customer has a list of names though?

class Customer
{
private List names;

// Copy constructor.
public Customer(Customer previousCustomer)
{
names = previousCustomer.names;
}

...
}

...

Customer cust = SelectCustomerFromDB(id);
Customer cust2 = new Customer(cust);

Now try changing something in cust.names. Ooops, the cust2.names reflected the changes. Unfortunately, the article linked above only shows the approach to copy the object with members of value types. Now, if you have members of reference types in your class, you would have to copy each one properly.

For the List type, for example, the following approach would work:

class Customer
{
private List names;

// Copy constructor.
public Customer(Customer previousCustomer)
{
names = new List(previousCustomer.names.ToArray());
}

...
}

I came across a few smart ways to implement the copy constructor so that you would not have to specify each class field separately, like in this exapmle

An Intelligent Copy Constructor In C# Using Reflection

public MyClass( MyClass rhs )
{
// get all the fields in the class
FieldInfo[] fields_of_class = this.GetType().GetFields(
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance );

// copy each value over to 'this'
foreach( FieldInfo fi in fields_of_class )
{
fi.SetValue( this, fi.GetValue( rhs ) );
}
}

This is great, because if you add a new field to the class and forget to add it to a copy constructor, your code will compile without warnings but you will get troubles sooner or later.

Unfortunately, this approach also works for value type fields only so I could not benefit from it. I could not quickly find a way to use this approach to reference type class members so I'll leave it for myself as a TODO task.

by . Also posted on my website

No comments: