ResourceSet.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. /*============================================================
  5. **
  6. **
  7. **
  8. **
  9. **
  10. ** Purpose: Culture-specific collection of resources.
  11. **
  12. **
  13. ===========================================================*/
  14. using System.Collections;
  15. using System.Diagnostics;
  16. using System.IO;
  17. using System.Reflection;
  18. namespace System.Resources
  19. {
  20. // A ResourceSet stores all the resources defined in one particular CultureInfo.
  21. //
  22. // The method used to load resources is straightforward - this class
  23. // enumerates over an IResourceReader, loading every name and value, and
  24. // stores them in a hash table. Custom IResourceReaders can be used.
  25. //
  26. public class ResourceSet : IDisposable, IEnumerable
  27. {
  28. protected IResourceReader Reader = null!;
  29. internal Hashtable? Table; // TODO-NULLABLE: Avoid nulling out in Dispose
  30. private Hashtable? _caseInsensitiveTable; // For case-insensitive lookups.
  31. protected ResourceSet()
  32. {
  33. // To not inconvenience people subclassing us, we should allocate a new
  34. // hashtable here just so that Table is set to something.
  35. Table = new Hashtable();
  36. }
  37. // For RuntimeResourceSet, ignore the Table parameter - it's a wasted
  38. // allocation.
  39. internal ResourceSet(bool junk)
  40. {
  41. }
  42. // Creates a ResourceSet using the system default ResourceReader
  43. // implementation. Use this constructor to open & read from a file
  44. // on disk.
  45. //
  46. public ResourceSet(string fileName)
  47. : this()
  48. {
  49. Reader = new ResourceReader(fileName);
  50. ReadResources();
  51. }
  52. // Creates a ResourceSet using the system default ResourceReader
  53. // implementation. Use this constructor to read from an open stream
  54. // of data.
  55. //
  56. public ResourceSet(Stream stream)
  57. : this()
  58. {
  59. Reader = new ResourceReader(stream);
  60. ReadResources();
  61. }
  62. public ResourceSet(IResourceReader reader)
  63. : this()
  64. {
  65. if (reader == null)
  66. throw new ArgumentNullException(nameof(reader));
  67. Reader = reader;
  68. ReadResources();
  69. }
  70. // Closes and releases any resources used by this ResourceSet, if any.
  71. // All calls to methods on the ResourceSet after a call to close may
  72. // fail. Close is guaranteed to be safely callable multiple times on a
  73. // particular ResourceSet, and all subclasses must support these semantics.
  74. public virtual void Close()
  75. {
  76. Dispose(true);
  77. }
  78. protected virtual void Dispose(bool disposing)
  79. {
  80. if (disposing)
  81. {
  82. // Close the Reader in a thread-safe way.
  83. IResourceReader? copyOfReader = Reader;
  84. Reader = null!; // TODO-NULLABLE: Avoid nulling out in Dispose
  85. if (copyOfReader != null)
  86. copyOfReader.Close();
  87. }
  88. Reader = null!; // TODO-NULLABLE: Avoid nulling out in Dispose
  89. _caseInsensitiveTable = null;
  90. Table = null;
  91. }
  92. public void Dispose()
  93. {
  94. Dispose(true);
  95. }
  96. // Returns the preferred IResourceReader class for this kind of ResourceSet.
  97. // Subclasses of ResourceSet using their own Readers &; should override
  98. // GetDefaultReader and GetDefaultWriter.
  99. public virtual Type GetDefaultReader()
  100. {
  101. return typeof(ResourceReader);
  102. }
  103. // Returns the preferred IResourceWriter class for this kind of ResourceSet.
  104. // Subclasses of ResourceSet using their own Readers &; should override
  105. // GetDefaultReader and GetDefaultWriter.
  106. public virtual Type GetDefaultWriter()
  107. {
  108. Assembly resourceWriterAssembly = Assembly.Load("System.Resources.Writer, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
  109. return resourceWriterAssembly.GetType("System.Resources.ResourceWriter", throwOnError: true)!;
  110. }
  111. public virtual IDictionaryEnumerator GetEnumerator()
  112. {
  113. return GetEnumeratorHelper();
  114. }
  115. IEnumerator IEnumerable.GetEnumerator()
  116. {
  117. return GetEnumeratorHelper();
  118. }
  119. private IDictionaryEnumerator GetEnumeratorHelper()
  120. {
  121. Hashtable? copyOfTable = Table; // Avoid a race with Dispose
  122. if (copyOfTable == null)
  123. throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
  124. return copyOfTable.GetEnumerator();
  125. }
  126. // Look up a string value for a resource given its name.
  127. //
  128. public virtual string? GetString(string name)
  129. {
  130. object? obj = GetObjectInternal(name);
  131. try
  132. {
  133. return (string?)obj;
  134. }
  135. catch (InvalidCastException)
  136. {
  137. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
  138. }
  139. }
  140. public virtual string? GetString(string name, bool ignoreCase)
  141. {
  142. object? obj;
  143. string? s;
  144. // Case-sensitive lookup
  145. obj = GetObjectInternal(name);
  146. try
  147. {
  148. s = (string?)obj;
  149. }
  150. catch (InvalidCastException)
  151. {
  152. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
  153. }
  154. // case-sensitive lookup succeeded
  155. if (s != null || !ignoreCase)
  156. {
  157. return s;
  158. }
  159. // Try doing a case-insensitive lookup
  160. obj = GetCaseInsensitiveObjectInternal(name);
  161. try
  162. {
  163. return (string?)obj;
  164. }
  165. catch (InvalidCastException)
  166. {
  167. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
  168. }
  169. }
  170. // Look up an object value for a resource given its name.
  171. //
  172. public virtual object? GetObject(string name)
  173. {
  174. return GetObjectInternal(name);
  175. }
  176. public virtual object? GetObject(string name, bool ignoreCase)
  177. {
  178. object? obj = GetObjectInternal(name);
  179. if (obj != null || !ignoreCase)
  180. return obj;
  181. return GetCaseInsensitiveObjectInternal(name);
  182. }
  183. protected virtual void ReadResources()
  184. {
  185. Debug.Assert(Table != null);
  186. Debug.Assert(Reader != null);
  187. IDictionaryEnumerator en = Reader.GetEnumerator();
  188. while (en.MoveNext())
  189. {
  190. object? value = en.Value;
  191. Table.Add(en.Key, value);
  192. }
  193. // While technically possible to close the Reader here, don't close it
  194. // to help with some WinRes lifetime issues.
  195. }
  196. private object? GetObjectInternal(string name)
  197. {
  198. if (name == null)
  199. throw new ArgumentNullException(nameof(name));
  200. Hashtable? copyOfTable = Table; // Avoid a race with Dispose
  201. if (copyOfTable == null)
  202. throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
  203. return copyOfTable[name];
  204. }
  205. private object? GetCaseInsensitiveObjectInternal(string name)
  206. {
  207. Hashtable? copyOfTable = Table; // Avoid a race with Dispose
  208. if (copyOfTable == null)
  209. throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
  210. Hashtable? caseTable = _caseInsensitiveTable; // Avoid a race condition with Close
  211. if (caseTable == null)
  212. {
  213. caseTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
  214. IDictionaryEnumerator en = copyOfTable.GetEnumerator();
  215. while (en.MoveNext())
  216. {
  217. caseTable.Add(en.Key, en.Value);
  218. }
  219. _caseInsensitiveTable = caseTable;
  220. }
  221. return caseTable[name];
  222. }
  223. }
  224. }