Closures and full copy of an object

Today, I faced the challenge of making a complete copy of the object, that is, DeepClone. Consider some code and I will show what problems may arise and how to solve them.

Source class:
class ClassForClone
{
	//here are value type fields
	public readonly A a;
	public readonly Lazy lazy;
	protected void Func1()
	{
		//to to something;
	}
	public ClassForClone(A a)
	{
		this.a = a;
		lazy = new Lazy(() =>
		{
			// some calculations 
			Func1();
			return a.SomeText;
		});
	}
}

We will use the function of bitwise copying the fields of the Object.MemberwiseClone () object . It saves us from the monotonous work of copying fields, but all the fields with reference types will have to be initialized ourselves.

At this point, I see at least two type problems:
  1. The referenced fields a and lazy are declared readonly . Therefore, we can assign them values ​​only in the constructor or directly when declaring a field.
  2. The constructor declares a parameter with the same name as the class field, which introduces some confusion both in the constructor code itself and in the lambda expression code.

The first problem can be solved by replacing the read-only fields with similar properties of the object.
public A a { get; private set; }
public Lazy lazy { get; private set; }

Now a and lazy can be changed not only inside the constructor and at the time of declaration, but also generally within any function of our class.

We will consider the second problem in more detail. Let's go back to the constructor. If the line this.a = a; understandable at first glance, then with a lambda expression, everything is not immediately obvious.

Func1 is called in the context of the current instance of the class. But how to interpret the return line a.SomeText? Most likely, the author intended to use the value of the field, not the parameter, which is actually but without the keyword this. And, most interestingly, there was no error in the source code, because the field a was declared read-only and cannot be changed outside the scope of the constructor. As soon as the field is no longer read-only, the lambda expression will return the value of the SomeText field / property of the constructor parameter! And when it comes to performing the expression's ljabda, the field a and the parameter a may already not be equal to each other.
Since we replaced read-only fields with similar properties, we need to change the lambda expression:
public ClassForClone(A a)
{
	this.a = a;
	lazy = new Lazy(() =>
	{
		// some calculations 
		Func1();
		return this.a.SomeText;
	});
}

But the situation is much simpler if the parameter names of the functions did not match the names of the fields / properties. For example, like this:
public ClassForClone(A aParam)
{
	a = aParam;
	lazy = new Lazy(() =>
	{
		// some calculations 
		Func1();
		return a.SomeText;
	});
}


Now let's get to the function of cloning. Just want to write something like this:
public object DeepClone()
{
	var clone = (ClassForClone) MemberwiseClone();
	clone.a = new A();
	clone.lazy = new Lazy(() =>
	{
		Func1();
		return a.SomeText;
	});
	return clone;
}

Again, we must not forget which object will be enclosed in the closure. With this approach, Func1 and a.SomeText of the original object are called in the clone. Therefore, the correct version is this:
public object DeepClone()
{
	var clone = (ClassForClone) MemberwiseClone();
	clone.a = new A();
	clone.lazy = new Lazy(() =>
	{
		clone.Func1();
		return clone.a.SomeText;
	});
	return clone;
}


From this we can draw the following conclusions:
  1. Try not to use the same parameter names for functions and fields / properties of classes, or accept an agreement in which access to internal fields occurs only through this.
  2. Be careful using closures. Pay close attention to what references or values ​​of variables are remembered in a temporary object.
  3. Closures should not use variable loop values. But this is a completely different story .

Also popular now: