using System; using System.Collections.Generic; using System.Linq; namespace Umbraco.Core.Models.PublishedContent { /// /// Represents a set of . /// /// The type of content. /// /// A ContentSet{T} is created from an IEnumerable{T} using the ToContentSet /// extension method. /// 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. /// public class PublishedContentSet : IEnumerable where T : class, IPublishedContent { // used by ToContentSet extension method to initialize a new set from an IEnumerable. internal PublishedContentSet(IEnumerable source) { if (source == null) throw new ArgumentNullException("source"); Source = source; } #region Source protected readonly IEnumerable 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 _xContent = new Dictionary(); // 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); } } /// /// Gets the number of items in the set. /// /// The number of items in the set. /// Will cause the set to be enumerated if it hasn't been already. public virtual int Count { get { return Enumerated.Length; } } #endregion #region IEnumerable public IEnumerator GetEnumerator() { return ((IEnumerable)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 predicate) { return MapContentAsT(Source.First(predicate)); } public T FirstOrDefault() { var first = Source.FirstOrDefault(); return first == null ? null : MapContentAsT(first); } public T FirstOrDefault(Func predicate) { var first = Source.FirstOrDefault(predicate); return first == null ? null : MapContentAsT(first); } public T Last() { return MapContentAsT(Source.Last()); } public T Last(Func predicate) { return MapContentAsT(Source.Last(predicate)); } public T LastOrDefault() { var last = Source.LastOrDefault(); return last == null ? null : MapContentAsT(last); } public T LastOrDefault(Func predicate) { var last = Source.LastOrDefault(predicate); return last == null ? null : MapContentAsT(last); } public T Single() { return MapContentAsT(Source.Single()); } public T Single(Func predicate) { return MapContentAsT(Source.Single(predicate)); } public T SingleOrDefault() { var single = Source.SingleOrDefault(); return single == null ? null : MapContentAsT(single); } public T SingleOrDefault(Func predicate) { var single = Source.SingleOrDefault(predicate); return single == null ? null : MapContentAsT(single); } #endregion #region Wrap methods returning IOrderedEnumerable public PublishedContentOrderedSet OrderBy(Func keySelector) { return new PublishedContentOrderedSet(Source.OrderBy(keySelector)); } public PublishedContentOrderedSet OrderBy(Func keySelector, IComparer comparer) { return new PublishedContentOrderedSet(Source.OrderBy(keySelector, comparer)); } public PublishedContentOrderedSet OrderByDescending(Func keySelector) { return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector)); } public PublishedContentOrderedSet OrderByDescending(Func keySelector, IComparer comparer) { return new PublishedContentOrderedSet(Source.OrderByDescending(keySelector, comparer)); } #endregion } }