Abstract: This is a continuation of the article on Immutable Object Pattern. We discuss some issues related to the creation of "defense copy".
Prerequisite
This article is a continuation of another piece of mine:
Immutable object and Defensive copies
When passing a struct object to a method with an "in" parameter modifier, some optimizations are possible if the struct is marked with "readonly". Because if a mutation is possibly going to happen, the compiler will create the "defense copy" of the struct to prevent possible mutation of the parameter marked with the "in" modifier.
2.1 Example
Let us look at the following example.
We will create the following structs for our example:
- CarStruct - Mutable struct
- CarStructI1 - Partly Mutable/Immutable struct that has a hidden mutator method
- CarStructI3 - Immutable struct marked "readonly"
We are going to monitor the addresses of structs passed to another service method in 4 different cases:
- Case 1: Mutable struct passed by ref ("ref" modifier)
- Case 2: Mutable struct passed by value
- Case 3: Immutable struct passed with "in" modifier, applying hidden mutator on it
- Case 4: Immutable struct passed with "in" modifier, applying getter method
By monitoring object addresses, outside and inside service method ("TestDefenseCopy"), we will see if and when "defense-copy" has been created.
//=============================================
public struct CarStruct {
public CarStruct(Char ? brand, Char ? model, int ? year) {
Brand = brand;
Model = model;
Year = year;
}
public Char ? Brand {
get;
set;
}
public Char ? Model {
get;
set;
}
public int ? Year {
get;
set;
}
public override string ToString() {
return $ "Brand:{Brand}, Model:{Model}, Year:{Year}";
}
public readonly unsafe string ? GetAddress() {
string ? result = null;
fixed(void * pointer1 = ( & this)) {
result = $ "0x{(long)pointer1:X}";
}
return result;
}
}
//=============================================
public struct CarStructI1 {
public CarStructI1(Char ? brand, Char ? model, int ? year) {
Brand = brand;
Model = model;
Year = year;
}
public Char ? Brand {
get;
private set;
}
public Char ? Model {
get;
}
public int ? Year {
get;
}
public readonly override string ToString() {
return $ "Brand:{Brand}, Model:{Model}, Year:{Year}";
}
public(string ? , string ? ) HiddenMutatorMethod() {
Brand = 'Z';
return (this.GetAddress(), this.ToString());
}
public readonly unsafe string ? GetAddress() {
string ? result = null;
fixed(void * pointer1 = ( & this)) {
result = $ "0x{(long)pointer1:X}";
}
return result;
}
}
//=============================================
public readonly struct CarStructI3 {
public CarStructI3(Char brand, Char model, int year) {
this.Brand = brand;
this.Model = model;
this.Year = year;
}
public Char Brand {
get;
}
public Char Model {
get;
}
public int Year {
get;
}
public override string ToString() {
return $ "Brand:{Brand}, Model:{Model}, Year:{Year}";
}
public unsafe string ? GetAddress() {
string ? result = null;
fixed(void * pointer1 = ( & this)) {
result = $ "0x{(long)pointer1:X}";
}
return result;
}
public(string ? , string ? ) GetterMethod() {
return (this.GetAddress(), this.ToString());
}
}
//=============================================
//===Sample code===============================
internal class Program {
private static void TestDefenseCopy(ref CarStruct car1, CarStruct car2, in CarStructI1 car3, in CarStructI3 car4, out string ? address1, out string ? address2, out string ? address3, out string ? address4, out string ? address3d, out string ? state3d, out string ? address4d, out string ? state4d) {
car1.Brand = 's';
(address3d, state3d) = car3.HiddenMutatorMethod(); //(*1)
(address4d, state4d) = car4.GetterMethod(); //(*2)
address1 = car1.GetAddress();
address2 = car2.GetAddress();
address3 = car3.GetAddress();
address4 = car4.GetAddress();
}
static void Main(string[] args) {
CarStruct car1 = new CarStruct('T', 'C', 2022);
CarStruct car2 = new CarStruct('T', 'C', 2022);
CarStructI1 car3 = new CarStructI1('T', 'C', 2022);
CarStructI3 car4 = new CarStructI3('T', 'C', 2022);
string ? address_in_main_1 = car1.GetAddress();
string ? address_in_main_2 = car2.GetAddress();
string ? address_in_main_3 = car3.GetAddress();
string ? address_in_main_4 = car4.GetAddress();
Console.WriteLine($ "State of structs before method call:");
Console.WriteLine($ "car1 : before ={car1}");
Console.WriteLine($ "car2 : before ={car2}");
Console.WriteLine($ "car3 : before ={car3}");
Console.WriteLine($ "car4 : before ={car4}");
Console.WriteLine();
TestDefenseCopy(ref car1, car2, in car3, in car4, out string ? address_in_method_1, out string ? address_in_method_2, out string ? address_in_method_3, out string ? address_in_method_4, out string ? address_in_method_3d, out string ? state3d, out string ? address_in_method_4d, out string ? state4d);
Console.WriteLine($ "State of struct - defense copy:");
Console.WriteLine($ "car3d: d-copy ={state3d}");
Console.WriteLine();
Console.WriteLine($ "State of structs after method call:");
Console.WriteLine($ "car1 : after ={car1}");
Console.WriteLine($ "car2 : after ={car2}");
Console.WriteLine($ "car3 : after ={car3}");
Console.WriteLine($ "car4 : after ={car4}");
Console.WriteLine();
Console.WriteLine($ "Case 1 : Mutable struct passed by ref:");
Console.WriteLine($ "car1 : address_in_main_1 ={address_in_main_1}, address_in_method_1 ={address_in_method_1}");
Console.WriteLine($ "Case 2 :Mutable struct passed by value:");
Console.WriteLine($ "car2 : address_in_main_2 ={address_in_main_2}, address_in_method_2 ={address_in_method_2}");
Console.WriteLine($ "Case 3 :Immutable struct passed with in modifier:");
Console.WriteLine($ "car3 : address_in_main_3 ={address_in_main_3}, address_in_method_3 ={address_in_method_3}");
Console.WriteLine($ "Case 3d:Immutable struct passed with in modifier, applying hidden mutator");
Console.WriteLine($ "car3d: address_in_main_3 ={address_in_main_3}, address_in_method_3d={address_in_method_3d}");
Console.WriteLine($ "Case 4 :Immutable struct passed with in modifier:");
Console.WriteLine($ "car4 : address_in_main_4 ={address_in_main_4}, address_in_method_4 ={address_in_method_4}");
Console.WriteLine($ "Case 4d:Immutable struct passed with in modifier, , applying getter method");
Console.WriteLine($ "car4 : address_in_main_4 ={address_in_main_4}, address_in_method_4d={address_in_method_4d}");
Console.WriteLine();
Console.ReadLine();
}
}
//=============================================
//===Result of execution=======================
/*
State of structs before method call:
car1 : before =Brand:T, Model:C, Year:2022
car2 : before =Brand:T, Model:C, Year:2022
car3 : before =Brand:T, Model:C, Year:2022
car4 : before =Brand:T, Model:C, Year:2022
State of struct - defense copy:
car3d: d-copy =Brand:Z, Model:C, Year:2022
State of structs after method call:
car1 : after =Brand:s, Model:C, Year:2022
car2 : after =Brand:T, Model:C, Year:2022
car3 : after =Brand:T, Model:C, Year:2022
car4 : after =Brand:T, Model:C, Year:2022
Case 1 : Mutable struct passed by ref:
car1 : address_in_main_1 =0x44C0D7E7D0, address_in_method_1 =0x44C0D7E7D0
Case 2 :Mutable struct passed by value:
car2 : address_in_main_2 =0x44C0D7E7C0, address_in_method_2 =0x44C0D7E698
Case 3 :Immutable struct passed with in modifier:
car3 : address_in_main_3 =0x44C0D7E7B0, address_in_method_3 =0x44C0D7E7B0
Case 3d:Immutable struct passed with in modifier, applying hidden mutator
car3d: address_in_main_3 =0x44C0D7E7B0, address_in_method_3d=0x44C0D7E5D0
Case 4 :Immutable struct passed with in modifier:
car4 : address_in_main_4 =0x44C0D7E7A8, address_in_method_4 =0x44C0D7E7A8
Case 4d:Immutable struct passed with in modifier, , applying getter method
car4 : address_in_main_4 =0x44C0D7E7A8, address_in_method_4d=0x44C0D7E7A8
*/
Join the conversation! Your thoughts help the community grow.