1 //---------------------------------------------------------------------
2 // <copyright file="AliasGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
11 using System.Collections.Generic;
12 using System.Diagnostics;
13 using System.Globalization;
16 namespace System.Data.Common.Utils
19 /// Generates monotonically increasing names of the form PrefixCounter, where Prefix is an optional prefix string and Counter is the string representation of a monotonically increasing int value that wraps to zero at int.MaxValue
21 internal sealed class AliasGenerator
23 // beyond this size - we recycle the cache and regenerate names
24 // this recycling is in place because CTreeGenerator.GenerateNameForVar has prefix of "columnName" which could be unbounded
25 private const int MaxPrefixCount = 500;
27 // beyond this size per prefix, we don't cache the names (really large queries)
28 private const int CacheSize = 250;
30 // this caches integer->string so that happens less fequently
31 private readonly static string[] _counterNames = new string[CacheSize];
33 // We are using a copy-on-write instead of lock-on-read because dictionary is not multi-reader/single-writer safe.
34 // safe for frequent multi-thread reading by creating new instances (copy of previous instance) for uncommon writes.
35 private static Dictionary<string, string[]> _prefixCounter;
38 private readonly string _prefix;
39 private string[] _cache;
42 /// Constructs a new AliasGenerator with the specified prefix string
44 /// <param name="prefix">The prefix string that will appear as the first part of all aliases generated by this AliasGenerator. May be null to indicate that no prefix should be used</param>
45 internal AliasGenerator(string prefix) : this(prefix, CacheSize) { }
47 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1309:UseOrdinalStringComparison", MessageId = "System.Collections.Generic.Dictionary`2<System.String,System.String[]>.#ctor(System.Int32,System.Collections.Generic.IEqualityComparer`1<System.String>)")]
48 internal AliasGenerator(string prefix, int cacheSize)
50 _prefix = prefix ?? String.Empty;
52 // don't cache all alias, some are truely unique like CommandTree.BindingAliases
55 string[] cache = null;
56 Dictionary<string, string[]> updatedCache;
57 Dictionary<string, string[]> prefixCounter;
58 while ((null == (prefixCounter = _prefixCounter)) || !prefixCounter.TryGetValue(prefix, out _cache))
61 { // we need to create an instance, but it a different thread may win
62 cache = new string[cacheSize];
65 // grow the cache for prefixes
66 // We are using a copy-on-write instead of lock-on-read because dictionary is not multi-reader/single-writer safe.
67 // a)Create a larger dictionary
68 // b) Copy references from previous dictionary
69 // c) If previous dictionary changed references, repeat from (a)
70 // d) We now know the individual cache
71 int capacity = 1 + ((null != prefixCounter) ? prefixCounter.Count : 0);
72 updatedCache = new Dictionary<string, string[]>(capacity, StringComparer.InvariantCultureIgnoreCase);
73 if ((null != prefixCounter) && (capacity < MaxPrefixCount))
75 foreach (KeyValuePair<string, string[]> entry in prefixCounter)
77 updatedCache.Add(entry.Key, entry.Value);
80 updatedCache.Add(prefix, cache);
81 System.Threading.Interlocked.CompareExchange(ref _prefixCounter, updatedCache, prefixCounter);
87 /// Generates the next alias and increments the Counter.
89 /// <returns>The generated alias</returns>
90 internal string Next()
92 _counter = Math.Max(unchecked(1+_counter), 0);
93 return GetName(_counter);
97 /// Generates the alias for the index.
99 /// <param name="index">index to generate the alias for</param>
100 /// <returns>The generated alias</returns>
101 internal string GetName(int index)
104 if ((null == _cache) || unchecked((uint)_cache.Length <= (uint)index))
105 { // names are not cached beyond a particlar size
106 name = String.Concat(_prefix, index.ToString(CultureInfo.InvariantCulture));
108 else if (null == (name = _cache[index]))
109 { // name has not been generated and cached yet
110 if (unchecked((uint)_counterNames.Length <= (uint)index))
111 { // integer->string are not cached beyond a particular size
112 name = index.ToString(CultureInfo.InvariantCulture);
114 else if (null == (name = _counterNames[index]))
115 { // generate and cache the integer->string
116 _counterNames[index] = name = index.ToString(CultureInfo.InvariantCulture);
118 // generate and cache the prefix+integer
119 _cache[index] = name = String.Concat(_prefix, name);
125 internal string Prefix
127 get { return _prefix; }