File: System\Xml\Linq\XNamespace.cs
Web Access
Project: src\src\libraries\System.Private.Xml.Linq\src\System.Private.Xml.Linq.csproj (System.Private.Xml.Linq)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using Debug = System.Diagnostics.Debug;
using Interlocked = System.Threading.Interlocked;
using SuppressMessageAttribute = System.Diagnostics.CodeAnalysis.SuppressMessageAttribute;
 
namespace System.Xml.Linq
{
    /// <summary>
    /// Represents an XML namespace. This class cannot be inherited.
    /// </summary>
    public sealed class XNamespace
    {
        internal const string xmlPrefixNamespace = "http://www.w3.org/XML/1998/namespace";
        internal const string xmlnsPrefixNamespace = "http://www.w3.org/2000/xmlns/";
 
        private static XHashtable<WeakReference<XNamespace>>? s_namespaces;
        private static WeakReference<XNamespace>? s_refNone;
        private static WeakReference<XNamespace>? s_refXml;
        private static WeakReference<XNamespace>? s_refXmlns;
 
        private readonly string _namespaceName;
        private readonly int _hashCode;
        private readonly XHashtable<XName> _names;
 
        private const int NamesCapacity = 8;           // Starting capacity of XName table, which must be power of 2
        private const int NamespacesCapacity = 32;     // Starting capacity of XNamespace table, which must be power of 2
 
        /// <summary>
        /// Constructor, internal so that external users must go through the Get() method to create an XNamespace.
        /// </summary>
        internal XNamespace(string namespaceName)
        {
            _namespaceName = namespaceName;
            _hashCode = namespaceName.GetHashCode();
            _names = new XHashtable<XName>(ExtractLocalName, NamesCapacity);
        }
 
        /// <summary>
        /// Gets the namespace name of the namespace.
        /// </summary>
        public string NamespaceName
        {
            get { return _namespaceName; }
        }
 
        /// <summary>
        /// Returns an <see cref="XName"/> object created from the current instance and the specified local name.
        /// </summary>
        /// <remarks>
        /// The returned <see cref="XName"/> object is guaranteed to be atomic (i.e. the only one in the system for this
        /// particular expanded name).
        /// </remarks>
        public XName GetName(string localName)
        {
            ArgumentNullException.ThrowIfNull(localName);
 
            return GetName(localName, 0, localName.Length);
        }
 
        /// <summary>
        /// Returns the namespace name of this <see cref="XNamespace"/>.
        /// </summary>
        /// <returns>A string value containing the namespace name.</returns>
        public override string ToString()
        {
            return _namespaceName;
        }
 
        /// <summary>
        /// Gets the <see cref="XNamespace"/> object that corresponds to no namespace.
        /// </summary>
        /// <remarks>
        /// If an element or attribute is in no namespace, its namespace
        /// will be set to the namespace returned by this property.
        /// </remarks>
        public static XNamespace None
        {
            get
            {
                return EnsureNamespace(ref s_refNone, string.Empty);
            }
        }
 
        /// <summary>
        /// Gets the <see cref="XNamespace"/> object that corresponds to the xml uri (http://www.w3.org/XML/1998/namespace).
        /// </summary>
        public static XNamespace Xml
        {
            get
            {
                return EnsureNamespace(ref s_refXml, xmlPrefixNamespace);
            }
        }
 
        /// <summary>
        /// Gets the <see cref="XNamespace"/> object that corresponds to the xmlns uri (http://www.w3.org/2000/xmlns/).
        /// </summary>
        public static XNamespace Xmlns
        {
            get
            {
                return EnsureNamespace(ref s_refXmlns, xmlnsPrefixNamespace);
            }
        }
 
        /// <summary>
        /// Gets an <see cref="XNamespace"/> created from the specified namespace name.
        /// </summary>
        /// <remarks>
        /// The returned <see cref="XNamespace"/> object is guaranteed to be atomic
        /// (i.e. the only one in the system for that particular namespace name).
        /// </remarks>
        public static XNamespace Get(string namespaceName)
        {
            ArgumentNullException.ThrowIfNull(namespaceName);
 
            return Get(namespaceName, 0, namespaceName.Length);
        }
 
        /// <summary>
        /// Converts a string containing a namespace name to an <see cref="XNamespace"/>.
        /// </summary>
        /// <param name="namespaceName">A string containing the namespace name.</param>
        /// <returns>An <see cref="XNamespace"/> constructed from the namespace name string.</returns>
        [CLSCompliant(false)]
        [return: NotNullIfNotNull(nameof(namespaceName))]
        public static implicit operator XNamespace?(string? namespaceName)
        {
            return namespaceName != null ? Get(namespaceName) : null;
        }
 
        /// <summary>
        /// Combines an <see cref="XNamespace"/> object with a local name to create an <see cref="XName"/>.
        /// </summary>
        /// <param name="ns">The namespace for the expanded name.</param>
        /// <param name="localName">The local name for the expanded name.</param>
        /// <returns>The new XName constructed from the namespace and local name.</returns>
        public static XName operator +(XNamespace ns, string localName)
        {
            ArgumentNullException.ThrowIfNull(ns);
 
            return ns.GetName(localName);
        }
 
        /// <summary>
        /// Determines whether the specified <see cref="XNamespace"/> is equal to the current <see cref="XNamespace"/>.
        /// </summary>
        /// <param name="obj">The <see cref="XNamespace"/> to compare to the current <see cref="XNamespace"/>.</param>
        /// <returns>
        /// true if the specified <see cref="XNamespace"/> is equal to the current <see cref="XNamespace"/>; otherwise false.
        /// </returns>
        /// <remarks>
        /// For two <see cref="XNamespace"/> objects to be equal they must have the same
        /// namespace name.
        /// </remarks>
        public override bool Equals([NotNullWhen(true)] object? obj)
        {
            return (object)this == obj;
        }
 
        /// <summary>
        /// Serves as a hash function for <see cref="XNamespace"/>. GetHashCode is suitable
        /// for use in hashing algorithms and data structures like a hash table.
        /// </summary>
        public override int GetHashCode()
        {
            return _hashCode;
        }
 
 
        // The overloads of == and != are included to enable comparisons between
        // XNamespace and string (e.g. element.Name.Namespace == "foo"). C#'s
        // predefined reference equality operators require one operand to be
        // convertible to the type of the other through reference conversions only
        // and do not consider the implicit conversion from string to XNamespace.
 
        /// <summary>
        /// Returns a value indicating whether two instances of <see cref="XNamespace"/> are equal.
        /// </summary>
        /// <param name="left">The first <see cref="XNamespace"/> to compare.</param>
        /// <param name="right">The second <see cref="XNamespace"/> to compare.</param>
        /// <returns>true if left and right are equal; otherwise false.</returns>
        /// <remarks>
        /// This overload is included to enable the comparison between
        /// an instance of <see cref="XNamespace"/> and string.
        /// </remarks>
        public static bool operator ==(XNamespace? left, XNamespace? right)
        {
            return (object?)left == (object?)right;
        }
 
        /// <summary>
        /// Returns a value indicating whether two instances of <see cref="XNamespace"/> are not equal.
        /// </summary>
        /// <param name="left">The first <see cref="XNamespace"/> to compare.</param>
        /// <param name="right">The second <see cref="XNamespace"/> to compare.</param>
        /// <returns>true if left and right are not equal; otherwise false.</returns>
        /// <remarks>
        /// This overload is included to enable the comparison between
        /// an instance of <see cref="XNamespace"/> and string.
        /// </remarks>
        public static bool operator !=(XNamespace? left, XNamespace? right)
        {
            return (object?)left != (object?)right;
        }
 
        /// <summary>
        /// Returns an <see cref="XName"/> created from this XNamespace <see cref="XName"/> and a portion of the passed in
        /// local name parameter.  The returned <see cref="XName"/> object is guaranteed to be atomic (i.e. the only one in the system for
        /// this particular expanded name).
        /// </summary>
        internal XName GetName(string localName, int index, int count)
        {
            Debug.Assert(index >= 0 && index <= localName.Length, "Caller should have checked that index was in bounds");
            Debug.Assert(count >= 0 && index + count <= localName.Length, "Caller should have checked that count was in bounds");
 
            // Attempt to get the local name from the hash table
            XName? name;
            if (_names.TryGetValue(localName, index, count, out name))
                return name;
 
            // No local name has yet been added, so add it now
            return _names.Add(new XName(this, localName.Substring(index, count)));
        }
 
        /// <summary>
        /// Returns an <see cref="XNamespace"/> created from a portion of the passed in namespace name parameter.  The returned <see cref="XNamespace"/>
        /// object is guaranteed to be atomic (i.e. the only one in the system for this particular namespace name).
        /// </summary>
        internal static XNamespace Get(string namespaceName, int index, int count)
        {
            Debug.Assert(index >= 0 && index <= namespaceName.Length, "Caller should have checked that index was in bounds");
            Debug.Assert(count >= 0 && index + count <= namespaceName.Length, "Caller should have checked that count was in bounds");
 
            if (count == 0) return None;
 
            // Use CompareExchange to ensure that exactly one XHashtable<WeakReference> is used to store namespaces
            if (s_namespaces == null)
                Interlocked.CompareExchange(ref s_namespaces, new XHashtable<WeakReference<XNamespace>>(ExtractNamespace, NamespacesCapacity), null);
 
            WeakReference<XNamespace>? refNamespace;
            XNamespace? ns;
 
            // Keep looping until a non-null namespace has been retrieved
            do
            {
                // Attempt to get the WeakReference for the namespace from the hash table
                if (!s_namespaces.TryGetValue(namespaceName, index, count, out refNamespace))
                {
                    // If it is not there, first determine whether it's a special namespace
                    if (count == xmlPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlPrefixNamespace, 0, count) == 0) return Xml;
                    if (count == xmlnsPrefixNamespace.Length && string.CompareOrdinal(namespaceName, index, xmlnsPrefixNamespace, 0, count) == 0) return Xmlns;
 
                    // Go ahead and create the namespace and add it to the table
                    refNamespace = s_namespaces.Add(new WeakReference<XNamespace>(new XNamespace(namespaceName.Substring(index, count))));
                }
 
                ns = refNamespace != null && refNamespace.TryGetTarget(out XNamespace? target) ? target : null;
            }
            while (ns == null);
 
            return ns;
        }
 
        /// <summary>
        /// This function is used by the <see cref="XHashtable{XName}"/> to extract the local name part from an <see cref="XName"/>.  The hash table
        /// uses the local name as the hash key.
        /// </summary>
        private static string ExtractLocalName(XName n)
        {
            Debug.Assert(n != null, "Null name should never exist here");
            return n.LocalName;
        }
 
        /// <summary>
        /// This function is used to extract the XNamespace that the WeakReference is referencing.
        /// In cases where the XNamespace has been cleaned up, this function returns null.
        /// </summary>
        private static string? ExtractNamespace(WeakReference<XNamespace>? r) =>
            r is not null &&
            r.TryGetTarget(out XNamespace? target) ? target.NamespaceName : null;
 
        /// <summary>
        /// Ensure that an XNamespace object for 'namespaceName' has been atomically created.  In other words, all outstanding
        /// references to this particular namespace, on any thread, must all be to the same object.  Care must be taken,
        /// since other threads can be concurrently calling this method, and the target of a WeakReference can be cleaned up
        /// at any time by the GC.
        /// </summary>
        private static XNamespace EnsureNamespace(ref WeakReference<XNamespace>? refNmsp, string namespaceName)
        {
            WeakReference<XNamespace>? refOld;
 
            // Keep looping until a non-null namespace has been retrieved
            while (true)
            {
                // Save refNmsp in local variable, so we can work on a value that will not be changed by another thread
                refOld = refNmsp;
 
                if (refOld != null && refOld.TryGetTarget(out XNamespace? ns))
                {
                    // If the target of the WeakReference is non-null, then we're done--just return the value
                    return ns;
                }
 
                // Either refNmsp is null, or its target is null, so update it
                // Make sure to do this atomically, so that we can guarantee atomicity of XNamespace objects
                Interlocked.CompareExchange(ref refNmsp, new WeakReference<XNamespace>(new XNamespace(namespaceName)), refOld);
            }
        }
    }
}