-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
7 changed files
with
662 additions
and
10 deletions.
There are no files selected for viewing
36 changes: 36 additions & 0 deletions
36
src/CloneExtensions.UnitTests/CloneItDelegateCacheTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} |
Oops, something went wrong.