File: Microsoft\CSharp\RuntimeBinder\BinderEquivalence.cs
Web Access
Project: src\src\libraries\Microsoft.CSharp\src\Microsoft.CSharp.csproj (Microsoft.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
 
namespace Microsoft.CSharp.RuntimeBinder
{
    internal static class BinderEquivalence
    {
        // An upper bound on the size of binder cache.
        // We do not need to cache all the binders, 4K should be enough for most cases.
        //
        // For the perspective: the dynamic testsuite has a lot of dynamic operations,
        // but ends up needing only ~500 binders once caching is enabled.
        //
        // NOTE:
        //    typical C# binders, once created, are rooted to their callsites, which are stored in static fields.
        //    the cache is unlikely to extend the life time of the binders.
        //    the limit here is just to have assurance on how large the cache may get.
        private const uint BINDER_CACHE_LIMIT = 4096;
 
        // keep a separate count because it is cheaper than calling CD.Count()
        // it does not need to be very precise either
        private static int cachedBinderCount;
 
        // it is unlikely to see a lot of contention on the binder cache.
        // creating binders is not a very frequent operation.
        // typically a dynamic operation in the source will create just one binder lazily when first executed.
        private static readonly ConcurrentDictionary<ICSharpBinder, ICSharpBinder> binderEquivalenceCache =
            new ConcurrentDictionary<ICSharpBinder, ICSharpBinder>(concurrencyLevel: 2, capacity: 32, new BinderEqualityComparer());
 
        internal static T TryGetExisting<T>(this T binder)
            where T : ICSharpBinder
        {
            var fromCache = binderEquivalenceCache.GetOrAdd(binder, binder);
            if (fromCache == (object)binder)
            {
                var count = Interlocked.Increment(ref cachedBinderCount);
 
                // a simple eviction policy -
                // if cache grows too big, just flush it and start over.
                if ((uint)count > BINDER_CACHE_LIMIT)
                {
                    binderEquivalenceCache.Clear();
                    cachedBinderCount = 0;
                }
            }
 
            return (T)fromCache;
        }
 
        internal sealed class BinderEqualityComparer : IEqualityComparer<ICSharpBinder>
        {
            public bool Equals(ICSharpBinder x, ICSharpBinder y) => x.IsEquivalentTo(y);
            public int GetHashCode(ICSharpBinder obj) => obj.GetGetBinderEquivalenceHash();
        }
    }
}