Skip to content

Commit

Permalink
Add support for polymorphism.
Browse files Browse the repository at this point in the history
In order to support polymorphism, we have to find the runtime type and run the appropriate CloneDelegate against the source object.  I added the CloneIdDelegateCache to assist with this process.  I needed to make a couple changes to the ComplexTypeExpression factory to add polymorphism support.  In order to maintain backwards compatibility, if an initializer was supplied by the user, that should be used before the call to the new code.  I added a new test class that asserts several scenarios, including the scenario outlined in issue #7.  Also in the new unit test, I added some SpeedComparions tests  (which I commented out for integration purposes) to help give an idea of what type of performance hit this change will cause.

Let me know what you think.
  • Loading branch information
deipax committed Sep 6, 2017
1 parent 867690b commit d2035d9
Show file tree
Hide file tree
Showing 7 changed files with 662 additions and 10 deletions.
36 changes: 36 additions & 0 deletions src/CloneExtensions.UnitTests/CloneItDelegateCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using CloneExtensions.UnitTests.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;

namespace CloneExtensions.UnitTests
{
[TestClass]
public class CloneItDelegateCacheTests
{
[TestMethod]
public void CloneItDelegateCacheTests_IReadOnlyList_Int()
{
IReadOnlyList<int> source = new List<int>()
{
RandGen.GenerateInt()
};

var cloneItDelegate = CloneItDelegateCache.Get(source);

var flags = CloningFlags.Fields | CloningFlags.Properties | CloningFlags.CollectionItems;
var initializers = new Dictionary<Type, Func<object, object>>();
var clonedObjects = new Dictionary<object, object>();

var target = cloneItDelegate(source, flags, initializers, clonedObjects);

var targetAsList = target as List<int>;

Assert.IsNotNull(targetAsList);
Assert.AreNotSame(targetAsList, source);
Assert.AreEqual(targetAsList.Count, source.Count);
Assert.AreEqual(targetAsList[0], source[0]);
Assert.AreNotSame(targetAsList[0], source[0]);
}
}
}
24 changes: 21 additions & 3 deletions src/CloneExtensions.UnitTests/ComplexTypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ public void GetClone_SameTypepProperty_Cloned()

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void GetClone_AbstractClassInitializerNotSpecified_InvalidOperationExceptionThrown()
public void GetClone_ClassInitializerNotSpecified_InvalidOperationExceptionThrown()
{
var source = (AbstractClass)new DerivedClass() { AbstractProperty = 10 };
NoDefaultConstructorClass source = new NoDefaultConstructorClass(10);
var target = CloneFactory.GetClone(source);
}

Expand All @@ -57,7 +57,7 @@ public void GetCLone_AbstractClassInitializerSpecified_InstanceCloned()
[ExpectedException(typeof(InvalidOperationException))]
public void GetClone_InterfaceInitializerNotSpecified_InvalidOperationExceptionThrown()
{
IInterface source = new DerivedClass() { InterfaceProperty = 10 };
INoDefaultConstructor source = new NoDefaultConstructorClass(10);
var target = CloneFactory.GetClone(source);
}

Expand Down Expand Up @@ -133,5 +133,23 @@ class CircularReference2
{
public CircularReference1 Other { get;set; }
}

interface INoDefaultConstructor
{
}

class NoDefaultConstructorClass : INoDefaultConstructor
{
public NoDefaultConstructorClass(int propOne)
{
PropOne = PropOne;
}

private NoDefaultConstructorClass()
{
}

public int PropOne { get; set; }
}
}
}
91 changes: 91 additions & 0 deletions src/CloneExtensions.UnitTests/Helpers/RandGen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace CloneExtensions.UnitTests.Helpers
{
public static class RandGen
{
#region Field Members
private static string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
private static Random _rand = new Random(DateTime.Now.Millisecond);
#endregion

#region Public Members
public static string GenerateString(
uint length)
{
StringBuilder sb = new StringBuilder();

for (int i = 0; i < length; i++)
{
sb.Append(_chars[(int)(_rand.NextDouble() * _chars.Length)]);
}

return sb.ToString();
}

public static List<string> GenerateStringList(
uint listLength,
uint stringLength)
{
List<string> list = new List<string>();

for (int i = 0; i < listLength; i++)
{
list.Add(GenerateString(stringLength));
}

return list;
}

public static int GenerateInt(
int min = int.MinValue,
int max = int.MaxValue)
{
return _rand.Next(min, max);
}

public static List<int> GenerateIntList(
uint listLength,
int min = int.MinValue,
int max = int.MaxValue)
{
List<int> list = new List<int>();

for (int i = 0; i < listLength; i++)
{
list.Add(GenerateInt(min, max));
}

return list;
}

public static DateTime? GenerateNullableDate(
uint daysFromNow)
{
var days = _rand.Next((int)daysFromNow);

if (days % 2 == 0)
{
return DateTime.UtcNow.AddDays(days);
}

return null;
}

public static byte[] GenerateByteArray(
uint length)
{
List<byte> data = new List<byte>((int)length);

for (int x = 0; x < length; x++)
{
data.Add((byte)_rand.Next(0, 255));
}

return data.ToArray();
}
#endregion
}
}
142 changes: 142 additions & 0 deletions src/CloneExtensions.UnitTests/Helpers/TimingHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Diagnostics;
using System.Text;

namespace CloneExtensions.UnitTests.Helpers
{
public static class TimingHelper
{
public static TimingResult<T> TimeIt<T>(Func<T> func)
{
DateTime start = DateTime.Now;

T result = func();

return new TimingResult<T>()
{
Result = result,
Elapsed = DateTime.Now.Subtract(start).TotalMilliseconds
};
}

public static ComparisonResult ComparePerformance(
int iterationsFirst,
int iterationsSecond,
Action first,
Action second)
{
first();
DateTime start = DateTime.Now;
for (int i = 0; i < iterationsFirst; i++)
{
first();
}
var firstElapsed = DateTime.Now.Subtract(start).TotalMilliseconds;

second();
start = DateTime.Now;
for (int i = 0; i < iterationsSecond; i++)
{
second();
}
var secondElapsed = DateTime.Now.Subtract(start).TotalMilliseconds;

var firstOpsPerSec = ((double)iterationsFirst) / (firstElapsed / ((double)1000));
var secondOpsPerSec = ((double)iterationsSecond) / (secondElapsed / ((double)1000));

return new ComparisonResult()
{
IterationsFirst = iterationsFirst,
IterationsSecond = iterationsSecond,
FirstTotalTime = firstElapsed,
SecondTotalTime = secondElapsed,
FirstOpsPerSec = firstOpsPerSec,
SecondOpsPerSec = secondOpsPerSec,
PeformanceDiff = Math.Ceiling((((firstElapsed / secondElapsed) - 1) * 100))
};
}

public static PerformanceResult GetPerformance(
int iterations,
Action act)
{
Stopwatch sw = Stopwatch.StartNew();

for (int i = 0; i < iterations; i++)
{
act();
}

sw.Stop();
var total = sw.ElapsedMilliseconds;

var opsPerSec = ((double)iterations) / (total / ((double)1000));

return new PerformanceResult()
{
Ave = total / ((double)iterations),
Count = iterations,
Total = total,
OpsPerSec = opsPerSec
};
}
}

[DebuggerDisplay("{Elapsed} - {Result}")]
public class TimingResult<T>
{
public double Elapsed { get; set; }
public T Result { get; set; }
}

public class ComparisonResult
{
public int IterationsFirst { get; set; }
public int IterationsSecond { get; set; }
public double FirstTotalTime { get; set; }
public double SecondTotalTime { get; set; }
public double FirstOpsPerSec { get; set; }
public double SecondOpsPerSec { get; set; }
public double PeformanceDiff { get; set; }

public string GetReport()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("IterationsFirst : " + this.IterationsFirst.ToString("N0"));
sb.AppendLine("IterationsSecond : " + this.IterationsSecond.ToString("N0"));
sb.AppendLine("Total Time - First (MS): " + this.FirstTotalTime);
sb.AppendLine("Total Time - Second (MS): " + this.SecondTotalTime);
sb.AppendLine("Ops per Sec - First: " + this.FirstOpsPerSec.ToString("N3"));
sb.AppendLine("Ops per Sec - Second: " + this.SecondOpsPerSec.ToString("N3"));

if (this.PeformanceDiff > 0)
{
sb.AppendLine("Performance Increase: " + this.PeformanceDiff + "%");
}
else
{
sb.AppendLine("Performance Decrease: " + Math.Abs(this.PeformanceDiff) + "%");
}

return sb.ToString();
}
}

public class PerformanceResult
{
public int Count { get; set; }
public double Ave { get; set; }
public double Total { get; set; }
public double OpsPerSec { get; set; }

public string GetReport()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Count : " + this.Count.ToString("N0"));
sb.AppendLine("Ave : " + this.Ave.ToString("N10"));
sb.AppendLine("Total : " + this.Total);
sb.AppendLine("Ops per Sec: " + this.OpsPerSec.ToString("N3"));
return sb.ToString().Trim();
}
}
}
Loading

0 comments on commit d2035d9

Please sign in to comment.