Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilip Navara <filip.navara@gmail.com>2018-09-16 06:51:28 +0300
committerJan Kotas <jkotas@microsoft.com>2018-09-17 06:53:24 +0300
commita11fabf11537bbe3bdf4691d29e91c862d55f9b4 (patch)
tree9647bd838c35a8947455d34770ae1a5b3e2fe38f
parenta0b3dc4d9b861b08da102f0b8b97945c61dac0f7 (diff)
System.Private.CoreLib: Move ResoureReader/ResourceSet to shared. (#19994)
* Move ResoureReader/ResourceSet to shared. * Remove unnecessary cast to RuntimeType from ResoureReader. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems2
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/ResourceReader.cs1184
-rw-r--r--src/System.Private.CoreLib/shared/System/Resources/ResourceSet.cs262
3 files changed, 1448 insertions, 0 deletions
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
index 769b21609..af1b99a30 100644
--- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -403,6 +403,8 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\MissingSatelliteAssemblyException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\NeutralResourcesLanguageAttribute.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceFallbackManager.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceReader.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceSet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\ResourceTypeCode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\RuntimeResourceSet.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Resources\SatelliteContractVersionAttribute.cs" />
diff --git a/src/System.Private.CoreLib/shared/System/Resources/ResourceReader.cs b/src/System.Private.CoreLib/shared/System/Resources/ResourceReader.cs
new file mode 100644
index 000000000..cd2b0d080
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/ResourceReader.cs
@@ -0,0 +1,1184 @@
+// 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.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Default way to read streams of resources on
+** demand.
+**
+** Version 2 support on October 6, 2003
+**
+===========================================================*/
+
+namespace System.Resources
+{
+ using System;
+ using System.IO;
+ using System.Text;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Reflection;
+ using System.Security;
+ using System.Globalization;
+ using System.Configuration.Assemblies;
+ using System.Runtime.Versioning;
+ using System.Diagnostics;
+ using System.Diagnostics.Contracts;
+
+ // Provides the default implementation of IResourceReader, reading
+ // .resources file from the system default binary format. This class
+ // can be treated as an enumerator once.
+ //
+ // See the RuntimeResourceSet overview for details on the system
+ // default file format.
+ //
+
+ internal struct ResourceLocator
+ {
+ internal object _value; // Can be null. Consider WeakReference instead?
+ internal int _dataPos;
+
+ internal ResourceLocator(int dataPos, object value)
+ {
+ _dataPos = dataPos;
+ _value = value;
+ }
+
+ internal int DataPosition
+ {
+ get { return _dataPos; }
+ //set { _dataPos = value; }
+ }
+
+ // Allows adding in profiling data in a future version, or a special
+ // resource profiling build. We could also use WeakReference.
+ internal object Value
+ {
+ get { return _value; }
+ set { _value = value; }
+ }
+
+ internal static bool CanCache(ResourceTypeCode value)
+ {
+ Debug.Assert(value >= 0, "negative ResourceTypeCode. What?");
+ return value <= ResourceTypeCode.LastPrimitive;
+ }
+ }
+
+ public sealed class ResourceReader : IResourceReader
+ {
+ // A reasonable default buffer size for reading from files, especially
+ // when we will likely be seeking frequently. Could be smaller, but does
+ // it make sense to use anything less than one page?
+ private const int DefaultFileStreamBufferSize = 4096;
+
+ private BinaryReader _store; // backing store we're reading from.
+ // Used by RuntimeResourceSet and this class's enumerator. Maps
+ // resource name to a value, a ResourceLocator, or a
+ // LooselyLinkedManifestResource.
+ internal Dictionary<string, ResourceLocator> _resCache;
+ private long _nameSectionOffset; // Offset to name section of file.
+ private long _dataSectionOffset; // Offset to Data section of file.
+
+ // Note this class is tightly coupled with UnmanagedMemoryStream.
+ // At runtime when getting an embedded resource from an assembly,
+ // we're given an UnmanagedMemoryStream referring to the mmap'ed portion
+ // of the assembly. The pointers here are pointers into that block of
+ // memory controlled by the OS's loader.
+ private int[] _nameHashes; // hash values for all names.
+ private unsafe int* _nameHashesPtr; // In case we're using UnmanagedMemoryStream
+ private int[] _namePositions; // relative locations of names
+ private unsafe int* _namePositionsPtr; // If we're using UnmanagedMemoryStream
+ private Type[] _typeTable; // Lazy array of Types for resource values.
+ private int[] _typeNamePositions; // To delay initialize type table
+ private int _numResources; // Num of resources files, in case arrays aren't allocated.
+
+ // We'll include a separate code path that uses UnmanagedMemoryStream to
+ // avoid allocating String objects and the like.
+ private UnmanagedMemoryStream _ums;
+
+ // Version number of .resources file, for compatibility
+ private int _version;
+
+
+ public ResourceReader(string fileName)
+ {
+ _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
+ _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.RandomAccess), Encoding.UTF8);
+
+ try
+ {
+ ReadResources();
+ }
+ catch
+ {
+ _store.Close(); // If we threw an exception, close the file.
+ throw;
+ }
+ }
+
+ public ResourceReader(Stream stream)
+ {
+ if (stream == null)
+ throw new ArgumentNullException(nameof(stream));
+ if (!stream.CanRead)
+ throw new ArgumentException(SR.Argument_StreamNotReadable);
+
+ _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
+ _store = new BinaryReader(stream, Encoding.UTF8);
+ // We have a faster code path for reading resource files from an assembly.
+ _ums = stream as UnmanagedMemoryStream;
+
+ ReadResources();
+ }
+
+ // This is the constructor the RuntimeResourceSet calls,
+ // passing in the stream to read from and the RuntimeResourceSet's
+ // internal hash table (hash table of names with file offsets
+ // and values, coupled to this ResourceReader).
+ internal ResourceReader(Stream stream, Dictionary<string, ResourceLocator> resCache)
+ {
+ Debug.Assert(stream != null, "Need a stream!");
+ Debug.Assert(stream.CanRead, "Stream should be readable!");
+ Debug.Assert(resCache != null, "Need a Dictionary!");
+
+ _resCache = resCache;
+ _store = new BinaryReader(stream, Encoding.UTF8);
+
+ _ums = stream as UnmanagedMemoryStream;
+
+ ReadResources();
+ }
+
+
+ public void Close()
+ {
+ Dispose(true);
+ }
+
+ public void Dispose()
+ {
+ Close();
+ }
+
+ private unsafe void Dispose(bool disposing)
+ {
+ if (_store != null)
+ {
+ _resCache = null;
+ if (disposing)
+ {
+ // Close the stream in a thread-safe way. This fix means
+ // that we may call Close n times, but that's safe.
+ BinaryReader copyOfStore = _store;
+ _store = null;
+ if (copyOfStore != null)
+ copyOfStore.Close();
+ }
+ _store = null;
+ _namePositions = null;
+ _nameHashes = null;
+ _ums = null;
+ _namePositionsPtr = null;
+ _nameHashesPtr = null;
+ }
+ }
+
+ internal static unsafe int ReadUnalignedI4(int* p)
+ {
+ byte* buffer = (byte*)p;
+ // Unaligned, little endian format
+ return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
+ }
+
+
+ private void SkipString()
+ {
+ int stringLength = _store.Read7BitEncodedInt();
+ if (stringLength < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
+ }
+ _store.BaseStream.Seek(stringLength, SeekOrigin.Current);
+ }
+
+ private unsafe int GetNameHash(int index)
+ {
+ Debug.Assert(index >= 0 && index < _numResources, "Bad index into hash array. index: " + index);
+ Debug.Assert((_ums == null && _nameHashes != null && _nameHashesPtr == null) ||
+ (_ums != null && _nameHashes == null && _nameHashesPtr != null), "Internal state mangled.");
+ if (_ums == null)
+ return _nameHashes[index];
+ else
+ return ReadUnalignedI4(&_nameHashesPtr[index]);
+ }
+
+ private unsafe int GetNamePosition(int index)
+ {
+ Debug.Assert(index >= 0 && index < _numResources, "Bad index into name position array. index: " + index);
+ Debug.Assert((_ums == null && _namePositions != null && _namePositionsPtr == null) ||
+ (_ums != null && _namePositions == null && _namePositionsPtr != null), "Internal state mangled.");
+ int r;
+ if (_ums == null)
+ r = _namePositions[index];
+ else
+ r = ReadUnalignedI4(&_namePositionsPtr[index]);
+ if (r < 0 || r > _dataSectionOffset - _nameSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, r));
+ }
+ return r;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public IDictionaryEnumerator GetEnumerator()
+ {
+ if (_resCache == null)
+ throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+ return new ResourceEnumerator(this);
+ }
+
+ internal ResourceEnumerator GetEnumeratorInternal()
+ {
+ return new ResourceEnumerator(this);
+ }
+
+ // From a name, finds the associated virtual offset for the data.
+ // To read the data, seek to _dataSectionOffset + dataPos, then
+ // read the resource type & data.
+ // This does a binary search through the names.
+ internal int FindPosForResource(string name)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ int hash = FastResourceComparer.HashFunction(name);
+
+ // Binary search over the hashes. Use the _namePositions array to
+ // determine where they exist in the underlying stream.
+ int lo = 0;
+ int hi = _numResources - 1;
+ int index = -1;
+ bool success = false;
+ while (lo <= hi)
+ {
+ index = (lo + hi) >> 1;
+ // Do NOT use subtraction here, since it will wrap for large
+ // negative numbers.
+ int currentHash = GetNameHash(index);
+ int c;
+ if (currentHash == hash)
+ c = 0;
+ else if (currentHash < hash)
+ c = -1;
+ else
+ c = 1;
+
+ if (c == 0)
+ {
+ success = true;
+ break;
+ }
+ if (c < 0)
+ lo = index + 1;
+ else
+ hi = index - 1;
+ }
+ if (!success)
+ {
+ return -1;
+ }
+
+ // index is the location in our hash array that corresponds with a
+ // value in the namePositions array.
+ // There could be collisions in our hash function. Check on both sides
+ // of index to find the range of hash values that are equal to the
+ // target hash value.
+ if (lo != index)
+ {
+ lo = index;
+ while (lo > 0 && GetNameHash(lo - 1) == hash)
+ lo--;
+ }
+ if (hi != index)
+ {
+ hi = index;
+ while (hi < _numResources - 1 && GetNameHash(hi + 1) == hash)
+ hi++;
+ }
+
+ lock (this)
+ {
+ for (int i = lo; i <= hi; i++)
+ {
+ _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin);
+ if (CompareStringEqualsName(name))
+ {
+ int dataPos = _store.ReadInt32();
+ if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
+ }
+ return dataPos;
+ }
+ }
+ }
+ return -1;
+ }
+
+ // This compares the String in the .resources file at the current position
+ // with the string you pass in.
+ // Whoever calls this method should make sure that they take a lock
+ // so no one else can cause us to seek in the stream.
+ private unsafe bool CompareStringEqualsName(string name)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ int byteLen = _store.Read7BitEncodedInt();
+ if (byteLen < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
+ }
+ if (_ums != null)
+ {
+ byte* bytes = _ums.PositionPointer;
+ // Skip over the data in the Stream, positioning ourselves right after it.
+ _ums.Seek(byteLen, SeekOrigin.Current);
+ if (_ums.Position > _ums.Length)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesNameTooLong);
+ }
+
+ // On 64-bit machines, these char*'s may be misaligned. Use a
+ // byte-by-byte comparison instead.
+ //return FastResourceComparer.CompareOrdinal((char*)bytes, byteLen/2, name) == 0;
+ return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0;
+ }
+ else
+ {
+ // This code needs to be fast
+ byte[] bytes = new byte[byteLen];
+ int numBytesToRead = byteLen;
+ while (numBytesToRead > 0)
+ {
+ int n = _store.Read(bytes, byteLen - numBytesToRead, numBytesToRead);
+ if (n == 0)
+ throw new BadImageFormatException(SR.BadImageFormat_ResourceNameCorrupted);
+ numBytesToRead -= n;
+ }
+ return FastResourceComparer.CompareOrdinal(bytes, byteLen / 2, name) == 0;
+ }
+ }
+
+ // This is used in the enumerator. The enumerator iterates from 0 to n
+ // of our resources and this returns the resource name for a particular
+ // index. The parameter is NOT a virtual offset.
+ private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ byte[] bytes;
+ int byteLen;
+ long nameVA = GetNamePosition(index);
+ lock (this)
+ {
+ _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
+ // Can't use _store.ReadString, since it's using UTF-8!
+ byteLen = _store.Read7BitEncodedInt();
+ if (byteLen < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
+ }
+
+ if (_ums != null)
+ {
+ if (_ums.Position > _ums.Length - byteLen)
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourcesIndexTooLong, index));
+
+ string s = null;
+ char* charPtr = (char*)_ums.PositionPointer;
+
+ s = new string(charPtr, 0, byteLen / 2);
+
+ _ums.Position += byteLen;
+ dataOffset = _store.ReadInt32();
+ if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
+ }
+ return s;
+ }
+
+ bytes = new byte[byteLen];
+ // We must read byteLen bytes, or we have a corrupted file.
+ // Use a blocking read in case the stream doesn't give us back
+ // everything immediately.
+ int count = byteLen;
+ while (count > 0)
+ {
+ int n = _store.Read(bytes, byteLen - count, count);
+ if (n == 0)
+ throw new EndOfStreamException(SR.Format(SR.BadImageFormat_ResourceNameCorrupted_NameIndex, index));
+ count -= n;
+ }
+ dataOffset = _store.ReadInt32();
+ if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
+ }
+ }
+ return Encoding.Unicode.GetString(bytes, 0, byteLen);
+ }
+
+ // This is used in the enumerator. The enumerator iterates from 0 to n
+ // of our resources and this returns the resource value for a particular
+ // index. The parameter is NOT a virtual offset.
+ private object GetValueForNameIndex(int index)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ long nameVA = GetNamePosition(index);
+ lock (this)
+ {
+ _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
+ SkipString();
+
+ int dataPos = _store.ReadInt32();
+ if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
+ }
+ ResourceTypeCode junk;
+ if (_version == 1)
+ return LoadObjectV1(dataPos);
+ else
+ return LoadObjectV2(dataPos, out junk);
+ }
+ }
+
+ // This takes a virtual offset into the data section and reads a String
+ // from that location.
+ // Anyone who calls LoadObject should make sure they take a lock so
+ // no one can cause us to do a seek in here.
+ internal string LoadString(int pos)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
+ string s = null;
+ int typeIndex = _store.Read7BitEncodedInt();
+ if (_version == 1)
+ {
+ if (typeIndex == -1)
+ return null;
+ if (FindType(typeIndex) != typeof(string))
+ throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName));
+ s = _store.ReadString();
+ }
+ else
+ {
+ ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex;
+ if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null)
+ {
+ string typeString;
+ if (typeCode < ResourceTypeCode.StartOfUserTypes)
+ typeString = typeCode.ToString();
+ else
+ typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName;
+ throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString));
+ }
+ if (typeCode == ResourceTypeCode.String) // ignore Null
+ s = _store.ReadString();
+ }
+ return s;
+ }
+
+ // Called from RuntimeResourceSet
+ internal object LoadObject(int pos)
+ {
+ if (_version == 1)
+ return LoadObjectV1(pos);
+ ResourceTypeCode typeCode;
+ return LoadObjectV2(pos, out typeCode);
+ }
+
+ internal object LoadObject(int pos, out ResourceTypeCode typeCode)
+ {
+ if (_version == 1)
+ {
+ object o = LoadObjectV1(pos);
+ typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes;
+ return o;
+ }
+ return LoadObjectV2(pos, out typeCode);
+ }
+
+ // This takes a virtual offset into the data section and reads an Object
+ // from that location.
+ // Anyone who calls LoadObject should make sure they take a lock so
+ // no one can cause us to do a seek in here.
+ internal object LoadObjectV1(int pos)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ Debug.Assert(_version == 1, ".resources file was not a V1 .resources file!");
+
+ try
+ {
+ // mega try-catch performs exceptionally bad on x64; factored out body into
+ // _LoadObjectV1 and wrap here.
+ return _LoadObjectV1(pos);
+ }
+ catch (EndOfStreamException eof)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
+ }
+ }
+
+ private object _LoadObjectV1(int pos)
+ {
+ _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
+ int typeIndex = _store.Read7BitEncodedInt();
+ if (typeIndex == -1)
+ return null;
+ Type type = FindType(typeIndex);
+ // Consider putting in logic to see if this type is a
+ // primitive or a value type first, so we can reach the
+ // deserialization code faster for arbitrary objects.
+
+ if (type == typeof(string))
+ return _store.ReadString();
+ else if (type == typeof(int))
+ return _store.ReadInt32();
+ else if (type == typeof(byte))
+ return _store.ReadByte();
+ else if (type == typeof(sbyte))
+ return _store.ReadSByte();
+ else if (type == typeof(short))
+ return _store.ReadInt16();
+ else if (type == typeof(long))
+ return _store.ReadInt64();
+ else if (type == typeof(ushort))
+ return _store.ReadUInt16();
+ else if (type == typeof(uint))
+ return _store.ReadUInt32();
+ else if (type == typeof(ulong))
+ return _store.ReadUInt64();
+ else if (type == typeof(float))
+ return _store.ReadSingle();
+ else if (type == typeof(double))
+ return _store.ReadDouble();
+ else if (type == typeof(DateTime))
+ {
+ // Ideally we should use DateTime's ToBinary & FromBinary,
+ // but we can't for compatibility reasons.
+ return new DateTime(_store.ReadInt64());
+ }
+ else if (type == typeof(TimeSpan))
+ return new TimeSpan(_store.ReadInt64());
+ else if (type == typeof(decimal))
+ {
+ int[] bits = new int[4];
+ for (int i = 0; i < bits.Length; i++)
+ bits[i] = _store.ReadInt32();
+ return new decimal(bits);
+ }
+ else
+ {
+ throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
+ }
+ }
+
+ internal object LoadObjectV2(int pos, out ResourceTypeCode typeCode)
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+ Debug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!");
+
+ try
+ {
+ // mega try-catch performs exceptionally bad on x64; factored out body into
+ // _LoadObjectV2 and wrap here.
+ return _LoadObjectV2(pos, out typeCode);
+ }
+ catch (EndOfStreamException eof)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
+ }
+ }
+
+ private object _LoadObjectV2(int pos, out ResourceTypeCode typeCode)
+ {
+ _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
+ typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
+
+ switch (typeCode)
+ {
+ case ResourceTypeCode.Null:
+ return null;
+
+ case ResourceTypeCode.String:
+ return _store.ReadString();
+
+ case ResourceTypeCode.Boolean:
+ return _store.ReadBoolean();
+
+ case ResourceTypeCode.Char:
+ return (char)_store.ReadUInt16();
+
+ case ResourceTypeCode.Byte:
+ return _store.ReadByte();
+
+ case ResourceTypeCode.SByte:
+ return _store.ReadSByte();
+
+ case ResourceTypeCode.Int16:
+ return _store.ReadInt16();
+
+ case ResourceTypeCode.UInt16:
+ return _store.ReadUInt16();
+
+ case ResourceTypeCode.Int32:
+ return _store.ReadInt32();
+
+ case ResourceTypeCode.UInt32:
+ return _store.ReadUInt32();
+
+ case ResourceTypeCode.Int64:
+ return _store.ReadInt64();
+
+ case ResourceTypeCode.UInt64:
+ return _store.ReadUInt64();
+
+ case ResourceTypeCode.Single:
+ return _store.ReadSingle();
+
+ case ResourceTypeCode.Double:
+ return _store.ReadDouble();
+
+ case ResourceTypeCode.Decimal:
+ return _store.ReadDecimal();
+
+ case ResourceTypeCode.DateTime:
+ // Use DateTime's ToBinary & FromBinary.
+ long data = _store.ReadInt64();
+ return DateTime.FromBinary(data);
+
+ case ResourceTypeCode.TimeSpan:
+ long ticks = _store.ReadInt64();
+ return new TimeSpan(ticks);
+
+ // Special types
+ case ResourceTypeCode.ByteArray:
+ {
+ int len = _store.ReadInt32();
+ if (len < 0)
+ {
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
+ }
+
+ if (_ums == null)
+ {
+ if (len > _store.BaseStream.Length)
+ {
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
+ }
+ return _store.ReadBytes(len);
+ }
+
+ if (len > _ums.Length - _ums.Position)
+ {
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
+ }
+
+ byte[] bytes = new byte[len];
+ int r = _ums.Read(bytes, 0, len);
+ Debug.Assert(r == len, "ResourceReader needs to use a blocking read here. (Call _store.ReadBytes(len)?)");
+ return bytes;
+ }
+
+ case ResourceTypeCode.Stream:
+ {
+ int len = _store.ReadInt32();
+ if (len < 0)
+ {
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
+ }
+ if (_ums == null)
+ {
+ byte[] bytes = _store.ReadBytes(len);
+ // Lifetime of memory == lifetime of this stream.
+ return new PinnedBufferMemoryStream(bytes);
+ }
+
+ // make sure we don't create an UnmanagedMemoryStream that is longer than the resource stream.
+ if (len > _ums.Length - _ums.Position)
+ {
+ throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
+ }
+
+ // For the case that we've memory mapped in the .resources
+ // file, just return a Stream pointing to that block of memory.
+ unsafe
+ {
+ return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read);
+ }
+ }
+
+ default:
+ if (typeCode < ResourceTypeCode.StartOfUserTypes)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch);
+ }
+ break;
+ }
+
+ // Normal serialized objects
+ throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
+ }
+
+ // Reads in the header information for a .resources file. Verifies some
+ // of the assumptions about this resource set, and builds the class table
+ // for the default resource file format.
+ private void ReadResources()
+ {
+ Debug.Assert(_store != null, "ResourceReader is closed!");
+
+ try
+ {
+ // mega try-catch performs exceptionally bad on x64; factored out body into
+ // _ReadResources and wrap here.
+ _ReadResources();
+ }
+ catch (EndOfStreamException eof)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, eof);
+ }
+ catch (IndexOutOfRangeException e)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, e);
+ }
+ }
+
+ private void _ReadResources()
+ {
+ // Read ResourceManager header
+ // Check for magic number
+ int magicNum = _store.ReadInt32();
+ if (magicNum != ResourceManager.MagicNumber)
+ throw new ArgumentException(SR.Resources_StreamNotValid);
+ // Assuming this is ResourceManager header V1 or greater, hopefully
+ // after the version number there is a number of bytes to skip
+ // to bypass the rest of the ResMgr header. For V2 or greater, we
+ // use this to skip to the end of the header
+ int resMgrHeaderVersion = _store.ReadInt32();
+ int numBytesToSkip = _store.ReadInt32();
+ if (numBytesToSkip < 0 || resMgrHeaderVersion < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+ if (resMgrHeaderVersion > 1)
+ {
+ _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
+ }
+ else
+ {
+ // We don't care about numBytesToSkip; read the rest of the header
+
+ // Read in type name for a suitable ResourceReader
+ // Note ResourceWriter & InternalResGen use different Strings.
+ string readerType = _store.ReadString();
+ readerType = System.CoreLib.FixupCoreLibName(readerType);
+ AssemblyName mscorlib = new AssemblyName(ResourceManager.MscorlibName);
+
+ if (!ResourceManager.CompareNames(readerType, ResourceManager.ResReaderTypeName, mscorlib))
+ throw new NotSupportedException(SR.Format(SR.NotSupported_WrongResourceReader_Type, readerType));
+
+ // Skip over type name for a suitable ResourceSet
+ SkipString();
+ }
+
+ // Read RuntimeResourceSet header
+ // Do file version check
+ int version = _store.ReadInt32();
+ if (version != RuntimeResourceSet.Version && version != 1)
+ throw new ArgumentException(SR.Format(SR.Arg_ResourceFileUnsupportedVersion, RuntimeResourceSet.Version, version));
+ _version = version;
+
+ _numResources = _store.ReadInt32();
+ if (_numResources < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+
+ // Read type positions into type positions array.
+ // But delay initialize the type table.
+ int numTypes = _store.ReadInt32();
+ if (numTypes < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+ _typeTable = new Type[numTypes];
+ _typeNamePositions = new int[numTypes];
+ for (int i = 0; i < numTypes; i++)
+ {
+ _typeNamePositions[i] = (int)_store.BaseStream.Position;
+
+ // Skip over the Strings in the file. Don't create types.
+ SkipString();
+ }
+
+ // Prepare to read in the array of name hashes
+ // Note that the name hashes array is aligned to 8 bytes so
+ // we can use pointers into it on 64 bit machines. (4 bytes
+ // may be sufficient, but let's plan for the future)
+ // Skip over alignment stuff. All public .resources files
+ // should be aligned No need to verify the byte values.
+ long pos = _store.BaseStream.Position;
+ int alignBytes = ((int)pos) & 7;
+ if (alignBytes != 0)
+ {
+ for (int i = 0; i < 8 - alignBytes; i++)
+ {
+ _store.ReadByte();
+ }
+ }
+
+ // Read in the array of name hashes
+ if (_ums == null)
+ {
+ _nameHashes = new int[_numResources];
+ for (int i = 0; i < _numResources; i++)
+ {
+ _nameHashes[i] = _store.ReadInt32();
+ }
+ }
+ else
+ {
+ int seekPos = unchecked(4 * _numResources);
+ if (seekPos < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+ unsafe
+ {
+ _nameHashesPtr = (int*)_ums.PositionPointer;
+ // Skip over the array of nameHashes.
+ _ums.Seek(seekPos, SeekOrigin.Current);
+ // get the position pointer once more to check that the whole table is within the stream
+ byte* junk = _ums.PositionPointer;
+ }
+ }
+
+ // Read in the array of relative positions for all the names.
+ if (_ums == null)
+ {
+ _namePositions = new int[_numResources];
+ for (int i = 0; i < _numResources; i++)
+ {
+ int namePosition = _store.ReadInt32();
+ if (namePosition < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+
+ _namePositions[i] = namePosition;
+ }
+ }
+ else
+ {
+ int seekPos = unchecked(4 * _numResources);
+ if (seekPos < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+ unsafe
+ {
+ _namePositionsPtr = (int*)_ums.PositionPointer;
+ // Skip over the array of namePositions.
+ _ums.Seek(seekPos, SeekOrigin.Current);
+ // get the position pointer once more to check that the whole table is within the stream
+ byte* junk = _ums.PositionPointer;
+ }
+ }
+
+ // Read location of data section.
+ _dataSectionOffset = _store.ReadInt32();
+ if (_dataSectionOffset < 0)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+
+ // Store current location as start of name section
+ _nameSectionOffset = _store.BaseStream.Position;
+
+ // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt
+ if (_dataSectionOffset < _nameSectionOffset)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
+ }
+ }
+
+ // This allows us to delay-initialize the Type[]. This might be a
+ // good startup time savings, since we might have to load assemblies
+ // and initialize Reflection.
+ private Type FindType(int typeIndex)
+ {
+ if (typeIndex < 0 || typeIndex >= _typeTable.Length)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
+ }
+ if (_typeTable[typeIndex] == null)
+ {
+ long oldPos = _store.BaseStream.Position;
+ try
+ {
+ _store.BaseStream.Position = _typeNamePositions[typeIndex];
+ string typeName = _store.ReadString();
+ _typeTable[typeIndex] = Type.GetType(typeName, true);
+ }
+ // If serialization isn't supported, we convert FileNotFoundException to
+ // NotSupportedException for consistency with v2. This is a corner-case, but the
+ // idea is that we want to give the user a more accurate error message. Even if
+ // the dependency were found, we know it will require serialization since it
+ // can't be one of the types we special case. So if the dependency were found,
+ // it would go down the serialization code path, resulting in NotSupported for
+ // SKUs without serialization.
+ //
+ // We don't want to regress the expected case by checking the type info before
+ // getting to Type.GetType -- this is costly with v1 resource formats.
+ catch (FileNotFoundException)
+ {
+ throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
+ }
+ finally
+ {
+ _store.BaseStream.Position = oldPos;
+ }
+ }
+ Debug.Assert(_typeTable[typeIndex] != null, "Should have found a type!");
+ return _typeTable[typeIndex];
+ }
+
+ public void GetResourceData(string resourceName, out string resourceType, out byte[] resourceData)
+ {
+ if (resourceName == null)
+ throw new ArgumentNullException(nameof(resourceName));
+ if (_resCache == null)
+ throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+
+ // Get the type information from the data section. Also,
+ // sort all of the data section's indexes to compute length of
+ // the serialized data for this type (making sure to subtract
+ // off the length of the type code).
+ int[] sortedDataPositions = new int[_numResources];
+ int dataPos = FindPosForResource(resourceName);
+ if (dataPos == -1)
+ {
+ throw new ArgumentException(SR.Format(SR.Arg_ResourceNameNotExist, resourceName));
+ }
+
+ lock (this)
+ {
+ // Read all the positions of data within the data section.
+ for (int i = 0; i < _numResources; i++)
+ {
+ _store.BaseStream.Position = _nameSectionOffset + GetNamePosition(i);
+ // Skip over name of resource
+ int numBytesToSkip = _store.Read7BitEncodedInt();
+ if (numBytesToSkip < 0)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, numBytesToSkip));
+ }
+ _store.BaseStream.Position += numBytesToSkip;
+
+ int dPos = _store.ReadInt32();
+ if (dPos < 0 || dPos >= _store.BaseStream.Length - _dataSectionOffset)
+ {
+ throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dPos));
+ }
+ sortedDataPositions[i] = dPos;
+ }
+ Array.Sort(sortedDataPositions);
+
+ int index = Array.BinarySearch(sortedDataPositions, dataPos);
+ Debug.Assert(index >= 0 && index < _numResources, "Couldn't find data position within sorted data positions array!");
+ long nextData = (index < _numResources - 1) ? sortedDataPositions[index + 1] + _dataSectionOffset : _store.BaseStream.Length;
+ int len = (int)(nextData - (dataPos + _dataSectionOffset));
+ Debug.Assert(len >= 0 && len <= (int)_store.BaseStream.Length - dataPos + _dataSectionOffset, "Length was negative or outside the bounds of the file!");
+
+ // Read type code then byte[]
+ _store.BaseStream.Position = _dataSectionOffset + dataPos;
+ ResourceTypeCode typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
+ if (typeCode < 0 || typeCode >= ResourceTypeCode.StartOfUserTypes + _typeTable.Length)
+ {
+ throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
+ }
+ resourceType = TypeNameFromTypeCode(typeCode);
+
+ // The length must be adjusted to subtract off the number
+ // of bytes in the 7 bit encoded type code.
+ len -= (int)(_store.BaseStream.Position - (_dataSectionOffset + dataPos));
+ byte[] bytes = _store.ReadBytes(len);
+ if (bytes.Length != len)
+ throw new FormatException(SR.BadImageFormat_ResourceNameCorrupted);
+ resourceData = bytes;
+ }
+ }
+
+ private string TypeNameFromTypeCode(ResourceTypeCode typeCode)
+ {
+ Debug.Assert(typeCode >= 0, "can't be negative");
+ if (typeCode < ResourceTypeCode.StartOfUserTypes)
+ {
+ Debug.Assert(!string.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers.");
+ return "ResourceTypeCode." + typeCode.ToString();
+ }
+ else
+ {
+ int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
+ Debug.Assert(typeIndex >= 0 && typeIndex < _typeTable.Length, "TypeCode is broken or corrupted!");
+ long oldPos = _store.BaseStream.Position;
+ try
+ {
+ _store.BaseStream.Position = _typeNamePositions[typeIndex];
+ return _store.ReadString();
+ }
+ finally
+ {
+ _store.BaseStream.Position = oldPos;
+ }
+ }
+ }
+
+ internal sealed class ResourceEnumerator : IDictionaryEnumerator
+ {
+ private const int ENUM_DONE = int.MinValue;
+ private const int ENUM_NOT_STARTED = -1;
+
+ private ResourceReader _reader;
+ private bool _currentIsValid;
+ private int _currentName;
+ private int _dataPosition; // cached for case-insensitive table
+
+ internal ResourceEnumerator(ResourceReader reader)
+ {
+ _currentName = ENUM_NOT_STARTED;
+ _reader = reader;
+ _dataPosition = -2;
+ }
+
+ public bool MoveNext()
+ {
+ if (_currentName == _reader._numResources - 1 || _currentName == ENUM_DONE)
+ {
+ _currentIsValid = false;
+ _currentName = ENUM_DONE;
+ return false;
+ }
+ _currentIsValid = true;
+ _currentName++;
+ return true;
+ }
+
+ public object Key
+ {
+ get
+ {
+ if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
+ if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+
+ return _reader.AllocateStringForNameIndex(_currentName, out _dataPosition);
+ }
+ }
+
+ public object Current
+ {
+ get
+ {
+ return Entry;
+ }
+ }
+
+ // Warning: This requires that you call the Key or Entry property FIRST before calling it!
+ internal int DataPosition
+ {
+ get
+ {
+ return _dataPosition;
+ }
+ }
+
+ public DictionaryEntry Entry
+ {
+ get
+ {
+ if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
+ if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+
+ string key;
+ object value = null;
+ lock (_reader)
+ { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock
+ lock (_reader._resCache)
+ {
+ key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader
+ ResourceLocator locator;
+ if (_reader._resCache.TryGetValue(key, out locator))
+ {
+ value = locator.Value;
+ }
+ if (value == null)
+ {
+ if (_dataPosition == -1)
+ value = _reader.GetValueForNameIndex(_currentName);
+ else
+ value = _reader.LoadObject(_dataPosition);
+ // If enumeration and subsequent lookups happen very
+ // frequently in the same process, add a ResourceLocator
+ // to _resCache here. But WinForms enumerates and
+ // just about everyone else does lookups. So caching
+ // here may bloat working set.
+ }
+ }
+ }
+ return new DictionaryEntry(key, value);
+ }
+ }
+
+ public object Value
+ {
+ get
+ {
+ if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
+ if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
+ if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+
+ // Consider using _resCache here, eventually, if
+ // this proves to be an interesting perf scenario.
+ // But mixing lookups and enumerators shouldn't be
+ // particularly compelling.
+ return _reader.GetValueForNameIndex(_currentName);
+ }
+ }
+
+ public void Reset()
+ {
+ if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
+ _currentIsValid = false;
+ _currentName = ENUM_NOT_STARTED;
+ }
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Resources/ResourceSet.cs b/src/System.Private.CoreLib/shared/System/Resources/ResourceSet.cs
new file mode 100644
index 000000000..2d33f3a8b
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Resources/ResourceSet.cs
@@ -0,0 +1,262 @@
+// 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.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: Culture-specific collection of resources.
+**
+**
+===========================================================*/
+
+using System.Collections;
+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;
+ internal Hashtable Table;
+
+ private Hashtable _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.
+ CommonInit();
+ }
+
+ // For RuntimeResourceSet, ignore the Table parameter - it's a wasted
+ // allocation.
+ internal ResourceSet(bool junk)
+ {
+ }
+
+ // Creates a ResourceSet using the system default ResourceReader
+ // implementation. Use this constructor to open & read from a file
+ // on disk.
+ //
+ public ResourceSet(string fileName)
+ {
+ Reader = new ResourceReader(fileName);
+ CommonInit();
+ 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)
+ {
+ Reader = new ResourceReader(stream);
+ CommonInit();
+ ReadResources();
+ }
+
+ public ResourceSet(IResourceReader reader)
+ {
+ if (reader == null)
+ throw new ArgumentNullException(nameof(reader));
+ Reader = reader;
+ CommonInit();
+ ReadResources();
+ }
+
+ private void CommonInit()
+ {
+ Table = new Hashtable();
+ }
+
+ // 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;
+ if (copyOfReader != 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()
+ {
+ Assembly resourceWriterAssembly = Assembly.Load("System.Resources.Writer, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
+ return resourceWriterAssembly.GetType("System.Resources.ResourceWriter", true);
+ }
+
+ public virtual IDictionaryEnumerator GetEnumerator()
+ {
+ return GetEnumeratorHelper();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumeratorHelper();
+ }
+
+ private IDictionaryEnumerator GetEnumeratorHelper()
+ {
+ Hashtable copyOfTable = Table; // Avoid a race with Dispose
+ if (copyOfTable == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+ return copyOfTable.GetEnumerator();
+ }
+
+ // Look up a string value for a resource given its name.
+ //
+ public virtual string GetString(string name)
+ {
+ object obj = GetObjectInternal(name);
+ try
+ {
+ return (string)obj;
+ }
+ catch (InvalidCastException)
+ {
+ throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
+ }
+ }
+
+ public virtual string GetString(string name, bool ignoreCase)
+ {
+ object obj;
+ string s;
+
+ // Case-sensitive lookup
+ obj = GetObjectInternal(name);
+ try
+ {
+ s = (string)obj;
+ }
+ catch (InvalidCastException)
+ {
+ throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Name, name));
+ }
+
+ // case-sensitive lookup succeeded
+ if (s != null || !ignoreCase)
+ {
+ return s;
+ }
+
+ // Try doing a case-insensitive lookup
+ obj = GetCaseInsensitiveObjectInternal(name);
+ try
+ {
+ return (string)obj;
+ }
+ catch (InvalidCastException)
+ {
+ 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()
+ {
+ IDictionaryEnumerator en = Reader.GetEnumerator();
+ while (en.MoveNext())
+ {
+ object value = en.Value;
+ Table.Add(en.Key, 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)
+ {
+ if (name == null)
+ throw new ArgumentNullException(nameof(name));
+
+ Hashtable copyOfTable = Table; // Avoid a race with Dispose
+
+ if (copyOfTable == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+
+ return copyOfTable[name];
+ }
+
+ private object GetCaseInsensitiveObjectInternal(string name)
+ {
+ Hashtable copyOfTable = Table; // Avoid a race with Dispose
+
+ if (copyOfTable == null)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_ResourceSet);
+
+ Hashtable caseTable = _caseInsensitiveTable; // Avoid a race condition with Close
+ if (caseTable == null)
+ {
+ caseTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
+
+ IDictionaryEnumerator en = copyOfTable.GetEnumerator();
+ while (en.MoveNext())
+ {
+ caseTable.Add(en.Key, en.Value);
+ }
+ _caseInsensitiveTable = caseTable;
+ }
+
+ return caseTable[name];
+ }
+ }
+}