Skip to content

Commit

Permalink
Merge pull request #818 from zvirja/lookup-target-node-in-adapted-col…
Browse files Browse the repository at this point in the history
…lections-lazily

Improve fixture customization performance by adding laziness to adapted collection
  • Loading branch information
zvirja committed Sep 18, 2017
2 parents 3807550 + 3d707aa commit 06ca10b
Showing 1 changed file with 55 additions and 44 deletions.
99 changes: 55 additions & 44 deletions Src/AutoFixture/SpecimenBuilderNodeAdapterCollection.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ploeh.AutoFixture.Kernel;

namespace Ploeh.AutoFixture
Expand All @@ -22,7 +21,33 @@ namespace Ploeh.AutoFixture
public class SpecimenBuilderNodeAdapterCollection : IList<ISpecimenBuilder>
{
private readonly Func<ISpecimenBuilderNode, bool> isAdaptedBuilder;
private IEnumerable<ISpecimenBuilder> adaptedBuilders;
private ISpecimenBuilderNode adaptedBuilderNode;
private ISpecimenBuilderNode graph;

private ISpecimenBuilderNode AdaptedBuilderNode
{
get
{
// We evaluate this value lazily for the performance reasons.
// Usually during the Fixture construction it's customized a lot of times.
// Some of the collections are not even touched after the construction and are immediately
// recreated again after a subsequent customization.
// To cover such scenarios we evaluate value on demand only saving the initialization time.
if (this.adaptedBuilderNode != null)
return this.adaptedBuilderNode;

// The intermediate "result" variable is needed to ensure that null value can be never returned
// in case of concurrency (we can set field to null). While current collection implementation doesn't
// seem to support concurrency, the additional guard adds more safety.
var result = this.adaptedBuilderNode = this.FindAdaptedSpecimenBuilderNode();
return result;
}
}

private void InvalidateCachedAdaptedBuilderNode() => this.adaptedBuilderNode = null;

private IEnumerable<ISpecimenBuilder> AdaptedBuilders => this.AdaptedBuilderNode;


/// <summary>
/// Initializes a new instance of the
Expand Down Expand Up @@ -50,9 +75,8 @@ public SpecimenBuilderNodeAdapterCollection(
ISpecimenBuilderNode graph,
Func<ISpecimenBuilderNode, bool> adaptedBuilderPredicate)
{
this.Graph = graph;
this.graph = graph;
this.isAdaptedBuilder = adaptedBuilderPredicate;
this.adaptedBuilders = this.Graph.FindFirstNode(this.TargetMemo.IsSpecifiedBy);
}

/// <summary>
Expand Down Expand Up @@ -90,7 +114,7 @@ public SpecimenBuilderNodeAdapterCollection(
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public int IndexOf(ISpecimenBuilder item)
{
return this.adaptedBuilders.IndexOf(item);
return this.AdaptedBuilders.IndexOf(item);
}

/// <summary>
Expand Down Expand Up @@ -119,7 +143,7 @@ public int IndexOf(ISpecimenBuilder item)
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public void Insert(int index, ISpecimenBuilder item)
{
this.Mutate(this.adaptedBuilders.Insert(index, item));
this.Mutate(this.AdaptedBuilders.Insert(index, item));
}

/// <summary>
Expand All @@ -144,7 +168,7 @@ public void Insert(int index, ISpecimenBuilder item)
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public void RemoveAt(int index)
{
this.Mutate(this.adaptedBuilders.RemoveAt(index));
this.Mutate(this.AdaptedBuilders.RemoveAt(index));
}

/// <summary>
Expand All @@ -168,11 +192,11 @@ public ISpecimenBuilder this[int index]
{
get
{
return this.adaptedBuilders.ElementAt(index);
return this.AdaptedBuilders.ElementAt(index);
}
set
{
this.Mutate(this.adaptedBuilders.SetItem(index, value));
this.Mutate(this.AdaptedBuilders.SetItem(index, value));
}
}

Expand All @@ -196,7 +220,7 @@ public ISpecimenBuilder this[int index]
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public void Add(ISpecimenBuilder item)
{
this.Mutate(this.adaptedBuilders.Concat(new[] { item }));
this.Mutate(this.AdaptedBuilders.Concat(new[] { item }));
}

/// <summary>
Expand Down Expand Up @@ -241,7 +265,7 @@ public void Clear()
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public bool Contains(ISpecimenBuilder item)
{
return this.adaptedBuilders.Contains(item);
return this.AdaptedBuilders.Contains(item);
}

/// <summary>Copies the elements of the collection to an
Expand All @@ -267,7 +291,7 @@ public bool Contains(ISpecimenBuilder item)
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public void CopyTo(ISpecimenBuilder[] array, int arrayIndex)
{
this.adaptedBuilders.ToArray().CopyTo(array, arrayIndex);
this.AdaptedBuilders.ToArray().CopyTo(array, arrayIndex);
}

/// <summary>
Expand All @@ -286,7 +310,7 @@ public void CopyTo(ISpecimenBuilder[] array, int arrayIndex)
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public int Count
{
get { return this.adaptedBuilders.Count(); }
get { return this.AdaptedBuilders.Count(); }
}

/// <summary>
Expand Down Expand Up @@ -358,7 +382,7 @@ public bool Remove(ISpecimenBuilder item)
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public IEnumerator<ISpecimenBuilder> GetEnumerator()
{
return this.adaptedBuilders.GetEnumerator();
return this.AdaptedBuilders.GetEnumerator();
}

/// <summary>
Expand Down Expand Up @@ -399,7 +423,16 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
/// </remarks>
/// <seealso cref="SpecimenBuilderNodeAdapterCollection" />
/// <seealso cref="SpecimenBuilderNodeAdapterCollection(ISpecimenBuilderNode, Func{ISpecimenBuilderNode, bool})" />
public ISpecimenBuilderNode Graph { get; private set; }
public ISpecimenBuilderNode Graph
{
get => this.graph;
private set
{
this.graph = value;
this.InvalidateCachedAdaptedBuilderNode();
this.OnGraphChanged(new SpecimenBuilderNodeEventArgs(value));
}
}

/// <summary>Raises the <see cref="E:GraphChanged" /> event.</summary>
/// <param name="e">
Expand All @@ -408,44 +441,22 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
/// </param>
protected virtual void OnGraphChanged(SpecimenBuilderNodeEventArgs e)
{
var handler = this.GraphChanged;
if (handler != null)
handler(this, e);
this.GraphChanged?.Invoke(this, e);
}

private void Mutate(IEnumerable<ISpecimenBuilder> builders)
{
var adaptedNode = this.AdaptedBuilderNode;

this.Graph = this.Graph.ReplaceNodes(
with: builders,
when: this.TargetMemo.IsSpecifiedBy);
this.adaptedBuilders = this.Graph.FindFirstNode(this.TargetMemo.IsSpecifiedBy);

this.OnGraphChanged(new SpecimenBuilderNodeEventArgs(this.Graph));
}

private TargetSpecification TargetMemo
{
get
{
var markerNode = this.Graph.FindFirstNode(this.isAdaptedBuilder);
var target = (ISpecimenBuilderNode)markerNode.First();
return new TargetSpecification(target);
}
when: adaptedNode.Equals);
}

private class TargetSpecification
private ISpecimenBuilderNode FindAdaptedSpecimenBuilderNode()
{
private readonly ISpecimenBuilderNode target;

public TargetSpecification(ISpecimenBuilderNode target)
{
this.target = target;
}

public bool IsSpecifiedBy(ISpecimenBuilderNode n)
{
return object.Equals(this.target, n);
}
var markerNode = this.Graph.FindFirstNode(this.isAdaptedBuilder);
return (ISpecimenBuilderNode) markerNode.First();
}
}
}

0 comments on commit 06ca10b

Please sign in to comment.