Skip to content

Latest commit

 

History

History
253 lines (223 loc) · 9.76 KB

EXPRESSION_TREES.md

File metadata and controls

253 lines (223 loc) · 9.76 KB

Expression Tree generation

All generated expression trees follows following delegate signature:

Func<T, CloningFlags, IDictionary<Type, Func<object, object>>, T>

where

  • T is the type of cloned object
  • CloningFlags represents custom flags passed by developer to impact the cloning process
  • IDictionary<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.

Primitive types, know immutable types and delegates

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;

Arrays

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;

Nullable

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;

Other classes and structs

null check

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;

Initialization

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();

Fields

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);
}

Properties

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);
}

Collections

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));
    }
}

Return statement

At the end return statement is generated to satisfy delegate signature:

return target;

Expression Tree debug listings

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.

Primitive type: GetClone<int>

.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
}

Nullable: GetClone<int?>

.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:
    }
}

Array: GetClone<int[]>

.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:
    }
}

List: GetClone<List<int>>

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:
    }
}