File: src\libraries\System.Private.CoreLib\src\System\Resources\ResourceSet.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
 
namespace System.Resources
{
    // A ResourceSet stores all the resources defined in one particular CultureInfo.
    //
    // The method used to load resources is straightforward - this class
    // enumerates over an IResourceReader, loading every name and value, and
    // stores them in a hash table.  Custom IResourceReaders can be used.
    //
    public class ResourceSet : IDisposable, IEnumerable
    {
        protected IResourceReader? Reader; // The field is protected for .NET Framework compatibility
 
        private Dictionary<object, object?>? _table;
        private Dictionary<string, object?>? _caseInsensitiveTable;  // For case-insensitive lookups.
 
        protected ResourceSet()
        {
            // To not inconvenience people subclassing us, we should allocate a new
            // hashtable here just so that Table is set to something.
            _table = new Dictionary<object, object?>();
        }
 
        // For RuntimeResourceSet, ignore the Table parameter - it's a wasted
        // allocation.
        internal ResourceSet(bool _)
        {
        }
 
        // Creates a ResourceSet using the system default ResourceReader
        // implementation.  Use this constructor to open & read from a file
        // on disk.
        //
        public ResourceSet(string fileName)
            : this()
        {
            Reader = new ResourceReader(fileName);
            ReadResources();
        }
 
        // Creates a ResourceSet using the system default ResourceReader
        // implementation.  Use this constructor to read from an open stream
        // of data.
        //
        public ResourceSet(Stream stream)
            : this()
        {
            Reader = new ResourceReader(stream);
            ReadResources();
        }
 
        public ResourceSet(IResourceReader reader)
            : this()
        {
            ArgumentNullException.ThrowIfNull(reader);
 
            Reader = reader;
            ReadResources();
        }
 
        // Closes and releases any resources used by this ResourceSet, if any.
        // All calls to methods on the ResourceSet after a call to close may
        // fail.  Close is guaranteed to be safely callable multiple times on a
        // particular ResourceSet, and all subclasses must support these semantics.
        public virtual void Close()
        {
            Dispose(true);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Close the Reader in a thread-safe way.
                IResourceReader? copyOfReader = Reader;
                Reader = null!;
                copyOfReader?.Close();
            }
            Reader = null!;
            _caseInsensitiveTable = null;
            _table = null;
        }
 
        public void Dispose()
        {
            Dispose(true);
        }
 
        // Returns the preferred IResourceReader class for this kind of ResourceSet.
        // Subclasses of ResourceSet using their own Readers &; should override
        // GetDefaultReader and GetDefaultWriter.
        public virtual Type GetDefaultReader()
        {
            return typeof(ResourceReader);
        }
 
        // Returns the preferred IResourceWriter class for this kind of ResourceSet.
        // Subclasses of ResourceSet using their own Readers &; should override
        // GetDefaultReader and GetDefaultWriter.
        public virtual Type GetDefaultWriter()
        {
            return Type.GetType("System.Resources.ResourceWriter, System.Resources.Writer", throwOnError: true)!;
        }
 
        public virtual IDictionaryEnumerator GetEnumerator()
        {
            return GetEnumeratorHelper();
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumeratorHelper();
        }
 
        private IDictionaryEnumerator GetEnumeratorHelper()
        {
            Dictionary<object, object?> table = _table ?? throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);  // Avoid a race with Dispose
 
            // Use IDictionary.GetEnumerator() for backward compatibility. Callers expect the enumerator to return DictionaryEntry instances.
            return ((IDictionary)table).GetEnumerator();
        }
 
        // Look up a string value for a resource given its name.
        //
        public virtual string? GetString(string name)
        {
            object? obj = GetObjectInternal(name);
            if (obj is string s)
                return s;
 
            if (obj is null)
                return null;
 
            throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
        }
 
        public virtual string? GetString(string name, bool ignoreCase)
        {
            // Case-sensitive lookup
            object? obj = GetObjectInternal(name);
            if (obj is string s)
                return s;
 
            if (obj is not null)
                throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
 
            if (!ignoreCase)
                return null;
 
            // Try doing a case-insensitive lookup
            obj = GetCaseInsensitiveObjectInternal(name);
            if (obj is string si)
                return si;
 
            if (obj is null)
                return null;
 
            throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
        }
 
        // Look up an object value for a resource given its name.
        //
        public virtual object? GetObject(string name)
        {
            return GetObjectInternal(name);
        }
 
        public virtual object? GetObject(string name, bool ignoreCase)
        {
            object? obj = GetObjectInternal(name);
 
            if (obj != null || !ignoreCase)
                return obj;
 
            return GetCaseInsensitiveObjectInternal(name);
        }
 
        protected virtual void ReadResources()
        {
            Debug.Assert(_table != null);
            Debug.Assert(Reader != null);
            IDictionaryEnumerator en = Reader.GetEnumerator();
            while (en.MoveNext())
            {
                _table.Add(en.Key, en.Value);
            }
            // While technically possible to close the Reader here, don't close it
            // to help with some WinRes lifetime issues.
        }
 
        private object? GetObjectInternal(string name)
        {
            ArgumentNullException.ThrowIfNull(name);
 
            Dictionary<object, object?> copyOfTable = _table ?? throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);  // Avoid a race with Dispose
 
            copyOfTable.TryGetValue(name, out object? value);
            return value;
        }
 
        private object? GetCaseInsensitiveObjectInternal(string name)
        {
            Dictionary<object, object?> copyOfTable = _table ?? throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);  // Avoid a race with Dispose
 
            Dictionary<string, object?>? caseTable = _caseInsensitiveTable;  // Avoid a race condition with Close
            if (caseTable == null)
            {
                caseTable = new Dictionary<string, object?>(copyOfTable.Count, StringComparer.OrdinalIgnoreCase);
                foreach (var item in copyOfTable)
                {
                    if (item.Key is not string s)
                        continue;
 
                    caseTable.Add(s, item.Value);
                }
                _caseInsensitiveTable = caseTable;
            }
 
            caseTable.TryGetValue(name, out object? value);
            return value;
        }
    }
}