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.
5 /*============================================================
11 ** Purpose: Culture-specific collection of resources.
14 ===========================================================*/
16 using System
.Collections
;
17 using System
.Diagnostics
;
19 using System
.Reflection
;
21 namespace System
.Resources
23 // A ResourceSet stores all the resources defined in one particular CultureInfo.
25 // The method used to load resources is straightforward - this class
26 // enumerates over an IResourceReader, loading every name and value, and
27 // stores them in a hash table. Custom IResourceReaders can be used.
29 public class ResourceSet
: IDisposable
, IEnumerable
31 protected IResourceReader Reader
= null!;
32 internal Hashtable
? Table
; // TODO-NULLABLE: Avoid nulling out in Dispose
34 private Hashtable
? _caseInsensitiveTable
; // For case-insensitive lookups.
36 protected ResourceSet()
38 // To not inconvenience people subclassing us, we should allocate a new
39 // hashtable here just so that Table is set to something.
40 Table
= new Hashtable();
43 // For RuntimeResourceSet, ignore the Table parameter - it's a wasted
45 internal ResourceSet(bool junk
)
49 // Creates a ResourceSet using the system default ResourceReader
50 // implementation. Use this constructor to open & read from a file
53 public ResourceSet(string fileName
)
56 Reader
= new ResourceReader(fileName
);
60 // Creates a ResourceSet using the system default ResourceReader
61 // implementation. Use this constructor to read from an open stream
64 public ResourceSet(Stream stream
)
67 Reader
= new ResourceReader(stream
);
71 public ResourceSet(IResourceReader reader
)
75 throw new ArgumentNullException(nameof(reader
));
80 // Closes and releases any resources used by this ResourceSet, if any.
81 // All calls to methods on the ResourceSet after a call to close may
82 // fail. Close is guaranteed to be safely callable multiple times on a
83 // particular ResourceSet, and all subclasses must support these semantics.
84 public virtual void Close()
89 protected virtual void Dispose(bool disposing
)
93 // Close the Reader in a thread-safe way.
94 IResourceReader
? copyOfReader
= Reader
;
95 Reader
= null!; // TODO-NULLABLE: Avoid nulling out in Dispose
96 if (copyOfReader
!= null)
99 Reader
= null!; // TODO-NULLABLE: Avoid nulling out in Dispose
100 _caseInsensitiveTable
= null;
104 public void Dispose()
109 // Returns the preferred IResourceReader class for this kind of ResourceSet.
110 // Subclasses of ResourceSet using their own Readers &; should override
111 // GetDefaultReader and GetDefaultWriter.
112 public virtual Type
GetDefaultReader()
114 return typeof(ResourceReader
);
117 // Returns the preferred IResourceWriter class for this kind of ResourceSet.
118 // Subclasses of ResourceSet using their own Readers &; should override
119 // GetDefaultReader and GetDefaultWriter.
120 public virtual Type
GetDefaultWriter()
122 Assembly resourceWriterAssembly
= Assembly
.Load("System.Resources.Writer, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
123 return resourceWriterAssembly
.GetType("System.Resources.ResourceWriter", throwOnError
: true)!;
126 public virtual IDictionaryEnumerator
GetEnumerator()
128 return GetEnumeratorHelper();
131 IEnumerator IEnumerable
.GetEnumerator()
133 return GetEnumeratorHelper();
136 private IDictionaryEnumerator
GetEnumeratorHelper()
138 Hashtable
? copyOfTable
= Table
; // Avoid a race with Dispose
139 if (copyOfTable
== null)
140 throw new ObjectDisposedException(null, SR
.ObjectDisposed_ResourceSet
);
141 return copyOfTable
.GetEnumerator();
144 // Look up a string value for a resource given its name.
146 public virtual string? GetString(string name
)
148 object? obj
= GetObjectInternal(name
);
153 catch (InvalidCastException
)
155 throw new InvalidOperationException(SR
.Format(SR
.InvalidOperation_ResourceNotString_Name
, name
));
159 public virtual string? GetString(string name
, bool ignoreCase
)
164 // Case-sensitive lookup
165 obj
= GetObjectInternal(name
);
170 catch (InvalidCastException
)
172 throw new InvalidOperationException(SR
.Format(SR
.InvalidOperation_ResourceNotString_Name
, name
));
175 // case-sensitive lookup succeeded
176 if (s
!= null || !ignoreCase
)
181 // Try doing a case-insensitive lookup
182 obj
= GetCaseInsensitiveObjectInternal(name
);
187 catch (InvalidCastException
)
189 throw new InvalidOperationException(SR
.Format(SR
.InvalidOperation_ResourceNotString_Name
, name
));
193 // Look up an object value for a resource given its name.
195 public virtual object? GetObject(string name
)
197 return GetObjectInternal(name
);
200 public virtual object? GetObject(string name
, bool ignoreCase
)
202 object? obj
= GetObjectInternal(name
);
204 if (obj
!= null || !ignoreCase
)
207 return GetCaseInsensitiveObjectInternal(name
);
210 protected virtual void ReadResources()
212 Debug
.Assert(Table
!= null);
213 Debug
.Assert(Reader
!= null);
214 IDictionaryEnumerator en
= Reader
.GetEnumerator();
215 while (en
.MoveNext())
217 object? value = en
.Value
;
218 Table
.Add(en
.Key
, value);
220 // While technically possible to close the Reader here, don't close it
221 // to help with some WinRes lifetime issues.
224 private object? GetObjectInternal(string name
)
227 throw new ArgumentNullException(nameof(name
));
229 Hashtable
? copyOfTable
= Table
; // Avoid a race with Dispose
231 if (copyOfTable
== null)
232 throw new ObjectDisposedException(null, SR
.ObjectDisposed_ResourceSet
);
234 return copyOfTable
[name
];
237 private object? GetCaseInsensitiveObjectInternal(string name
)
239 Hashtable
? copyOfTable
= Table
; // Avoid a race with Dispose
241 if (copyOfTable
== null)
242 throw new ObjectDisposedException(null, SR
.ObjectDisposed_ResourceSet
);
244 Hashtable
? caseTable
= _caseInsensitiveTable
; // Avoid a race condition with Close
245 if (caseTable
== null)
247 caseTable
= new Hashtable(StringComparer
.OrdinalIgnoreCase
);
249 IDictionaryEnumerator en
= copyOfTable
.GetEnumerator();
250 while (en
.MoveNext())
252 caseTable
.Add(en
.Key
, en
.Value
);
254 _caseInsensitiveTable
= caseTable
;
257 return caseTable
[name
];