using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; namespace Mustache { /// /// Provides methods for creating instances of PropertyDictionary. /// internal sealed class PropertyDictionary : IDictionary { private static readonly Dictionary>> _cache = new Dictionary>>(); private readonly Dictionary> _typeCache; /// /// Initializes a new instance of a PropertyDictionary. /// /// The instance to wrap in the PropertyDictionary. public PropertyDictionary(object instance) { Instance = instance; if (instance == null) { _typeCache = new Dictionary>(); } else { lock (_cache) { _typeCache = getCacheType(instance); } } } private static Dictionary> getCacheType(object instance) { Type type = instance.GetType(); Dictionary> typeCache; if (!_cache.TryGetValue(type, out typeCache)) { typeCache = new Dictionary>(); BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy; var properties = getMembers(type, type.GetProperties(flags).Where(p => !p.IsSpecialName)); foreach (PropertyInfo propertyInfo in properties) { typeCache.Add(propertyInfo.Name, i => propertyInfo.GetValue(i, null)); } var fields = getMembers(type, type.GetFields(flags).Where(f => !f.IsSpecialName)); foreach (FieldInfo fieldInfo in fields) { typeCache.Add(fieldInfo.Name, i => fieldInfo.GetValue(i)); } _cache.Add(type, typeCache); } return typeCache; } private static IEnumerable getMembers(Type type, IEnumerable members) where TMember : MemberInfo { var singles = from member in members group member by member.Name into nameGroup where nameGroup.Count() == 1 from property in nameGroup select property; var multiples = from member in members group member by member.Name into nameGroup where nameGroup.Count() > 1 select ( from member in nameGroup orderby getDistance(type, member) select member ).First(); var combined = singles.Concat(multiples); return combined; } private static int getDistance(Type type, MemberInfo memberInfo) { int distance = 0; for (; type != null && type != memberInfo.DeclaringType; type = type.BaseType) { ++distance; } return distance; } /// /// Gets the underlying instance. /// public object Instance { get; } [EditorBrowsable(EditorBrowsableState.Never)] void IDictionary.Add(string key, object value) { throw new NotSupportedException(); } /// /// Determines whether a property with the given name exists. /// /// The name of the property. /// True if the property exists; otherwise, false. public bool ContainsKey(string key) { return _typeCache.ContainsKey(key); } /// /// Gets the name of the properties in the type. /// public ICollection Keys { get { return _typeCache.Keys; } } [EditorBrowsable(EditorBrowsableState.Never)] bool IDictionary.Remove(string key) { throw new NotSupportedException(); } /// /// Tries to get the value for the given property name. /// /// The name of the property to get the value for. /// The variable to store the value of the property or the default value if the property is not found. /// True if a property with the given name is found; otherwise, false. /// The name of the property was null. public bool TryGetValue(string key, out object value) { Func getter; if (!_typeCache.TryGetValue(key, out getter)) { value = null; return false; } value = getter(Instance); return true; } /// /// Gets the values of all of the properties in the object. /// public ICollection Values { get { ICollection> getters = _typeCache.Values; List values = new List(); foreach (Func getter in getters) { object value = getter(Instance); values.Add(value); } return values.AsReadOnly(); } } /// /// Gets or sets the value of the property with the given name. /// /// The name of the property to get or set. /// The value of the property with the given name. /// The property name was null. /// The type does not have a property with the given name. /// The property did not support getting or setting. /// /// The object does not match the target type, or a property is a value type but the value is null. /// public object this[string key] { get { Func getter = _typeCache[key]; return getter(Instance); } [EditorBrowsable(EditorBrowsableState.Never)] set { throw new NotSupportedException(); } } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Add(KeyValuePair item) { throw new NotSupportedException(); } [EditorBrowsable(EditorBrowsableState.Never)] void ICollection>.Clear() { throw new NotSupportedException(); } bool ICollection>.Contains(KeyValuePair item) { if (!_typeCache.TryGetValue(item.Key, out Func getter)) { return false; } object value = getter(Instance); return Equals(item.Value, value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { List> pairs = new List>(); ICollection>> collection = _typeCache; foreach (KeyValuePair> pair in collection) { Func getter = pair.Value; object value = getter(Instance); pairs.Add(new KeyValuePair(pair.Key, value)); } pairs.CopyTo(array, arrayIndex); } /// /// Gets the number of properties in the type. /// public int Count { get { return _typeCache.Count; } } /// /// Gets or sets whether updates will be ignored. /// bool ICollection>.IsReadOnly { get { return true; } } [EditorBrowsable(EditorBrowsableState.Never)] bool ICollection>.Remove(KeyValuePair item) { throw new NotSupportedException(); } /// /// Gets the propety name/value pairs in the object. /// /// public IEnumerator> GetEnumerator() { foreach (KeyValuePair> pair in _typeCache) { Func getter = pair.Value; object value = getter(Instance); yield return new KeyValuePair(pair.Key, value); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }