File: System\Xaml\XamlSchemaContext.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\System.Xaml\System.Xaml.csproj (System.Xaml)
// 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.
 
#nullable disable
 
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Xaml.MS.Impl;
using System.Xaml.Schema;
using MS.Internal.Xaml.Parser;
using System.Collections.Concurrent;
 
namespace System.Xaml
{
    /// <remarks>
    /// This class, and the closure of its references (i.e. XamlType, XamlMember, etc) are all
    /// thread-safe in their base implementations. Derived implementations can choose whether or not
    /// to be thread-safe.
    /// </remarks>
    public class XamlSchemaContext
    {
        #region Common Fields
 
        // We don't expect a lot of contention on our dictionaries, so avoid the overhead of
        // extra lock partitioning
        const int ConcurrencyLevel = 1;
        const int DictionaryCapacity = 17;
 
        // Immutable, initialized in constructor
        private readonly ReadOnlyCollection<Assembly> _referenceAssemblies;
 
        // take this lock when iterating new assemblies in the AppDomain/RefAssm
        object _syncExaminingAssemblies;
 
        #endregion
 
        #region Constructors
 
        public XamlSchemaContext()
            : this(null, null) { }
 
        public XamlSchemaContext(XamlSchemaContextSettings settings)
            : this(null, settings) { }
 
        public XamlSchemaContext(IEnumerable<Assembly> referenceAssemblies)
            : this(referenceAssemblies, null) { }
 
        public XamlSchemaContext(IEnumerable<Assembly> referenceAssemblies, XamlSchemaContextSettings settings)
        {
            if (referenceAssemblies is not null)
            {
                // ReadOnlyCollection wants an IList but we have an IEnumerable.
                List<Assembly> listOfAssemblies = new List<Assembly>(referenceAssemblies);
                _referenceAssemblies = new ReadOnlyCollection<Assembly>(listOfAssemblies);
            }
            _settings = (settings is not null)
                ? new XamlSchemaContextSettings(settings)
                : new XamlSchemaContextSettings();
            _syncExaminingAssemblies = new Object();
            InitializeAssemblyLoadHook();
        }
 
        // Deliberate implementation of Finalize without Dispose:
        // We don't actually hold any unmanaged resources, the purpose of this finalizer is to unhook
        // the AppDomain.AssemblyLoad event, and thus allow the AssemblyLoadHandler to be GCed.
        // We don't implement IDisposable, because if we did, users would probably try to dispose us
        // right after the XAML load completes, which is wrong if there were any XamlDeferLoad properties.
        ~XamlSchemaContext()
        {
            try
            {
                if (_assemblyLoadHandler is not null && !Environment.HasShutdownStarted)
                {
                    _assemblyLoadHandler.Unhook();
                }
            }
            catch
            {
                // Finalizers shouldn't throw
            }
        }
 
        #endregion
 
        #region Enumeration methods
 
        // This list is valid when it is non-null. If it gets invalidated (e.g. by the addition of
        // new namespaces), it is reset to null.
        private IList<string> _nonClrNamespaces;
 
        public virtual ICollection<XamlType> GetAllXamlTypes(string xamlNamespace)
        {
            UpdateXmlNsInfo();
            XamlNamespace ns = GetXamlNamespace(xamlNamespace);
            return ns.GetAllXamlTypes();
        }
 
        public virtual IEnumerable<string> GetAllXamlNamespaces()
        {
            UpdateXmlNsInfo();
            IList<string> result = _nonClrNamespaces;
            if (result is null)
            {
                // To avoid a race condition when assigning this list, don't allow any additional
                // namespaces to be added while we're iterating the current list
                lock (_syncExaminingAssemblies)
                {
                    result = new List<string>();
                    foreach (KeyValuePair<string, XamlNamespace> ns in NamespaceByUriList)
                    {
                        if (ns.Value.IsResolved && !ns.Value.IsClrNamespace)
                        {
                            result.Add(ns.Key);
                        }
                    }
                    result = new ReadOnlyCollection<string>(result);
                    _nonClrNamespaces = result;
                }
            }
            return result;
        }
 
        #endregion
 
        #region Prefix lookup
 
        // This dictionary defaults to null. The first time it is used, it needs to be initialized
        // in full. After that, it will be updated as needed by UpdateXmlNsInfo.
        private ConcurrentDictionary<string, string> _preferredPrefixes;
 
        public virtual string GetPreferredPrefix(string xmlns)
        {
            ArgumentNullException.ThrowIfNull(xmlns);
            UpdateXmlNsInfo();
            if (_preferredPrefixes is null)
            {
                InitializePreferredPrefixes();
            }
            string result;
            if (!_preferredPrefixes.TryGetValue(xmlns, out result))
            {
                if (XamlLanguage.XamlNamespaces.Contains(xmlns))
                {
                    result = XamlLanguage.PreferredPrefix;
                }
                else
                {
                    string clrNs, assemblyName;
                    if (ClrNamespaceUriParser.TryParseUri(xmlns, out clrNs, out assemblyName))
                    {
                        result = GetPrefixForClrNs(clrNs, assemblyName);
                    }
                    else
                    {
                        result = KnownStrings.DefaultPrefix;
                    }
                }
                result = TryAdd(_preferredPrefixes, xmlns, result);
            }
            return result;
        }
 
        string GetPrefixForClrNs(string clrNs, string assemblyName)
        {
            if (string.IsNullOrEmpty(assemblyName))
            {
                return KnownStrings.LocalPrefix;
            }
            var sb = new StringBuilder();
            foreach (string segment in clrNs.Split('.'))
            {
                if (!string.IsNullOrEmpty(segment))
                {
                    sb.Append(Char.ToLower(segment[0], TypeConverterHelper.InvariantEnglishUS));
                }
            }
            if (sb.Length > 0)
            {
                string result = sb.ToString();
 
                if (KS.Eq(result, XamlLanguage.PreferredPrefix))
                {
                    // Prevent this algorithm from stealing 'x'
                    return KnownStrings.DefaultPrefix;
                }
                else if (KS.Eq(result, KnownStrings.XmlPrefix))
                {
                    // Prevent this algorithm from stealing 'xml'
                    return KnownStrings.DefaultPrefix;
                }
                else
                {
                    return result;
                }
            }
            else
            {
                return KnownStrings.LocalPrefix;
            }
        }
 
        void InitializePreferredPrefixes()
        {
            // To avoid an assignment race condition, prevent new assemblies from being processed while we're
            // iterating the existing list
            lock (_syncExaminingAssemblies)
            {
                ConcurrentDictionary<string, string> preferredPrefixes = CreateDictionary<string, string>();
                foreach (XmlNsInfo nsInfo in EnumerateXmlnsInfos())
                {
                    UpdatePreferredPrefixes(nsInfo, preferredPrefixes);
                }
                _preferredPrefixes = preferredPrefixes;
            }
        }
 
        void UpdatePreferredPrefixes(XmlNsInfo newNamespaces, ConcurrentDictionary<string, string> prefixDict)
        {
            foreach (KeyValuePair<string, string> nsToPrefix in newNamespaces.Prefixes)
            {
                string existingPrefix;
                string preferredPrefix = nsToPrefix.Value;
                if (!prefixDict.TryGetValue(nsToPrefix.Key, out existingPrefix))
                {
                    existingPrefix = TryAdd(prefixDict, nsToPrefix.Key, preferredPrefix);
                }
                while (existingPrefix != preferredPrefix)
                {
                    preferredPrefix = XmlNsInfo.GetPreferredPrefix(existingPrefix, preferredPrefix);
                    if (!KS.Eq(preferredPrefix, existingPrefix))
                    {
                        existingPrefix = TryUpdate(prefixDict, nsToPrefix.Key, preferredPrefix, existingPrefix);
                    }
                }
            }
        }
 
        #endregion
 
        #region Name -> Type/Directive mapping
 
        public virtual XamlDirective GetXamlDirective(string xamlNamespace, string name)
        {
            ArgumentNullException.ThrowIfNull(xamlNamespace);
            ArgumentNullException.ThrowIfNull(name);
 
            if (XamlLanguage.XamlNamespaces.Contains(xamlNamespace))
            {
                return XamlLanguage.LookupXamlDirective(name);
            }
            else if (XamlLanguage.XmlNamespaces.Contains(xamlNamespace))
            {
                return XamlLanguage.LookupXmlDirective(name);
            }
            return null;
        }
 
        public XamlType GetXamlType(XamlTypeName xamlTypeName)
        {
            ArgumentNullException.ThrowIfNull(xamlTypeName);
            if (xamlTypeName.Name is null)
            {
                throw new ArgumentException(SR.Format(SR.ReferenceIsNull, "xamlTypeName.Name"), nameof(xamlTypeName));
            }
            if (xamlTypeName.Namespace is null)
            {
                throw new ArgumentException(SR.Format(SR.ReferenceIsNull, "xamlTypeName.Namespace"), nameof(xamlTypeName));
            }
 
            XamlType[] typeArgs = null;
            if (xamlTypeName.HasTypeArgs)
            {
                typeArgs = new XamlType[xamlTypeName.TypeArguments.Count];
                for (int i = 0; i < xamlTypeName.TypeArguments.Count; i++)
                {
                    if (xamlTypeName.TypeArguments[i] is null)
                    {
                        throw new ArgumentException(SR.Format(SR.CollectionCannotContainNulls, "xamlTypeName.TypeArguments"));
                    }
                    typeArgs[i] = GetXamlType(xamlTypeName.TypeArguments[i]);
                    if (typeArgs[i] is null)
                    {
                        return null;
                    }
                }
            }
            return GetXamlType(xamlTypeName.Namespace, xamlTypeName.Name, typeArgs);
        }
 
        protected internal virtual XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
        {
            ArgumentNullException.ThrowIfNull(xamlNamespace);
            ArgumentNullException.ThrowIfNull(name);
            if (typeArguments is not null)
            {
                foreach (XamlType typeArg in typeArguments)
                {
                    if (typeArg is null)
                    {
                        throw new ArgumentException(SR.Format(SR.CollectionCannotContainNulls, "typeArguments"));
                    }
                    if (typeArg.UnderlyingType is null)
                    {
                        return null;
                    }
                }
            }
 
            XamlType result = null;
            if (typeArguments is null || typeArguments.Length == 0)
            {
                result = XamlLanguage.LookupXamlType(xamlNamespace, name);
                if (result is not null)
                {
                    if (FullyQualifyAssemblyNamesInClrNamespaces)
                    {
                        // We can't return types from XamlLanguage directly because their SchemaContext
                        // has FullyQualifyAssemblyNamesInClrNamespaces set to false
                        result = GetXamlType(result.UnderlyingType);
                    }
                    return result;
                }
            }
 
            XamlNamespace ns = GetXamlNamespace(xamlNamespace);
            int revision = ns.RevisionNumber;
            result = ns.GetXamlType(name, typeArguments);
            if (result is null && !ns.IsClrNamespace)
            {
                UpdateXmlNsInfo();
                if (ns.RevisionNumber > revision)
                {
                    result = ns.GetXamlType(name, typeArguments);
                }
            }
 
            return result;
        }
 
        #endregion
 
        #region Namespace Compatibility Lookup
 
        // Laziy init, always access through property
        private ConcurrentDictionary<string, string> _xmlNsCompatDict;
 
        // Thread-safe cache - always use TryAdd or TryUpdate to write
        private ConcurrentDictionary<string, string> XmlNsCompatDict
        {
            get
            {
                if (_xmlNsCompatDict is null)
                    Interlocked.CompareExchange(ref _xmlNsCompatDict, CreateDictionary<string, string>(), null);
                return _xmlNsCompatDict;
            }
        }
 
        // Note: this method doesn't apply transitive subsuming, the caller is responsible for doing that.
        public virtual bool TryGetCompatibleXamlNamespace(string xamlNamespace, out string compatibleNamespace)
        {
            ArgumentNullException.ThrowIfNull(xamlNamespace);
 
            // Note: this method has order-dependent behavior for backcompat.
            // When we look up a namespace, we throw if it has conflicting XmlnsCompatAttributes.
            // However, once we've looked up a namespace, we cache the result, and if an assembly with
            // conflicting attributes is loaded later, we just ignore it.
 
            // First try to load from our cache
            if (XmlNsCompatDict.TryGetValue(xamlNamespace, out compatibleNamespace))
            {
                return true;
            }
 
            // Then look for XmlnsCompatAttributes
            UpdateXmlNsInfo();
            compatibleNamespace = GetCompatibleNamespace(xamlNamespace);
 
            // Fall back to just using the requested namespace;
            if (compatibleNamespace is null)
            {
                compatibleNamespace = xamlNamespace;
            }
 
            // Make sure that the namespace actually exists
            XamlNamespace ns = GetXamlNamespace(compatibleNamespace);
            if (ns.IsResolved)
            {
                compatibleNamespace = TryAdd(XmlNsCompatDict, xamlNamespace, compatibleNamespace);
                return true;
            }
            else
            {
                // We don't cache negative results, since a lookup might succeed later after a new assembly
                // is loaded. XmlCompatibilityReader already caches all results (positive and negative) within
                // the scope of a given document.
                compatibleNamespace = null;
                return false;
            }
        }
 
        // Find a subsuming namespace within the current processed assemblies
        private string GetCompatibleNamespace(string oldNs)
        {
            string result = null;
            Assembly resultAssembly = null;
            // locking to prevent new loaded assemblies being processed while we iterate list
            lock (_syncExaminingAssemblies)
            {
                foreach (XmlNsInfo nsInfo in EnumerateXmlnsInfos())
                {
                    Assembly curAssembly = nsInfo.Assembly;
                    if (curAssembly is null)
                    {
                        continue;
                    }
                    IDictionary<string, string> oldToNewNs = null;
 
                    // When trawling the entire AppDomain, suppress exceptions from assemblies with bad attributes
                    if (ReferenceAssemblies is null)
                    {
                        try
                        {
                            oldToNewNs = nsInfo.OldToNewNs;
                        }
                        catch (Exception ex)
                        {
                            if (CriticalExceptions.IsCriticalException(ex))
                            {
                                throw;
                            }
                            // just skip to the next assembly
                            continue;
                        }
                    }
                    else
                    {
                        oldToNewNs = nsInfo.OldToNewNs;
                    }
 
                    string newNs;
                    if (oldToNewNs.TryGetValue(oldNs, out newNs))
                    {
                        if (result is not null && result != newNs)
                        {
                            throw new XamlSchemaException(SR.Format(SR.DuplicateXmlnsCompatAcrossAssemblies,
                                resultAssembly.FullName, curAssembly.FullName, oldNs));
                        }
                        result = newNs;
                        resultAssembly = curAssembly;
                    }
                }
            }
            return result;
        }
 
        #endregion
 
        #region Type and member cache
 
        // Lazy init, access these fields through the properties
        private ConcurrentDictionary<Type, XamlType> _masterTypeList;
        private ConcurrentDictionary<ReferenceEqualityTuple<Type, XamlType, Type>, object> _masterValueConverterList;
        private ConcurrentDictionary<ReferenceEqualityTuple<MemberInfo, MemberInfo>, XamlMember> _masterMemberList;
        private ConcurrentDictionary<XamlType, Dictionary<string,SpecialBracketCharacters> > _masterBracketCharacterCache;
 
        // Security note: all of these ConcurrentDictionaries use Reference Equality to prevent spoofing of
        // RuntimeTypes/Members by other custom derived descendants of System.Type/MemberInfo.
        // E.g. if a user called GetXamlType(Type) and passed in a custom descendant of System.Type
        // that reported itself as Equal to some real type, then when we went to look up the real
        // type, we would get the spoofed one instead. Using reference equality avoids that.
 
        // Thread-safe cache - always use TryAdd or TryUpdate to write
        private ConcurrentDictionary<XamlType, Dictionary<string, SpecialBracketCharacters> > MasterBracketCharacterCache
        {
            get
            {
                if (_masterBracketCharacterCache is null)
                    Interlocked.CompareExchange(ref _masterBracketCharacterCache, CreateDictionary<XamlType, Dictionary<string, SpecialBracketCharacters>>(), null);
                return _masterBracketCharacterCache;
            }
        }
 
        // Thread-safe cache - always use TryAdd or TryUpdate to write
        private ConcurrentDictionary<Type, XamlType> MasterTypeList
        {
            get
            {
                if (_masterTypeList is null)
                    Interlocked.CompareExchange(ref _masterTypeList, CreateDictionary<Type, XamlType>(ReferenceEqualityComparer.Instance), null);
                return _masterTypeList;
            }
        }
 
        // Thread-safe cache - always use TryAdd or TryUpdate to write
        private ConcurrentDictionary<ReferenceEqualityTuple<Type, XamlType, Type>, object> MasterValueConverterList
        {
            get
            {
                if (_masterValueConverterList is null)
                    Interlocked.CompareExchange(ref _masterValueConverterList, CreateDictionary<ReferenceEqualityTuple<Type, XamlType, Type>, object>(), null);
                return _masterValueConverterList;
            }
        }
 
        // Thread-safe cache - always use TryAdd or TryUpdate to write
        private ConcurrentDictionary<ReferenceEqualityTuple<MemberInfo, MemberInfo>, XamlMember> MasterMemberList
        {
            get
            {
                if (_masterMemberList is null)
                    Interlocked.CompareExchange(ref _masterMemberList, CreateDictionary<ReferenceEqualityTuple<MemberInfo, MemberInfo>, XamlMember>(), null);
                return _masterMemberList;
            }
        }
 
        public virtual XamlType GetXamlType(Type type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            return GetXamlType(type, XamlLanguage.TypeAlias(type));
        }
 
        internal XamlType GetXamlType(Type type, string alias)
        {
            ArgumentNullException.ThrowIfNull(type);
            XamlType xamlType = null;
            if (!MasterTypeList.TryGetValue(type, out xamlType))
            {
                xamlType = new XamlType(alias, type, this, null, null);
                xamlType = TryAdd(MasterTypeList, type, xamlType);
            }
            return xamlType;
        }
 
        /// <summary>
        /// Constructs a cache of all the members in this particular type that have
        /// MarkupExtensionBracketCharactersAttribute set on them. This cache is added to a master
        /// cache which stores the BracketCharacter cache for each type.
        /// </summary>
        internal Dictionary<string, SpecialBracketCharacters> InitBracketCharacterCacheForType(XamlType type)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            Dictionary<string, SpecialBracketCharacters> bracketCharacterCache = null;
            if (type.IsMarkupExtension)
            {
                if (!MasterBracketCharacterCache.TryGetValue(type, out bracketCharacterCache))
                {
                    bracketCharacterCache = BuildBracketCharacterCacheForType(type);
                    bracketCharacterCache = TryAdd(MasterBracketCharacterCache, type, bracketCharacterCache);
                }
            }
 
            return bracketCharacterCache;
        }
 
        /// <summary>
        /// Looks up all properties via reflection on the given type, and scans through the attributes on all of them
        /// to build a cache of properties which have MarkupExtensionBracketCharactersAttribute set on them.
        /// </summary>
        private Dictionary<string, SpecialBracketCharacters> BuildBracketCharacterCacheForType(XamlType type)
        {
            Dictionary<string, SpecialBracketCharacters> map = new Dictionary<string, SpecialBracketCharacters>(StringComparer.OrdinalIgnoreCase);
            ICollection<XamlMember> members = type.GetAllMembers();
            foreach (XamlMember member in members)
            {
                string constructorArgumentName = member.ConstructorArgument;
                string propertyName = member.Name;
                IReadOnlyDictionary<char,char> markupExtensionBracketCharactersList = member.MarkupExtensionBracketCharacters;
                SpecialBracketCharacters splBracketCharacters = markupExtensionBracketCharactersList is not null && markupExtensionBracketCharactersList.Count > 0
                    ? new SpecialBracketCharacters(markupExtensionBracketCharactersList)
                    : null;
                if (splBracketCharacters is not null)
                {
                    splBracketCharacters.EndInit();
                    map.Add(propertyName, splBracketCharacters);
                    if (!string.IsNullOrEmpty(constructorArgumentName))
                    {
                        map.Add(constructorArgumentName, splBracketCharacters);
                    }
                }
            }
 
            return map.Count > 0 ? map : null;
        }
 
        protected internal XamlValueConverter<TConverterBase> GetValueConverter<TConverterBase>(
            Type converterType, XamlType targetType)
            where TConverterBase : class
        {
            var key = new ReferenceEqualityTuple<Type, XamlType, Type>(converterType, targetType, typeof(TConverterBase));
            object result;
            if (!MasterValueConverterList.TryGetValue(key, out result))
            {
                result = new XamlValueConverter<TConverterBase>(converterType, targetType);
                result = TryAdd(MasterValueConverterList, key, result);
            }
            return (XamlValueConverter<TConverterBase>)result;
        }
 
        internal virtual XamlMember GetProperty(PropertyInfo pi)
        {
            var xpik = new ReferenceEqualityTuple<MemberInfo, MemberInfo>(pi, null);
            XamlMember member;
            if (!MasterMemberList.TryGetValue(xpik, out member))
            {
                member = new XamlMember(pi, this);
                member = TryAdd(MasterMemberList, xpik, member);
            }
            return member;
        }
 
        internal virtual XamlMember GetEvent(EventInfo ei)
        {
            var xpik = new ReferenceEqualityTuple<MemberInfo, MemberInfo>(ei, null);
            XamlMember member;
            if (!MasterMemberList.TryGetValue(xpik, out member))
            {
                member = new XamlMember(ei, this);
                member = TryAdd(MasterMemberList, xpik, member);
            }
            return member;
        }
 
        // Caller responsible for ensuring getter and setter not null
        internal virtual XamlMember GetAttachableProperty(string name, MethodInfo getter, MethodInfo setter)
        {
            XamlMember property;
            var xpik = new ReferenceEqualityTuple<MemberInfo, MemberInfo>(getter, setter);
            if (!MasterMemberList.TryGetValue(xpik, out property))
            {
                property = new XamlMember(name, getter, setter, this);
                property = TryAdd(MasterMemberList, xpik, property);
            }
            return property;
        }
 
        internal virtual XamlMember GetAttachableEvent(string name, MethodInfo adder)
        {
            XamlMember property;
            var xpik = new ReferenceEqualityTuple<MemberInfo, MemberInfo>(adder, null);
            if (!MasterMemberList.TryGetValue(xpik, out property))
            {
                property = new XamlMember(name, adder, this);
                property = TryAdd(MasterMemberList, xpik, property);
            }
            return property;
        }
 
        #endregion
 
        #region Settings
 
        // Unchanging, initialized in ctor
        private readonly XamlSchemaContextSettings _settings;
 
        public bool SupportMarkupExtensionsWithDuplicateArity
        {
            get { return _settings.SupportMarkupExtensionsWithDuplicateArity; }
        }
 
        public bool FullyQualifyAssemblyNamesInClrNamespaces
        {
            get { return _settings.FullyQualifyAssemblyNamesInClrNamespaces; }
        }
 
        public IList<Assembly> ReferenceAssemblies
        {
            get { return _referenceAssemblies; }
        }
 
        #endregion
 
        #region Namespace Mapping and Assembly Attribute (XmlNsInfo) caches
 
        // Lazy init, access these fields through the properties.
        private ConcurrentDictionary<String, XamlNamespace> _namespaceByUriList;
        private ConcurrentDictionary<Assembly, XmlNsInfo> _xmlnsInfo;
        private ConcurrentDictionary<WeakRefKey, XmlNsInfo> _xmlnsInfoForDynamicAssemblies;
        private ConcurrentDictionary<Assembly, XmlNsInfo> _xmlnsInfoForUnreferencedAssemblies;
 
        // immutable, initialized in ctor
        AssemblyLoadHandler _assemblyLoadHandler;
 
        // tracks new assemblies seen by the AssemblyLoad handler, but not yet reflected
        private IList<Assembly> _unexaminedAssemblies;
        bool _isGCCallbackPending;
 
        // take this lock when modifying _unexaminedAssemblies or _isGCCallbackPending
        // Acquisition order: If also taking _syncExaminingAssemblies, take it first
        object _syncAccessingUnexaminedAssemblies;
 
        // This dictionary is also thread-safe for single reads and writes, but if you're
        // iterating them, lock on _syncExaminingAssemblies to ensure consistent results
        private ConcurrentDictionary<Assembly, XmlNsInfo> XmlnsInfo
        {
            get
            {
                if (_xmlnsInfo is null)
                    Interlocked.CompareExchange(ref _xmlnsInfo, CreateDictionary<Assembly, XmlNsInfo>(ReferenceEqualityComparer.Instance), null);
                return _xmlnsInfo;
            }
        }
 
        // Same thread-safety as XmlnsInfo
        private ConcurrentDictionary<WeakRefKey, XmlNsInfo> XmlnsInfoForDynamicAssemblies
        {
            get
            {
                if (_xmlnsInfoForDynamicAssemblies is null)
                    Interlocked.CompareExchange(ref _xmlnsInfoForDynamicAssemblies, CreateDictionary<WeakRefKey, XmlNsInfo>(), null);
                return _xmlnsInfoForDynamicAssemblies;
            }
        }
 
        // This dictionary is also thread-safe for single reads and writes, but if you're
        // iterating them, lock on _syncExaminingAssemblies to ensure consistent results
        private ConcurrentDictionary<String, XamlNamespace> NamespaceByUriList
        {
            get
            {
                if (_namespaceByUriList is null)
                    Interlocked.CompareExchange(ref _namespaceByUriList,  CreateDictionary<string, XamlNamespace>(), null);
                return _namespaceByUriList;
            }
        }
 
        // The dictionary is used for storing xmlnsInfo for assemblies that are not 'referenced',
        // by that, we mean unreferenced assemblies when the schema context is passed in a set of referenced assemblies,
        // or assemblies not loaded in the app-domain when the schema context is not passed in a set of assemblies.
        // The dictionary is thread-safe for single reads and writes, and has no requirement for iteration.
        private ConcurrentDictionary<Assembly, XmlNsInfo> XmlnsInfoForUnreferencedAssemblies
        {
            get
            {
                if (_xmlnsInfoForUnreferencedAssemblies is null)
                {
                    Interlocked.CompareExchange(ref _xmlnsInfoForUnreferencedAssemblies, CreateDictionary<Assembly, XmlNsInfo>(ReferenceEqualityComparer.Instance), null);
                }
                return _xmlnsInfoForUnreferencedAssemblies;
            }
        }
 
        internal bool AreInternalsVisibleTo(Assembly fromAssembly, Assembly toAssembly)
        {
            if (fromAssembly.Equals(toAssembly))
            {
                return true;
            }
            XmlNsInfo nsInfo = GetXmlNsInfo(fromAssembly);
            ICollection<AssemblyName> friends = nsInfo.InternalsVisibleTo;
            if (friends.Count == 0)
            {
                return false;
            }
            // Not using Assembly.GetName() because it doesn't work in partial-trust
            AssemblyName toAssemblyName = new AssemblyName(toAssembly.FullName);
            foreach (AssemblyName friend in friends)
            {
                if (friend.Name == toAssemblyName.Name)
                {
                    byte[] expectedToken = friend.GetPublicKeyToken();
                    if (expectedToken is null)
                    {
                        // InternalsVisibleToAttribute doesn't specify a public key, so don't check it
                        return true;
                    }
                    byte[] actualToken = toAssemblyName.GetPublicKeyToken();
                    return SafeSecurityHelper.IsSameKeyToken(expectedToken, actualToken);
                }
            }
            return false;
        }
 
        // Entry point for GC callback. We indirect this through a static method that takes a weakref,
        // so that the callback doesn't keep the SchemaContext alive
        private static void CleanupCollectedAssemblies(object schemaContextWeakRef)
        {
            WeakReference weakRef = (WeakReference)schemaContextWeakRef;
            XamlSchemaContext schemaContext = weakRef.Target as XamlSchemaContext;
            if (schemaContext is not null)
            {
                schemaContext.CleanupCollectedAssemblies();
            }
        }
 
        // Iterate through any weak references we hold to dynamic assemblies, cleaning up references
        // that have been collected. This prevents our caches from growing unboundedly in the case
        // where dynamic assemblies are continually getting created and disposed.
        private void CleanupCollectedAssemblies()
        {
            bool foundLiveDynamicAssemblies = false;
            lock (_syncAccessingUnexaminedAssemblies)
            {
                // setting _isGCCallbackPending inside the lock; see comment in RegisterAssemblyCleanup
                _isGCCallbackPending = false;
 
                if (_unexaminedAssemblies is WeakReferenceList<Assembly>)
                {
                    for (int i = _unexaminedAssemblies.Count - 1; i >= 0; i--)
                    {
                        Assembly assembly = _unexaminedAssemblies[i];
                        if (assembly is null)
                        {
                            _unexaminedAssemblies.RemoveAt(i);
                        }
                        else if (assembly.IsDynamic)
                        {
                            foundLiveDynamicAssemblies = true;
                        }
                    }
                }
            }
            lock (_syncExaminingAssemblies)
            {
                if (_xmlnsInfoForDynamicAssemblies is not null)
                {
                    foreach (WeakRefKey weakRefKey in _xmlnsInfoForDynamicAssemblies.Keys)
                    {
                        if (weakRefKey.IsAlive)
                        {
                            foundLiveDynamicAssemblies = true;
                        }
                        else
                        {
                            // ConcurrentDictionary returns a copy of its keys, so it's safe to delete while enumerating
                            _xmlnsInfoForDynamicAssemblies.TryRemove(weakRefKey, out _);
                        }
                    }
                }
            }
            if (foundLiveDynamicAssemblies)
            {
                RegisterAssemblyCleanup();
            }
        }
 
        private void RegisterAssemblyCleanup()
        {
            // Locking around this check prevents multiple threads from redundantly registering
            // callbacks at the same time.
            // We could use either the examining or unexamined lock, since clenaup touches both;
            // we use the unexamined because it has a shorter blocking time.
            lock (_syncAccessingUnexaminedAssemblies)
            {
                if (!_isGCCallbackPending)
                {
                    GCNotificationToken.RegisterCallback(CleanupCollectedAssemblies, new WeakReference(this));
                    _isGCCallbackPending = true;
                }
            }
        }
 
        private IEnumerable<XmlNsInfo> EnumerateXmlnsInfos()
        {
            if (_xmlnsInfoForDynamicAssemblies is null)
            {
                return XmlnsInfo.Values;
            }
            else
            {
                return EnumerateStaticAndDynamicXmlnsInfos();
            }
        }
 
        private IEnumerable<XmlNsInfo> EnumerateStaticAndDynamicXmlnsInfos()
        {
            foreach (XmlNsInfo result in XmlnsInfo.Values)
            {
                yield return result;
            }
            foreach (XmlNsInfo result in XmlnsInfoForDynamicAssemblies.Values)
            {
                yield return result;
            }
        }
 
        internal string GetRootNamespace(Assembly asm)
        {
            XmlNsInfo nsInfo = GetXmlNsInfo(asm);
            return nsInfo.RootNamespace;
        }
 
        internal ReadOnlyCollection<string> GetXamlNamespaces(XamlType type)
        {
            Type clrType = type.UnderlyingType;
            if (clrType is null || clrType.Assembly is null)
            {
                return null;
            }
 
            if (XamlLanguage.AllTypes.Contains(type))
            {
                // We need a read-only list which combines the directive namespace(s) with the
                // the namespaces(s) that this type supports through standard CLR binding rules
                IList<string> clrBoundNamespaces = GetXmlNsMappings(clrType.Assembly, clrType.Namespace);
                List<string> combinedList = new List<string>();
                combinedList.AddRange(XamlLanguage.XamlNamespaces);
                combinedList.AddRange(clrBoundNamespaces);
                return combinedList.AsReadOnly();
            }
            else
            {
                return GetXmlNsMappings(clrType.Assembly, clrType.Namespace);
            }
        }
 
        private XamlNamespace GetXamlNamespace(string xmlns)
        {
            XamlNamespace xamlNamespace = null;
 
            if (NamespaceByUriList.TryGetValue(xmlns, out xamlNamespace))
            {
                return xamlNamespace;
            }
 
            string clrNs, assemblyName;
            if (ClrNamespaceUriParser.TryParseUri(xmlns, out clrNs, out assemblyName))
            {
                xamlNamespace = new XamlNamespace(this, clrNs, assemblyName);
            }
            else
            {
                // unresolved namespace
                xamlNamespace = new XamlNamespace(this);
            }
            xamlNamespace = TryAdd(NamespaceByUriList, xmlns, xamlNamespace);
            return xamlNamespace;
        }
 
        private XmlNsInfo GetXmlNsInfo(Assembly assembly)
        {
            XmlNsInfo result;
 
            if (XmlnsInfo.TryGetValue(assembly, out result) ||
                (_xmlnsInfoForDynamicAssemblies is not null && assembly.IsDynamic &&
                 _xmlnsInfoForDynamicAssemblies.TryGetValue(new WeakRefKey(assembly), out result)) ||
                (_xmlnsInfoForUnreferencedAssemblies is not null && _xmlnsInfoForUnreferencedAssemblies.TryGetValue(assembly, out result)))
            {
                return result;
            }
 
            // We store XmlnsInfo in three separate caches.
            //
            // If there is a hard-coded list of reference assemblies, then:
            // 1. Referenced assemblies are in XmlnsInfo
            // 2. Everything else is in XmlnsInfoForUnreferencedAssemblies, so that it doesn't
            //    pollute the 'real' cache.
            //
            // If we are using all AppDomain-loaded assemblies, then:
            // 1. Static RuntimeAssemblies are in XmlnsInfo
            // 2. Dynamic RuntimeAssemblies are in XmlnsInfoForDynamicAssemblies, so that
            //    collectible assemblies are weakrefed.
            // 3. ReflectionOnly assemblies and custom derivations of System.Assembly are in
            //    XmlnsInfoForUnreferencedAssemblies, so that they don't pollute the 'real' cache.
            bool isReferenced = false;
            if (_referenceAssemblies is not null)
            {
                foreach (var asm in _referenceAssemblies)
                {
                    if (ReferenceEquals(asm, assembly))
                    {
                        isReferenced = true;
                        break;
                    }
                }
            }
            else
            {
                isReferenced = !assembly.ReflectionOnly &&
                    typeof(object).Assembly.GetType().IsAssignableFrom(assembly.GetType());
            }
 
            // Add the assembly to the cache
            result = new XmlNsInfo(assembly, FullyQualifyAssemblyNamesInClrNamespaces);
            if (isReferenced)
            {
                if (assembly.IsDynamic && _referenceAssemblies is null)
                {
                    result = TryAdd(XmlnsInfoForDynamicAssemblies, new WeakRefKey(assembly), result);
                    // Ensure we clean up the cache if dynamic assemblies are collected
                    RegisterAssemblyCleanup();
                }
                else
                {
                    result = TryAdd(XmlnsInfo, assembly, result);
                }
            }
            else
            {
                result = TryAdd(XmlnsInfoForUnreferencedAssemblies, assembly, result);
            }
 
            return result;
        }
 
        private ReadOnlyCollection<string> GetXmlNsMappings(Assembly assembly, string clrNs)
        {
            XmlNsInfo nsInfo = GetXmlNsInfo(assembly);
            ConcurrentDictionary<string, IList<string>> assemblyMappings = nsInfo.ClrToXmlNs;
            IList<string> result;
 
            clrNs = clrNs ?? string.Empty;
 
            if (!assemblyMappings.TryGetValue(clrNs, out result))
            {
                string assemblyName = FullyQualifyAssemblyNamesInClrNamespaces ?
                    assembly.FullName : GetAssemblyShortName(assembly);
                string xmlns = ClrNamespaceUriParser.GetUri(clrNs, assemblyName);
                List<string> list = new List<string>();
                list.Add(xmlns);
                result = list.AsReadOnly();
                TryAdd(assemblyMappings, clrNs, result);
            }
            return (ReadOnlyCollection<string>)result;
        }
 
        private void InitializeAssemblyLoadHook()
        {
            _syncAccessingUnexaminedAssemblies = new Object();
            if (ReferenceAssemblies is null)
            {
 
                _assemblyLoadHandler = new AssemblyLoadHandler(this);
                _assemblyLoadHandler.Hook();
                lock (_syncAccessingUnexaminedAssemblies)
                {
                    Assembly[] currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
                    _unexaminedAssemblies = new WeakReferenceList<Assembly>(currentAssemblies.Length);
                    bool foundDynamic = false;
                    foreach (Assembly assembly in currentAssemblies)
                    {
                        _unexaminedAssemblies.Add(assembly);
                        if (assembly.IsDynamic)
                        {
                            foundDynamic = true;
                        }
                    }
                    if (foundDynamic)
                    {
                        // Ensure we clean up the cache if dynamic assemblies are collected
                        RegisterAssemblyCleanup();
                    }
                }
            }
            else
            {
                _unexaminedAssemblies = new List<Assembly>(ReferenceAssemblies);
            }
        }
 
        void SchemaContextAssemblyLoadEventHandler(object sender, AssemblyLoadEventArgs args)
        {
            lock (_syncAccessingUnexaminedAssemblies)
            {
                if (!args.LoadedAssembly.ReflectionOnly && !_unexaminedAssemblies.Contains(args.LoadedAssembly))
                {
                    _unexaminedAssemblies.Add(args.LoadedAssembly);
                    if (args.LoadedAssembly.IsDynamic)
                    {
                        // Ensure we clean up the cache if a dynamic assemblies is collected
                        RegisterAssemblyCleanup();
                    }
                }
            }
        }
 
        // When this method returns:
        // - _xmlnsInfo contains all assemblies in the AppDomain or reference assemblies
        // - NamespaceByUriList contains all xmlnsdefs in _xmlnsInfo
        private void UpdateXmlNsInfo()
        {
            bool foundNew = false;
            lock (_syncExaminingAssemblies)
            {
                IList<Assembly> unexaminedAssembliesCopy;
                lock (_syncAccessingUnexaminedAssemblies)
                {
                    unexaminedAssembliesCopy = _unexaminedAssemblies;
                    _unexaminedAssemblies = new WeakReferenceList<Assembly>(0);
                }
 
                // If we're trawling thorugh all assemblies in the AppDomain, then we'll ignore
                //  any exceptions from invalid attributes
                bool throwOnError = (ReferenceAssemblies is not null);
                for (int i = 0; i < unexaminedAssembliesCopy.Count; i++)
                {
                    var assembly = unexaminedAssembliesCopy[i];
                    if (assembly is null)
                    {
                        // The assembly is a collectible dynamic assembly, and has been GC'ed. Ignore it.
                        continue;
                    }
                    XmlNsInfo nsInfo = GetXmlNsInfo(assembly);
                    bool foundNewInThisAssembly = false;
                    try
                    {
                        foundNewInThisAssembly = UpdateXmlNsInfo(nsInfo);
                        if (foundNewInThisAssembly)
                        {
                            foundNew = true;
                        }
                    }
                    catch (Exception ex)
                    {
                        if (throwOnError || CriticalExceptions.IsCriticalException(ex))
                        {
                            // The assemblies after the i'th one in unexaminedAssembliesCopy have not been examined (including the i'th assembly).
                            // So we need to add them back to _unexaminedAssemblies while also keeping the assemblies that might have been added
                            // to _unexaminedAssemblies in parallel.
                            lock (_syncAccessingUnexaminedAssemblies)
                            {
                                for (int j = i; j < unexaminedAssembliesCopy.Count; j++)
                                {
                                    _unexaminedAssemblies.Add(unexaminedAssembliesCopy[j]);
                                }
                            }
                            throw;
                        }
                    }
                }
 
                if (foundNew && _nonClrNamespaces is not null)
                {
                    // invalidate this and force it to be re-evaluated
                    _nonClrNamespaces = null;
                }
            }
        }
 
        // This method should be called inside _syncExaminingAssemblies lock
        private bool UpdateXmlNsInfo(XmlNsInfo nsInfo)
        {
            bool foundNew = UpdateNamespaceByUriList(nsInfo);
            if (_preferredPrefixes is not null)
            {
                UpdatePreferredPrefixes(nsInfo, _preferredPrefixes);
            }
            return foundNew;
        }
 
        bool UpdateNamespaceByUriList(XmlNsInfo nsInfo)
        {
            bool foundNew = false;
            IList<XmlNsInfo.XmlNsDefinition> xmlnsDefs = nsInfo.NsDefs;
            int xmlnsDefsCount = xmlnsDefs.Count;
            for (int i = 0; i < xmlnsDefsCount; i++)
            {
                XmlNsInfo.XmlNsDefinition xmlnsDef = xmlnsDefs[i];
                AssemblyNamespacePair pair = new AssemblyNamespacePair(nsInfo.Assembly, xmlnsDef.ClrNamespace);
                XamlNamespace ns = GetXamlNamespace(xmlnsDef.XmlNamespace);
                ns.AddAssemblyNamespacePair(pair);
                foundNew = true;
            }
            return foundNew;
        }
 
        #endregion
 
        #region Helper Methods
 
        // Given an assembly, return the assembly short name.  We need to avoid Assembly.GetName() so we run in PartialTrust without asserting.
        internal static string GetAssemblyShortName(Assembly assembly)
        {
            string assemblyLongName = assembly.FullName;
            string assemblyShortName = assemblyLongName.Substring(0, assemblyLongName.IndexOf(','));
            return assemblyShortName;
        }
 
        internal static ConcurrentDictionary<K, V> CreateDictionary<K, V>()
        {
            return new ConcurrentDictionary<K, V>(ConcurrencyLevel, DictionaryCapacity);
        }
 
        internal static ConcurrentDictionary<K, V> CreateDictionary<K, V>(IEqualityComparer<K> comparer)
        {
            return new ConcurrentDictionary<K, V>(ConcurrencyLevel, DictionaryCapacity, comparer);
        }
 
        internal static V TryAdd<K, V>(ConcurrentDictionary<K, V> dictionary, K key, V value)
        {
            if (dictionary.TryAdd(key, value))
            {
                return value;
            }
            else
            {
                return dictionary[key];
            }
        }
 
        internal static V TryUpdate<K, V>(ConcurrentDictionary<K, V> dictionary, K key, V value, V comparand)
        {
            if (dictionary.TryUpdate(key, value, comparand))
            {
                return value;
            }
            else
            {
                return dictionary[key];
            }
        }
 
        #endregion
 
        #region Assembly resolution
 
        // Both the array itself and each item in it are lazily initialized.
        // The indexes should match _referenceAssemblies
        private AssemblyName[] _referenceAssemblyNames;
 
        protected internal virtual Assembly OnAssemblyResolve(string assemblyName)
        {
            if (String.IsNullOrEmpty(assemblyName))
            {
                return null;
            }
            if (_referenceAssemblies is not null)
            {
                return ResolveReferenceAssembly(assemblyName);
            }
            else
            {
                return ResolveAssembly(assemblyName);
            }
        }
 
        private Assembly ResolveReferenceAssembly(string assemblyName)
        {
            AssemblyName parsedAsmName = new AssemblyName(assemblyName);
            if (_referenceAssemblyNames is null)
            {
                AssemblyName[] asmNames = new AssemblyName[_referenceAssemblies.Count];
                Interlocked.CompareExchange(ref _referenceAssemblyNames, asmNames, null);
            }
            for (int i = 0; i < _referenceAssemblies.Count; i++)
            {
                AssemblyName refAsmName = _referenceAssemblyNames[i];
                if (_referenceAssemblyNames[i] is null)
                {
                    // Multiple threads may be simultaneously populating this array.
                    // That's okay; we're inserting identical data, so duplicate writes are harmless.
                    _referenceAssemblyNames[i] = new AssemblyName(_referenceAssemblies[i].FullName);
                }
                if (AssemblySatisfiesReference(_referenceAssemblyNames[i], parsedAsmName))
                {
                    return _referenceAssemblies[i];
                }
            }
            return null;
        }
 
        private static bool AssemblySatisfiesReference(AssemblyName assemblyName, AssemblyName reference)
        {
            if (reference.Name != assemblyName.Name)
            {
                return false;
            }
            if (reference.Version is not null && !reference.Version.Equals(assemblyName.Version))
            {
                return false;
            }
            if (reference.CultureInfo is not null && !reference.CultureInfo.Equals(assemblyName.CultureInfo))
            {
                return false;
            }
            byte[] requiredToken = reference.GetPublicKeyToken();
            if (requiredToken is not null)
            {
                byte[] actualToken = assemblyName.GetPublicKeyToken();
                if (!SafeSecurityHelper.IsSameKeyToken(requiredToken, actualToken))
                {
                    return false;
                }
            }
            return true;
        }
 
        private Assembly ResolveAssembly(string assemblyName)
        {
            // First see if the assembly is already loaded. This is necessary because Assembly.Load
            // won't match to assemblies in the LoadFile or LoadFrom contexts.
            // We only allow exact matches at this point. Version-tolerant matching happens
            // below, using Assembly.Load.
            AssemblyName parsedAsmName = new AssemblyName(assemblyName);
            Assembly result = SafeSecurityHelper.GetLoadedAssembly(parsedAsmName);
            if (result is not null)
            {
                return result;
            }
 
            try
            {
                byte[] publicKeyToken = parsedAsmName.GetPublicKeyToken();
                if (parsedAsmName.Version is not null || parsedAsmName.CultureInfo is not null || publicKeyToken is not null)
                {
                    try
                    {
                        // First try to load the exact requested version.
                        // This will throw if fusion can't find the assembly.
                        return Assembly.Load(assemblyName);
                    }
                    catch (Exception ex)
                    {
                        if (CriticalExceptions.IsCriticalException(ex))
                        {
                            throw;
                        }
                        // Version tolerance: fall back to the short name (+ public key, if specified)
                        AssemblyName shortName = new AssemblyName(parsedAsmName.Name);
                        if (publicKeyToken is not null)
                        {
                            shortName.SetPublicKeyToken(publicKeyToken);
                        }
                        return Assembly.Load(shortName);
                    }
                }
                else
                {
                    // Use LWPN because Load won't look in the GAC if it's given a short name.
                    return Assembly.LoadWithPartialName(assemblyName);
                }
            }
            catch (Exception ex)
            {
                if (CriticalExceptions.IsCriticalException(ex))
                {
                    throw;
                }
                // We don't want to throw if the assembly can't be found, we just treat it as unresolved
                return null;
            }
        }
 
        #endregion
 
        // WeakRef wrapper around XSC so that it can hook AppDomain event without getting rooted
        private class AssemblyLoadHandler
        {
            WeakReference schemaContextRef;
 
            public AssemblyLoadHandler(XamlSchemaContext schemaContext)
            {
                schemaContextRef = new WeakReference(schemaContext);
            }
 
            private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
            {
                XamlSchemaContext schemaContext = (XamlSchemaContext)schemaContextRef.Target;
                if (schemaContext is not null)
                {
                    schemaContext.SchemaContextAssemblyLoadEventHandler(sender, args);
                }
            }
 
            public void Hook()
            {
                AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
            }
 
            public void Unhook()
            {
                AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
            }
        }
 
        private class WeakReferenceList<T> : List<WeakReference>, IList<T> where T : class
        {
            public WeakReferenceList(int capacity)
                : base(capacity)
            {
            }
 
            int IList<T>.IndexOf(T item)
            {
                throw new NotSupportedException();
            }
 
            void IList<T>.Insert(int index, T item)
            {
                Insert(index, new WeakReference(item));
            }
 
            T IList<T>.this[int index]
            {
                get
                {
                    return (T)this[index].Target;
                }
                set
                {
                    this[index] = new WeakReference(value);
                }
            }
 
            void ICollection<T>.Add(T item)
            {
                Add(new WeakReference(item));
            }
 
            bool ICollection<T>.Contains(T item)
            {
                foreach (WeakReference weakRef in (IEnumerable<WeakReference>)this)
                {
                    if ((object)item == weakRef.Target)
                    {
                        return true;
                    }
                }
                return false;
            }
 
            void ICollection<T>.CopyTo(T[] array, int arrayIndex)
            {
                for (int i = 0; i < Count; i++)
                {
                    array[i + arrayIndex] = (T)this[i].Target;
                }
            }
 
            bool ICollection<T>.Remove(T item)
            {
                throw new NotSupportedException();
            }
 
            bool ICollection<T>.IsReadOnly
            {
                get { return false; }
            }
 
            IEnumerator<T> IEnumerable<T>.GetEnumerator()
            {
                return Enumerate().GetEnumerator();
            }
 
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<T>)this).GetEnumerator();
            }
 
            private IEnumerable<T> Enumerate()
            {
                foreach (WeakReference weakRef in (IEnumerable<WeakReference>)this)
                {
                    yield return (T)weakRef.Target;
                }
            }
        }
    }
}