Files
Umbraco-CMS/src/Umbraco.Core/Models/PublishedContent/PublishedContentSet.cs
T
Per Ploug Krogslund 7440855c72 merge
2013-11-07 17:16:22 +01:00

235 lines
7.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Represents a set of <see cref="IPublishedContent"/>.
/// </summary>
/// <typeparam name="T">The type of content.</typeparam>
/// <remarks>
/// <para>A <c>ContentSet{T}</c> is created from an <c>IEnumerable{T}</c> using the <c>ToContentSet</c>
/// extension method.</para>
/// <para>The content set source is enumerated only once. Same as what you get
/// when you call ToList on an IEnumerable. Only, ToList enumerates its source when
/// created, whereas a content set enumerates its source only when the content set itself
/// is enumerated.</para>
/// </remarks>
public class PublishedContentSet<T> : IEnumerable<T>
where T : class, IPublishedContent
{
// used by <c>ToContentSet</c> extension method to initialize a new set from an IEnumerable.
internal PublishedContentSet(IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException("source");
Source = source;
}
#region Source
protected readonly IEnumerable<T> Source;
#endregion
#region Enumerated
// cache the enumeration so we don't enumerate more than once. Same as what you get
// when you call ToList on an IEnumerable. Only, ToList enumerates its source when
// created, whereas a content set enumerates its source only when the content set itself
// is enumerated.
// cache the wrapped items so if we reset the enumeration, we do not re-wrap everything (only new items).
private T[] _enumerated;
private readonly Dictionary<T, IPublishedContentExtended> _xContent = new Dictionary<T, IPublishedContentExtended>();
// wrap an item, ie create the actual clone for this set
private T MapContentAsT(T t)
{
return MapContent(t) as T;
}
internal IPublishedContentExtended MapContent(T t)
{
IPublishedContentExtended extend;
if (_xContent.TryGetValue(t, out extend)) return extend;
extend = PublishedContentExtended.Extend(t, this);
var asT = extend as T;
if (asT == null)
throw new InvalidOperationException(string.Format("Failed extend a published content of type {0}."
+ "Got {1} when expecting {2}.", t.GetType().FullName, extend.GetType().FullName, typeof(T).FullName));
_xContent[t] = extend;
return extend;
}
private T[] Enumerated
{
get
{
// enumerate the source and cache the result
// tell clones about their index within the set (for perfs purposes)
var index = 0;
return _enumerated ?? (_enumerated = Source.Select(t =>
{
var extend = MapContent(t);
extend.SetIndex(index++);
return extend as T;
}).ToArray());
}
}
// indicates that the source has changed
// so the set can clear its inner caches
// should only be used by DynamicPublishedContentList
internal void SourceChanged()
{
// reset the cached enumeration so it's enumerated again
if (_enumerated == null) return;
_enumerated = null;
foreach (var item in _xContent.Values)
item.ClearIndex();
var removed = _xContent.Keys.Except(Source);
foreach (var content in removed)
{
_xContent[content].ClearContentSet();
_xContent.Remove(content);
}
}
/// <summary>
/// Gets the number of items in the set.
/// </summary>
/// <returns>The number of items in the set.</returns>
/// <remarks>Will cause the set to be enumerated if it hasn't been already.</remarks>
public virtual int Count
{
get { return Enumerated.Length; }
}
#endregion
#region IEnumerable<T>
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)Enumerated).GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region Wrap methods returning T
public T ElementAt(int index)
{
return MapContentAsT(Source.ElementAt(index));
}
public T ElementAtOrDefault(int index)
{
var element = Source.ElementAtOrDefault(index);
return element == null ? null : MapContentAsT(element);
}
public T First()
{
return MapContentAsT(Source.First());
}
public T First(Func<T, bool> predicate)
{
return MapContentAsT(Source.First(predicate));
}
public T FirstOrDefault()
{
var first = Source.FirstOrDefault();
return first == null ? null : MapContentAsT(first);
}
public T FirstOrDefault(Func<T, bool> predicate)
{
var first = Source.FirstOrDefault(predicate);
return first == null ? null : MapContentAsT(first);
}
public T Last()
{
return MapContentAsT(Source.Last());
}
public T Last(Func<T, bool> predicate)
{
return MapContentAsT(Source.Last(predicate));
}
public T LastOrDefault()
{
var last = Source.LastOrDefault();
return last == null ? null : MapContentAsT(last);
}
public T LastOrDefault(Func<T, bool> predicate)
{
var last = Source.LastOrDefault(predicate);
return last == null ? null : MapContentAsT(last);
}
public T Single()
{
return MapContentAsT(Source.Single());
}
public T Single(Func<T, bool> predicate)
{
return MapContentAsT(Source.Single(predicate));
}
public T SingleOrDefault()
{
var single = Source.SingleOrDefault();
return single == null ? null : MapContentAsT(single);
}
public T SingleOrDefault(Func<T, bool> predicate)
{
var single = Source.SingleOrDefault(predicate);
return single == null ? null : MapContentAsT(single);
}
#endregion
#region Wrap methods returning IOrderedEnumerable<T>
public PublishedContentOrderedSet<T> OrderBy<TKey>(Func<T, TKey> keySelector)
{
return new PublishedContentOrderedSet<T>(Source.OrderBy(keySelector));
}
public PublishedContentOrderedSet<T> OrderBy<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
{
return new PublishedContentOrderedSet<T>(Source.OrderBy(keySelector, comparer));
}
public PublishedContentOrderedSet<T> OrderByDescending<TKey>(Func<T, TKey> keySelector)
{
return new PublishedContentOrderedSet<T>(Source.OrderByDescending(keySelector));
}
public PublishedContentOrderedSet<T> OrderByDescending<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
{
return new PublishedContentOrderedSet<T>(Source.OrderByDescending(keySelector, comparer));
}
#endregion
}
}