All generated expression trees follows following delegate signature:
Func<T, CloningFlags, IDictionary<Type, Func<object, object>>, T>
where
T
is the type of cloned objectCloningFlags
represents custom flags passed by developer to impact the cloning processIDictionary<Type, Func<object, object>>
is a dictionary of initialization functions
Generated expression tree differs depending on type T and does not necessary use all these parameters.
For all primitive types (Boolean
, Byte
, SByte
, Int16
, UInt16
, Int32
, UInt32
, Int64
, UInt64
, IntPtr
, UIntPtr
, Char
, Double
, and Single
), known immutable types (DateTime
, TimeSpan
, String
) and Nullable<T>
and delegates (including Action<T>
, Func<T1, TResult>
, etc) simple return statement Expression is generated by compiler from following Func delegate:
CloneExpression = (s, fl, kt) => s;
For arrays new array initialization and a loop with GetClone method call for each item is generated. C# code describing the logic would look as follows:
var target = new T[source.Length];
for(int i = 0; i < source.Length; i++)
{
target[i] = source[i].GetClone(flags, initializers);
}
return target;
To return proper clone of Nullable<T>
instance HasValue
property value is checked and depending on the value null
or new Nullable<T>(source.Value.GetClone(flags, initializers))
is returned:
return source.HasValue ? new Nullable<T>(source.Value.GetClone(flags, initializers)) : null;
For reference types null
check expression is generated at the very beginning, to make sure no NullReferenceException
is thrown when cloning source is null
.
if(source == null)
return null;
Initiating expression differs depending on the presence of cloned type parameterless constructor. Additional check for custom initialization method provided by initializers dictionary is generated to make cloning of types which do not have parameterless constructor possible.
When parameterless constructor exists, generated Expression logic follows the one from this c# code:
if(initializers.ContainsKey(typeof(T))
target = (T)initializers[typeof(T)].Invoke((object)source);
else
target = new T();
otherwise code looks like that:
if(initializers.ContainsKey(typeof(T))
target = (T)initializers[typeof(T)].Invoke((object)source);
else
throw new InvalidOperationException();
If at least one public field without readonly modifier following code is generated:
if((flags & CloningFlags.Fields) == CloningFlags.Fields)
{
target.field1 = target.field1.GetClone(flags, initializers);
// (...)
target.fieldN = target.fieldX.GetClone(flags, initializers);
}
Code generated for properties looks very similar to the one generated for fields. It's generated if there is at least one public property with both get
and set
accessors.
if((flags & CloningFlags.Properties) == CloningFlags.Properties)
{
target.Property1 = target.Property1.GetClone(flags, initializers);
// (...)
target.PropertyN = target.PropertyN.GetClone(flags, initializers);
}
When T
implements ICollection
foreach
loop with ((ICollection<TItem>)target).Add(item.GetClone(flags, initializers))
method call for every item in source collection is generated.
if((flags & CloningFlags.CollectionItems) == CloningFlags.CollectionItems)
{
var targetCollection = (ICollection<TItem>)target;
foreach(var item in (ICollection<TItem>)source)
{
targetCollection.Add(item.Clone(flags, initializers));
}
}
At the end return statement is generated to satisfy delegate signature:
return target;
To point what really happens under the hoods when Expression Tree is created instead of simple c# code it's important to look at Expression Tree debug listing. Find couple listings for one type for each case described above.
.Lambda #Lambda1<System.Func`4[System.Int32,CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Int32]>(
System.Int32 $s,
CloneExtensions.CloningFlags $fl,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $kt) {
$s
}
.Lambda #Lambda1<System.Func`4[System.Nullable`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Nullable`1[System.Int32]]>(
System.Nullable`1[System.Int32] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Nullable`1[System.Int32] $target) {
.If ($source.HasValue == False) {
$target = null
} .Else {
$target = .New System.Nullable`1[System.Int32](.Call CloneExtensions.CloneFactory.GetClone(
$source.Value,
$flags,
$initializers))
};
.Label
$target
.LabelTarget #Label1:
}
}
.Lambda #Lambda1<System.Func`4[System.Int32[],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Int32[]]>(
System.Int32[] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Int32[] $target) {
$target = .NewArray System.Int32[$source.Length];
.Block(System.Int32 $var1) {
$var1 = 0;
.Loop {
.If ($var1 < $source.Length) {
.Block() {
$target[$var1] = .Call CloneExtensions.CloneFactory.GetClone(
$source[$var1],
$flags,
$initializers);
$var1 += 1
}
} .Else {
.Break #Label1 { }
}
}
.LabelTarget #Label1:
};
.Label
$target
.LabelTarget #Label2:
}
}
You can see almost all sections described above, except fields values cloning. That's because there are no public fields exposed by List<T>
.
.Lambda #Lambda1<System.Func`4[System.Collections.Generic.List`1[System.Int32],CloneExtensions.CloningFlags,System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]],System.Collections.Generic.List`1[System.Int32]]>(
System.Collections.Generic.List`1[System.Int32] $source,
CloneExtensions.CloningFlags $flags,
System.Collections.Generic.IDictionary`2[System.Type,System.Func`2[System.Object,System.Object]] $initializers) {
.Block(System.Collections.Generic.List`1[System.Int32] $target) {
.If ($source == null) {
.Return #Label1 { null }
} .Else {
.Default(System.Void)
};
.If (
.Call $initializers.ContainsKey(.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32]))
) {
$target = (System.Collections.Generic.List`1[System.Int32]).Call ($initializers.Item[.Constant<System.Type>(System.Collections.Generic.List`1[System.Int32])]
).Invoke((System.Object)$source)
} .Else {
$target = .New System.Collections.Generic.List`1[System.Int32]()
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Fields)
) {
.Default(System.Void)
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(Properties)
) {
.Block() {
$target.Capacity = .Call CloneExtensions.CloneFactory.GetClone(
$source.Capacity,
$flags,
$initializers)
}
} .Else {
.Default(System.Void)
};
.If (
((System.Byte)$flags & (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)) == (System.Byte).Constant<CloneExtensions.CloningFlags>(CollectionItems)
) {
.Block(
System.Collections.Generic.IEnumerator`1[System.Int32] $var1,
System.Collections.Generic.ICollection`1[System.Int32] $var2) {
$var1 = (System.Collections.Generic.IEnumerator`1[System.Int32]).Call $source.GetEnumerator();
$var2 = (System.Collections.Generic.ICollection`1[System.Int32])$target;
.Loop {
.If (.Call $var1.MoveNext() != False) {
.Call $var2.Add(.Call CloneExtensions.CloneFactory.GetClone(
$var1.Current,
$flags,
$initializers))
} .Else {
.Break #Label2 { }
}
}
.LabelTarget #Label2:
}
} .Else {
.Default(System.Void)
};
.Label
$target
.LabelTarget #Label1:
}
}