PageRenderTime 331ms CodeModel.GetById 100ms app.highlight 117ms RepoModel.GetById 106ms app.codeStats 0ms

/Microsoft.Scripting/Runtime/DynamicOperations.cs

https://bitbucket.org/stefanrusek/xronos
C# | 658 lines | 376 code | 80 blank | 202 comment | 22 complexity | 3c8d633657da95d8240965bd7b2a199c MD5 | raw file
  1/* ****************************************************************************
  2 *
  3 * Copyright (c) Microsoft Corporation. 
  4 *
  5 * This source code is subject to terms and conditions of the Microsoft Public License. A 
  6 * copy of the license can be found in the License.html file at the root of this distribution. If 
  7 * you cannot locate the  Microsoft Public License, please send an email to 
  8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
  9 * by the terms of the Microsoft Public License.
 10 *
 11 * You must not remove this notice, or any other, from this software.
 12 *
 13 *
 14 * ***************************************************************************/
 15
 16#if CODEPLEX_40
 17using System;
 18#else
 19using System; using Microsoft;
 20#endif
 21using System.Collections.Generic;
 22using System.Diagnostics;
 23#if CODEPLEX_40
 24using System.Dynamic;
 25using System.Linq.Expressions;
 26#else
 27using Microsoft.Scripting;
 28using Microsoft.Linq.Expressions;
 29#endif
 30using System.Runtime.CompilerServices;
 31#if !CODEPLEX_40
 32using Microsoft.Runtime.CompilerServices;
 33#endif
 34
 35using Microsoft.Contracts;
 36using Microsoft.Scripting.Utils;
 37
 38namespace Microsoft.Scripting.Runtime {
 39
 40    /// <summary>
 41    /// ObjectOperations provide a large catalogue of object operations such as member access, conversions, 
 42    /// indexing, and things like addition.  There are several introspection and tool support services available
 43    /// for more advanced hosts.  
 44    /// 
 45    /// You get ObjectOperation instances from ScriptEngine, and they are bound to their engines for the semantics 
 46    /// of the operations.  There is a default instance of ObjectOperations you can share across all uses of the 
 47    /// engine.  However, very advanced hosts can create new instances.
 48    /// </summary>
 49    public sealed class DynamicOperations {
 50        private readonly LanguageContext _lc;
 51
 52        /// <summary> a dictionary of SiteKey's which are used to cache frequently used operations, logically a set </summary>
 53        private Dictionary<SiteKey, SiteKey> _sites = new Dictionary<SiteKey, SiteKey>();
 54
 55        /// <summary> the # of sites we had created at the last cleanup </summary>
 56        private int LastCleanup;
 57
 58        /// <summary> the total number of sites we've ever created </summary>
 59        private int SitesCreated;
 60
 61        /// <summary> the number of sites required before we'll try cleaning up the cache... </summary>
 62        private const int CleanupThreshold = 20;
 63
 64        /// <summary> the minimum difference between the average that is required to remove </summary>
 65        private const int RemoveThreshold = 2;
 66
 67        /// <summary> the maximum number we'll remove on a single cache cleanup </summary>
 68        private const int StopCleanupThreshold = CleanupThreshold / 2;
 69
 70        /// <summary> the number of sites we should clear after if we can't make progress cleaning up otherwise </summary>
 71        private const int ClearThreshold = 50;
 72
 73        internal DynamicOperations(LanguageContext lc) {
 74            ContractUtils.RequiresNotNull(lc, "lc");
 75            _lc = lc;
 76        }
 77
 78        #region Basic Operations
 79
 80        /// <summary>
 81        /// Calls the provided object with the given parameters and returns the result.
 82        /// 
 83        /// The prefered way of calling objects is to convert the object to a strongly typed delegate 
 84        /// using the ConvertTo methods and then invoking that delegate.
 85        /// </summary>
 86        public object Invoke(object obj, params object[] parameters) {
 87            // we support a couple of parameters instead of just splatting because JS doesn't yet support splatted arguments for function calls.
 88            switch (parameters.Length) {
 89                case 0: {
 90                        CallSite<Func<CallSite, object, object>> site;
 91                        site = GetOrCreateSite<object, object>(_lc.CreateInvokeBinder(new CallInfo(0)));
 92                        return site.Target(site, obj);
 93                    }
 94                case 1: {
 95                        CallSite<Func<CallSite, object, object, object>> site;
 96                        site = GetOrCreateSite<object, object, object>(_lc.CreateInvokeBinder(new CallInfo(1)));
 97                        return site.Target(site, obj, parameters[0]);
 98                    }
 99                case 2: {
100                        CallSite<Func<CallSite, object, object, object, object>> site;
101                        site = GetOrCreateSite<object, object, object, object>(_lc.CreateInvokeBinder(new CallInfo(2)));
102                        return site.Target(site, obj, parameters[0], parameters[1]);
103                    }
104                default:
105                    throw new NotImplementedException();
106            }
107        }
108
109        /// <summary>
110        /// Invokes a member on the provided object with the given parameters and returns the result.
111        /// </summary>
112        public object InvokeMember(object obj, string memberName, params object[] parameters) {
113            return InvokeMember(obj, memberName, false, parameters);
114        }
115
116        /// <summary>
117        /// Invokes a member on the provided object with the given parameters and returns the result.
118        /// </summary>
119        public object InvokeMember(object obj, string memberName, bool ignoreCase, params object[] parameters) {
120            // we support a couple of parameters instead of just splatting because JS doesn't yet support splatted arguments for function calls.
121            switch (parameters.Length) {
122                case 0: {
123                        CallSite<Func<CallSite, object, object>> site;
124                        site = GetOrCreateSite<object, object>(_lc.CreateCallBinder(memberName, ignoreCase, new CallInfo(0)));
125                        return site.Target(site, obj);
126                    }
127                case 1: {
128                        CallSite<Func<CallSite, object, object, object>> site;
129                        site = GetOrCreateSite<object, object, object>(_lc.CreateCallBinder(memberName, ignoreCase, new CallInfo(1)));
130                        return site.Target(site, obj, parameters[0]);
131                    }
132                case 2: {
133                        CallSite<Func<CallSite, object, object, object, object>> site;
134                        site = GetOrCreateSite<object, object, object, object>(_lc.CreateCallBinder(memberName, ignoreCase, new CallInfo(2)));
135                        return site.Target(site, obj, parameters[0], parameters[1]);
136                    }
137                default:
138                    throw new NotImplementedException();
139            }
140        }
141
142        /// <summary>
143        /// Creates a new instance from the provided object using the given parameters, and returns the result.
144        /// </summary>
145        public object CreateInstance(object obj, params object[] parameters) {
146            // we support a couple of parameters instead of just splatting because JS doesn't yet support splatted arguments for function calls.
147            switch (parameters.Length) {
148                case 0: {
149                        CallSite<Func<CallSite, object, object>> site;
150                        site = GetOrCreateSite<object, object>(_lc.CreateCreateBinder(new CallInfo(0)));
151                        return site.Target(site, obj);
152                    }
153                case 1: {
154                        CallSite<Func<CallSite, object, object, object>> site;
155                        site = GetOrCreateSite<object, object, object>(_lc.CreateCreateBinder(new CallInfo(1)));
156                        return site.Target(site, obj, parameters[0]);
157                    }
158                case 2: {
159                        CallSite<Func<CallSite, object, object, object, object>> site;
160                        site = GetOrCreateSite<object, object, object, object>(_lc.CreateCreateBinder(new CallInfo(2)));
161                        return site.Target(site, obj, parameters[0], parameters[1]);
162                    }
163                default:
164                    throw new NotImplementedException();
165            }
166        }
167
168        /// <summary>
169        /// Gets the member name from the object obj.  Throws an exception if the member does not exist or is write-only.
170        /// </summary>
171        public object GetMember(object obj, string name) {
172            return GetMember(obj, name, false);
173        }
174
175        /// <summary>
176        /// Gets the member name from the object obj and converts it to the type T.  Throws an exception if the
177        /// member does not exist, is write-only, or cannot be converted.
178        /// </summary>
179        public T GetMember<T>(object obj, string name) {
180            return GetMember<T>(obj, name, false);
181        }
182
183        /// <summary>
184        /// Gets the member name from the object obj.  Returns true if the member is successfully retrieved and 
185        /// stores the value in the value out param.
186        /// </summary>
187        public bool TryGetMember(object obj, string name, out object value) {
188            return TryGetMember(obj, name, false, out value);
189        }
190
191        /// <summary>
192        /// Returns true if the object has a member named name, false if the member does not exist.
193        /// </summary>
194        public bool ContainsMember(object obj, string name) {
195            return ContainsMember(obj, name, false);
196        }
197
198        /// <summary>
199        /// Removes the member name from the object obj.  Returns true if the member was successfully removed
200        /// or false if the member does not exist.
201        /// </summary>
202        public bool RemoveMember(object obj, string name) {
203            return RemoveMember(obj, name, false);
204        }
205
206        /// <summary>
207        /// Sets the member name on object obj to value.
208        /// </summary>
209        public void SetMember(object obj, string name, object value) {
210            SetMember(obj, name, value, false);
211        }
212
213        /// <summary>
214        /// Sets the member name on object obj to value.  This overload can be used to avoid
215        /// boxing and casting of strongly typed members.
216        /// </summary>
217        public void SetMember<T>(object obj, string name, T value) {
218            SetMember<T>(obj, name, value, false);
219        }
220
221        /// <summary>
222        /// Gets the member name from the object obj.  Throws an exception if the member does not exist or is write-only.
223        /// </summary>
224        public object GetMember(object obj, string name, bool ignoreCase) {
225            CallSite<Func<CallSite, object, object>> site;
226            site = GetOrCreateSite<object, object>(_lc.CreateGetMemberBinder(name, ignoreCase));
227            return site.Target(site, obj);
228        }
229
230        /// <summary>
231        /// Gets the member name from the object obj and converts it to the type T.  Throws an exception if the
232        /// member does not exist, is write-only, or cannot be converted.
233        /// </summary>
234        public T GetMember<T>(object obj, string name, bool ignoreCase) {
235            CallSite<Func<CallSite, object, T>> convertSite = GetOrCreateSite<object, T>(_lc.CreateConvertBinder(typeof(T), false));
236            CallSite<Func<CallSite, object, object>> site = GetOrCreateSite<object, object>(_lc.CreateGetMemberBinder(name, ignoreCase));
237            return convertSite.Target(convertSite, site.Target(site, obj));
238        }
239
240        /// <summary>
241        /// Gets the member name from the object obj.  Returns true if the member is successfully retrieved and 
242        /// stores the value in the value out param.
243        /// </summary>
244        public bool TryGetMember(object obj, string name, bool ignoreCase, out object value) {
245            try {
246                value = GetMember(obj, name, ignoreCase);
247                return true;
248            } catch (MissingMemberException) {
249                value = null;
250                return false;
251            }
252        }
253
254        /// <summary>
255        /// Returns true if the object has a member named name, false if the member does not exist.
256        /// </summary>
257        public bool ContainsMember(object obj, string name, bool ignoreCase) {
258            object dummy;
259            return TryGetMember(obj, name, ignoreCase, out dummy);
260        }
261
262        /// <summary>
263        /// Removes the member name from the object obj.  Returns true if the member was successfully removed
264        /// or false if the member does not exist.
265        /// </summary>
266        public bool RemoveMember(object obj, string name, bool ignoreCase) {
267            CallSite<Func<CallSite, object, bool>> site;
268            site = GetOrCreateSite<object, bool>(_lc.CreateDeleteMemberBinder(name, ignoreCase));
269            return site.Target(site, obj);
270        }
271
272        /// <summary>
273        /// Sets the member name on object obj to value.
274        /// </summary>
275        public void SetMember(object obj, string name, object value, bool ignoreCase) {
276            CallSite<Func<CallSite, object, object, object>> site;
277            site = GetOrCreateSite<object, object, object>(_lc.CreateSetMemberBinder(name, ignoreCase));
278            site.Target(site, obj, value);
279        }
280
281        /// <summary>
282        /// Sets the member name on object obj to value.  This overload can be used to avoid
283        /// boxing and casting of strongly typed members.
284        /// </summary>
285        public void SetMember<T>(object obj, string name, T value, bool ignoreCase) {
286            CallSite<Func<CallSite, object, T, object>> site;
287            site = GetOrCreateSite<object, T, object>(_lc.CreateSetMemberBinder(name, ignoreCase));
288            site.Target(site, obj, value);
289        }
290
291        /// <summary>
292        /// Convers the object obj to the type T.
293        /// </summary>
294        public T ConvertTo<T>(object obj) {
295            CallSite<Func<CallSite, object, T>> site;
296            site = GetOrCreateSite<object, T>(_lc.CreateConvertBinder(typeof(T), false));
297            return site.Target(site, obj);
298        }
299
300        /// <summary>
301        /// Converts the object obj to the type type.
302        /// </summary>
303        public object ConvertTo(object obj, Type type) {
304            CallSite<Func<CallSite, object, object>> site;
305            site = GetOrCreateSite<object, object>(_lc.CreateConvertBinder(type, false));
306            return site.Target(site, obj);
307        }
308
309        /// <summary>
310        /// Converts the object obj to the type T.  Returns true if the value can be converted, false if it cannot.
311        /// </summary>
312        public bool TryConvertTo<T>(object obj, out T result) {
313            try {
314                result = ConvertTo<T>(obj);
315                return true;
316            } catch (ArgumentTypeException) {
317                result = default(T);
318                return false;
319            } catch (InvalidCastException) {
320                result = default(T);
321                return false;
322            }
323        }
324
325        /// <summary>
326        /// Converts the object obj to the type type.  Returns true if the value can be converted, false if it cannot.
327        /// </summary>
328        public bool TryConvertTo(object obj, Type type, out object result) {
329            try {
330                result = ConvertTo(obj, type);
331                return true;
332            } catch (ArgumentTypeException) {
333                result = null;
334                return false;
335            } catch (InvalidCastException) {
336                result = null;
337                return false;
338            }
339        }
340
341        /// <summary>
342        /// Convers the object obj to the type T including explicit conversions which may lose information.
343        /// </summary>
344        public T ExplicitConvertTo<T>(object obj) {
345            CallSite<Func<CallSite, object, T>> site;
346            site = GetOrCreateSite<object, T>(_lc.CreateConvertBinder(typeof(T), true));
347            return site.Target(site, obj);
348        }
349
350        /// <summary>
351        /// Converts the object obj to the type type including explicit conversions which may lose information.
352        /// </summary>
353        public object ExplicitConvertTo(object obj, Type type) {
354            CallSite<Func<CallSite, object, object>> site;
355            site = GetOrCreateSite<object, object>(_lc.CreateConvertBinder(type, true));
356            return site.Target(site, obj);
357        }
358
359        /// <summary>
360        /// Converts the object obj to the type type including explicit conversions which may lose information.  
361        /// 
362        /// Returns true if the value can be converted, false if it cannot.
363        /// </summary>
364        public bool TryExplicitConvertTo(object obj, Type type, out object result) {
365            try {
366                result = ExplicitConvertTo(obj, type);
367                return true;
368            } catch (ArgumentTypeException) {
369                result = null;
370                return false;
371            } catch (InvalidCastException) {
372                result = null;
373                return false;
374            }
375        }
376
377        /// <summary>
378        /// Converts the object obj to the type T.  Returns true if the value can be converted, false if it cannot.
379        /// </summary>
380        public bool TryExplicitConvertTo<T>(object obj, out T result) {
381            try {
382                result = ExplicitConvertTo<T>(obj);
383                return true;
384            } catch (ArgumentTypeException) {
385                result = default(T);
386                return false;
387            } catch (InvalidCastException) {
388                result = default(T);
389                return false;
390            }
391        }
392
393        /// <summary>
394        /// Performs a generic unary operation on the strongly typed target and returns the value as the specified type
395        /// </summary>
396        public TResult DoOperation<TTarget, TResult>(ExpressionType operation, TTarget target) {
397            var site = GetOrCreateSite<TTarget, TResult>(_lc.CreateUnaryOperationBinder(operation));
398            return site.Target(site, target);
399        }
400
401        /// <summary>
402        /// Peforms the generic binary operation on the specified strongly typed targets and returns
403        /// the strongly typed result.
404        /// </summary>
405        public TResult DoOperation<TTarget, TOther, TResult>(ExpressionType operation, TTarget target, TOther other) {
406            var site = GetOrCreateSite<TTarget, TOther, TResult>(_lc.CreateBinaryOperationBinder(operation));
407            return site.Target(site, target, other);
408        }
409        
410        /// <summary>
411        /// Performs a generic unary operation on the specified target and returns the result.
412        /// </summary>
413        [Obsolete("Use UnaryOperation or BinaryOperation")]
414        public object DoOperation(string op, object target) {
415            return DoOperation<object, object>(op, target);
416        }
417
418        /// <summary>
419        /// Performs a generic unary operation on the strongly typed target and returns the value as the specified type
420        /// </summary>
421        [Obsolete("Use UnaryOperation or BinaryOperation")]
422        public TResult DoOperation<TTarget, TResult>(string op, TTarget target) {
423            CallSite<Func<CallSite, TTarget, TResult>> site;
424            site = GetOrCreateSite<TTarget, TResult>(_lc.CreateOperationBinder(op));
425            return site.Target(site, target);
426        }
427
428        public string GetDocumentation(object o) {
429            return _lc.GetDocumentation(o);
430        }
431
432        public IList<string> GetCallSignatures(object o) {
433            return _lc.GetCallSignatures(o);
434        }
435
436        public bool IsCallable(object o) {
437            return _lc.IsCallable(o);
438        }
439
440        /// <summary>
441        /// Performs the generic binary operation on the specified targets and returns the result.
442        /// </summary>
443        [Obsolete("Use UnaryOperation or BinaryOperation")]
444        public object DoOperation(Operators op, object target, object other) {
445            return DoOperation<object, object, object>(op.ToString(), target, other);
446        }
447
448        /// <summary>
449        /// Peforms the generic binary operation on the specified strongly typed targets and returns
450        /// the strongly typed result.
451        /// </summary>
452        [Obsolete("Use UnaryOperation or BinaryOperation")]
453        public TResult DoOperation<TTarget, TOther, TResult>(string op, TTarget target, TOther other) {
454            CallSite<Func<CallSite, TTarget, TOther, TResult>> site;
455            site = GetOrCreateSite<TTarget, TOther, TResult>(_lc.CreateOperationBinder(op));
456            return site.Target(site, target, other);
457        }
458
459        /// <summary>
460        /// Returns a list of strings which contain the known members of the object.
461        /// </summary>
462        public IList<string> GetMemberNames(object obj) {
463            return _lc.GetMemberNames(obj);
464        }
465
466        /// <summary>
467        /// Returns a string representation of the object in a language specific object display format.
468        /// </summary>
469        public string Format(object obj) {
470            return _lc.FormatObject(this, obj);
471        }
472
473        #endregion
474
475        #region Private implementation details
476
477        /// <summary>
478        /// Gets or creates a dynamic site w/ the specified type parameters for the provided binder.
479        /// </summary>
480        /// <remarks>
481        /// This will either get the site from the cache or create a new site and return it. The cache
482        /// may be cleaned if it's gotten too big since the last usage.
483        /// </remarks>
484        public CallSite<Func<CallSite, T0, Tret>> GetOrCreateSite<T0, Tret>(CallSiteBinder siteBinder) {
485            return GetOrCreateSite<CallSite<Func<CallSite, T0, Tret>>>(siteBinder, CallSite<Func<CallSite, T0, Tret>>.Create);
486        }
487
488        /// <summary>
489        /// Gets or creates a dynamic site w/ the specified type parameters for the provided binder.
490        /// </summary>
491        /// <remarks>
492        /// This will either get the site from the cache or create a new site and return it. The cache
493        /// may be cleaned if it's gotten too big since the last usage.
494        /// </remarks>
495        public CallSite<Func<CallSite, T0, T1, Tret>> GetOrCreateSite<T0, T1, Tret>(CallSiteBinder siteBinder) {
496            return GetOrCreateSite<CallSite<Func<CallSite, T0, T1, Tret>>>(siteBinder, CallSite<Func<CallSite, T0, T1, Tret>>.Create);
497        }
498
499        /// <summary>
500        /// Gets or creates a dynamic site w/ the specified type parameters for the provided binder.
501        /// </summary>
502        /// <remarks>
503        /// This will either get the site from the cache or create a new site and return it. The cache
504        /// may be cleaned if it's gotten too big since the last usage.
505        /// </remarks>
506        public CallSite<Func<CallSite, T0, T1, T2, Tret>> GetOrCreateSite<T0, T1, T2, Tret>(CallSiteBinder siteBinder) {
507            return GetOrCreateSite<CallSite<Func<CallSite, T0, T1, T2, Tret>>>(siteBinder, CallSite<Func<CallSite, T0, T1, T2, Tret>>.Create);
508        }
509
510        /// <summary>
511        /// Gets or creates a dynamic site w/ the specified type parameters for the provided binder.
512        /// </summary>
513        /// <remarks>
514        /// This will either get the site from the cache or create a new site and return it. The cache
515        /// may be cleaned if it's gotten too big since the last usage.
516        /// </remarks>
517        public CallSite<TSiteFunc> GetOrCreateSite<TSiteFunc>(CallSiteBinder siteBinder) where TSiteFunc : class {
518            return GetOrCreateSite<CallSite<TSiteFunc>>(siteBinder, CallSite<TSiteFunc>.Create);
519        }
520
521        /// <summary>
522        /// Helper to create to get or create the dynamic site - called by the GetSite methods.
523        /// </summary>
524        private T GetOrCreateSite<T>(CallSiteBinder siteBinder, Func<CallSiteBinder, T> factory) where T : CallSite {
525            SiteKey sk = new SiteKey(typeof(T), siteBinder);
526
527            lock (_sites) {
528                SiteKey old;
529                if (!_sites.TryGetValue(sk, out old)) {
530                    SitesCreated++;
531                    if (SitesCreated < 0) {
532                        // overflow, just reset back to zero...
533                        SitesCreated = 0;
534                        LastCleanup = 0;
535                    }
536                    sk.Site = factory(sk.SiteBinder);
537                    _sites[sk] = sk;
538                } else {
539                    sk = old;
540                }
541
542                sk.HitCount++;
543
544                CleanupNoLock();
545            }
546
547            return (T)sk.Site;
548        }
549
550        /// <summary>
551        /// Removes items from the cache that have the lowest usage...
552        /// </summary>
553        private void CleanupNoLock() {
554            // cleanup only if we have too many sites and we've created a bunch since our last cleanup
555            if (_sites.Count > CleanupThreshold && (LastCleanup < SitesCreated - CleanupThreshold)) {
556                LastCleanup = SitesCreated;
557
558                // calculate the average use, remove up to StopCleanupThreshold that are below average.
559                int totalUse = 0;
560                foreach (SiteKey sk in _sites.Keys) {
561                    totalUse += sk.HitCount;
562                }
563
564                int avgUse = totalUse / _sites.Count;
565                if (avgUse == 1 && _sites.Count > ClearThreshold) {
566                    // we only have a bunch of one-off requests
567                    _sites.Clear();
568                    return;
569                }
570
571                List<SiteKey> toRemove = null;
572                foreach (SiteKey sk in _sites.Keys) {
573                    if (sk.HitCount < (avgUse - RemoveThreshold)) {
574                        if (toRemove == null) {
575                            toRemove = new List<SiteKey>();
576                        }
577
578                        toRemove.Add(sk);
579                        if (toRemove.Count > StopCleanupThreshold) {
580                            // if we have a setup like weight(100), weight(1), weight(1), weight(1), ... we don't want
581                            // to just run through and remove all of the weight(1)'s. 
582                            break;
583                        }
584                    }
585
586                }
587
588                if (toRemove != null) {
589                    foreach (SiteKey sk in toRemove) {
590                        _sites.Remove(sk);
591                    }
592
593                    // reset all hit counts so the next time through is fair 
594                    // to newly added members which may take precedence.
595                    foreach (SiteKey sk in _sites.Keys) {
596                        sk.HitCount = 0;
597                    }
598                }
599            }
600        }
601
602        /// <summary>
603        /// Helper class for tracking all of our unique dynamic sites and their
604        /// usage patterns.  We hash on the combination of the binder and site type.
605        /// 
606        /// We also track the hit count and the key holds the site associated w/ the 
607        /// key.  Logically this is a set based upon the binder and site-type but we
608        /// store it in a dictionary.
609        /// </summary>
610        private class SiteKey : IEquatable<SiteKey> {
611            // the key portion of the data
612            internal readonly CallSiteBinder SiteBinder;
613            private readonly Type _siteType;
614
615            // not used for equality, used for caching strategy
616            public int HitCount;
617            public CallSite Site;
618
619            public SiteKey(Type siteType, CallSiteBinder siteBinder) {
620                Debug.Assert(siteType != null);
621                Debug.Assert(siteBinder != null);
622
623                SiteBinder = siteBinder;
624                _siteType = siteType;
625            }
626
627            [Confined]
628            public override bool Equals(object obj) {
629                return Equals(obj as SiteKey);
630            }
631
632            [Confined]
633            public override int GetHashCode() {
634                return SiteBinder.GetHashCode() ^ _siteType.GetHashCode();
635            }
636
637            #region IEquatable<SiteKey> Members
638
639            [StateIndependent]
640            public bool Equals(SiteKey other) {
641                if (other == null) return false;
642
643                return other.SiteBinder.Equals(SiteBinder) &&
644                    other._siteType == _siteType;
645            }
646
647            #endregion
648#if DEBUG
649            [Confined]
650            public override string ToString() {
651                return String.Format("{0} {1}", SiteBinder.ToString(), HitCount);
652            }
653#endif
654        }
655
656        #endregion
657    }
658}