📜 ⬆️ ⬇️

Closures and full object copying

Today I faced the task 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.

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

Let's use the function of the bit- wise copying of the fields of the Object.MemberwiseClone () object. It saves us from the monotonous work of copying fields, but all fields with reference types will have to be initialized by ourselves.

At this stage, I see at least two problems with the type:
  1. Reference fields a and lazy are readonly only. Therefore, we can assign values ​​to them only in the constructor or directly during the declaration of the 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 read-only fields with similar properties of the object.
 public A a { get; private set; } public Lazy<string> lazy { get; private set; } 

Now a and lazy can be changed not only inside the constructor and at the time of the declaration, but also generally inside any function of our class.
')
Consider the second problem in more detail. Let's return to the designer. If the line is this.a = a; it is clear at first glance, it is not immediately obvious with lambda expression.

Func1 will be called in the context of the current class instance. But how to interpret the line return a .SomeText? Most likely, the author implied the use of the value of the field, and not the parameter, which is actually a without the keyword this. And, most interestingly, there was no error in the source code, because the a field was declared read-only and cannot be changed outside 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 the fulfillment of the expression, the field a and the parameter a may no longer 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<string>(() => { // some calculations Func1(); return this.a.SomeText; }); } 

But the situation is much simpler if the function parameter names did not coincide with the names of the fields / properties. For example:
 public ClassForClone(A aParam) { a = aParam; lazy = new Lazy<string>(() => { // some calculations Func1(); return a.SomeText; }); } 


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

Again, we must not forget which object will be enclosed in a closure. With this approach, the clone will call Func1 and a.SomeText of the original object. Therefore, the correct version is:
 public object DeepClone() { var clone = (ClassForClone) MemberwiseClone(); clone.a = new A(); clone.lazy = new Lazy<string>(() => { 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 the agreement whereby you can only access internal fields using this.
  2. Be careful about using closures. Pay close attention to what references or variable values ​​will be remembered in the temporary object.
  3. Closures should not use variable loop values. But this is a completely different story .

Source: https://habr.com/ru/post/138594/


All Articles