File: MS\Internal\Data\AccessorTable.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
//
// Description: Mapping of (SourceValueType, type, name) to (info, propertyType, args)
//
 
/***************************************************************************\
    Data binding uses reflection to obtain accessors for source properties,
    where an "accessor" can be a DependencyProperty, a PropertyInfo, or a
    PropertyDescriptor, depending on the nature of the source item and the
    property.  We cache the result of this discovery process in the
    AccessorTable;  table lookup is cheaper than doing reflection again.
\***************************************************************************/
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;                // IBindingList
using System.Diagnostics;
using System.Reflection;                    // TypeDescriptor
using System.Windows;                       // SR
using System.Windows.Threading;             // Dispatcher
using MS.Internal;                          // Invariant.Assert
 
namespace MS.Internal.Data
{
    internal sealed class AccessorInfo
    {
        internal AccessorInfo(object accessor, Type propertyType, object[] args)
        {
            _accessor = accessor;
            _propertyType = propertyType;
            _args = args;
        }
 
        internal object Accessor { get { return _accessor; } }
        internal Type PropertyType { get { return _propertyType; } }
        internal object[] Args { get { return _args; } }
 
        internal int Generation { get { return _generation; } set { _generation = value; } }
 
        private readonly object _accessor;   // DP, PD, or PI
        private readonly Type _propertyType; // type of the property
        private readonly object[] _args;     // args for indexed property
        private int _generation;             // used for discarding aged entries
    }
 
 
    internal sealed class AccessorTable
    {
        internal AccessorTable()
        {
        }
 
        // map (SourceValueType, type, name) to (accessor, propertyType, args)
        internal AccessorInfo this[SourceValueType sourceValueType, Type type, string name]
        {
            get
            {
                if (type == null || name == null)
                    return null;
 
                if (_table.TryGetValue(new AccessorTableKey(sourceValueType, type, name), out AccessorInfo info))
                {
#if DEBUG
                    // record the age of cache hits
                    int age = _generation - info.Generation;
 
                    if (age >= _ages.Length)
                    {
                        int[] newAges = new int[2*age];
                        _ages.CopyTo(newAges, 0);
                        _ages = newAges;
                    }
 
                    ++ _ages[age];
                    ++ _hits;
#endif
                    info.Generation = _generation;
                }
#if DEBUG
                else
                {
                    ++ _misses;
                }
#endif
                return info;
            }
            set
            {
                if (type != null && name != null)
                {
                    value.Generation = _generation;
                    _table[new AccessorTableKey(sourceValueType, type, name)] = value;
 
                    if (!_cleanupRequested)
                        RequestCleanup();
                }
            }
        }
 
        // request a cleanup pass
        private void RequestCleanup()
        {
            _cleanupRequested = true;
            Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new DispatcherOperationCallback(CleanupOperation), null);
        }
 
        // run a cleanup pass
        private object CleanupOperation(object arg)
        {
#if DEBUG
            int originalCount = _table.Count;
#endif
 
            // Remove entries that are sufficiently old
            foreach (KeyValuePair<AccessorTableKey, AccessorInfo> entry in _table)
            {
                int age = _generation - entry.Value.Generation;
                if (age >= AgeLimit)
                {
                    _table.Remove(entry.Key);
                }
            }
 
#if DEBUG
            if (_traceSize)
            {
                Console.WriteLine($"After generation {_generation}, removed {originalCount - _table.Count} of {originalCount} entries from AccessorTable, new count is {_table.Count}");
            }
#endif
 
            ++_generation;
 
            _cleanupRequested = false;
            return null;
        }
 
        // print data about how the cache behaved
        [Conditional("DEBUG")]
        internal void PrintStats()
        {
#if DEBUG
            if (_generation == 0 || _hits == 0)
            {
                Console.WriteLine("No stats available for AccessorTable.");
                return;
            }
 
            Console.WriteLine("AccessorTable had {0} hits, {1} misses ({2,2}%) in {3} generations.",
                        _hits, _misses, (100*_hits)/(_hits+_misses), _generation);
            Console.WriteLine("  Age   Hits   Pct   Cumulative");
            int cumulativeHits = 0;
            for (int i=0; i<_ages.Length; ++i)
            {
                if (_ages[i] > 0)
                {
                    cumulativeHits += _ages[i];
                    Console.WriteLine("{0,5} {1,6} {2,5} {3,5}",
                                    i, _ages[i], 100*_ages[i]/_hits, 100*cumulativeHits/_hits);
                }
            }
#endif
        }
 
        internal bool TraceSize
        {
            get { return _traceSize; }
            set { _traceSize = value; }
        }
 
        private const int AgeLimit = 10;      // entries older than this get removed.
 
        private readonly Dictionary<AccessorTableKey, AccessorInfo> _table = new Dictionary<AccessorTableKey, AccessorInfo>();
        private int _generation;
        private bool _cleanupRequested;
        bool _traceSize;
#if DEBUG
        private int[]       _ages = new int[10];
        private int         _hits, _misses;
#endif
 
        private readonly struct AccessorTableKey : IEquatable<AccessorTableKey>
        {
            public AccessorTableKey(SourceValueType sourceValueType, Type type, string name)
            {
                Invariant.Assert(type != null);
 
                _sourceValueType = sourceValueType;
                _type = type;
                _name = name;
            }
 
            public override bool Equals(object o) => o is AccessorTableKey other && Equals(other);
 
            public bool Equals(AccessorTableKey other) =>
                _sourceValueType == other._sourceValueType
                && _type == other._type
                && _name == other._name;
 
            public static bool operator ==(AccessorTableKey k1, AccessorTableKey k2) => k1.Equals(k2);
 
            public static bool operator !=(AccessorTableKey k1, AccessorTableKey k2) => !k1.Equals(k2);
 
            public override int GetHashCode() => unchecked(_type.GetHashCode() + _name.GetHashCode());
 
            private readonly SourceValueType _sourceValueType;
            private readonly Type _type;
            private readonly string _name;
        }
    }
}