ResourceReader.cs 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  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: Default way to read streams of resources on
  11. ** demand.
  12. **
  13. ** Version 2 support on October 6, 2003
  14. **
  15. ===========================================================*/
  16. namespace System.Resources
  17. {
  18. using System;
  19. using System.IO;
  20. using System.Text;
  21. using System.Collections;
  22. using System.Collections.Generic;
  23. using System.Reflection;
  24. using System.Security;
  25. using System.Globalization;
  26. using System.Configuration.Assemblies;
  27. using System.Runtime.Versioning;
  28. using System.Diagnostics;
  29. using System.Diagnostics.Contracts;
  30. using System.Threading;
  31. // Provides the default implementation of IResourceReader, reading
  32. // .resources file from the system default binary format. This class
  33. // can be treated as an enumerator once.
  34. //
  35. // See the RuntimeResourceSet overview for details on the system
  36. // default file format.
  37. //
  38. internal struct ResourceLocator
  39. {
  40. internal object _value; // Can be null. Consider WeakReference instead?
  41. internal int _dataPos;
  42. internal ResourceLocator(int dataPos, object value)
  43. {
  44. _dataPos = dataPos;
  45. _value = value;
  46. }
  47. internal int DataPosition
  48. {
  49. get { return _dataPos; }
  50. //set { _dataPos = value; }
  51. }
  52. // Allows adding in profiling data in a future version, or a special
  53. // resource profiling build. We could also use WeakReference.
  54. internal object Value
  55. {
  56. get { return _value; }
  57. set { _value = value; }
  58. }
  59. internal static bool CanCache(ResourceTypeCode value)
  60. {
  61. Debug.Assert(value >= 0, "negative ResourceTypeCode. What?");
  62. return value <= ResourceTypeCode.LastPrimitive;
  63. }
  64. }
  65. public sealed class ResourceReader : IResourceReader
  66. {
  67. // A reasonable default buffer size for reading from files, especially
  68. // when we will likely be seeking frequently. Could be smaller, but does
  69. // it make sense to use anything less than one page?
  70. private const int DefaultFileStreamBufferSize = 4096;
  71. private BinaryReader _store; // backing store we're reading from.
  72. // Used by RuntimeResourceSet and this class's enumerator. Maps
  73. // resource name to a value, a ResourceLocator, or a
  74. // LooselyLinkedManifestResource.
  75. internal Dictionary<string, ResourceLocator> _resCache;
  76. private long _nameSectionOffset; // Offset to name section of file.
  77. private long _dataSectionOffset; // Offset to Data section of file.
  78. // Note this class is tightly coupled with UnmanagedMemoryStream.
  79. // At runtime when getting an embedded resource from an assembly,
  80. // we're given an UnmanagedMemoryStream referring to the mmap'ed portion
  81. // of the assembly. The pointers here are pointers into that block of
  82. // memory controlled by the OS's loader.
  83. private int[] _nameHashes; // hash values for all names.
  84. private unsafe int* _nameHashesPtr; // In case we're using UnmanagedMemoryStream
  85. private int[] _namePositions; // relative locations of names
  86. private unsafe int* _namePositionsPtr; // If we're using UnmanagedMemoryStream
  87. private Type[] _typeTable; // Lazy array of Types for resource values.
  88. private int[] _typeNamePositions; // To delay initialize type table
  89. private int _numResources; // Num of resources files, in case arrays aren't allocated.
  90. private readonly bool _permitDeserialization; // can deserialize BinaryFormatted resources
  91. private object _binaryFormatter; // binary formatter instance to use for deserializing
  92. // statics used to dynamically call into BinaryFormatter
  93. // When successfully located s_binaryFormatterType will point to the BinaryFormatter type
  94. // and s_deserializeMethod will point to an unbound delegate to the deserialize method.
  95. private static Type s_binaryFormatterType;
  96. private static Func<object, Stream, object> s_deserializeMethod;
  97. // We'll include a separate code path that uses UnmanagedMemoryStream to
  98. // avoid allocating String objects and the like.
  99. private UnmanagedMemoryStream _ums;
  100. // Version number of .resources file, for compatibility
  101. private int _version;
  102. public ResourceReader(string fileName)
  103. {
  104. _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
  105. _store = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.RandomAccess), Encoding.UTF8);
  106. try
  107. {
  108. ReadResources();
  109. }
  110. catch
  111. {
  112. _store.Close(); // If we threw an exception, close the file.
  113. throw;
  114. }
  115. }
  116. public ResourceReader(Stream stream)
  117. {
  118. if (stream == null)
  119. throw new ArgumentNullException(nameof(stream));
  120. if (!stream.CanRead)
  121. throw new ArgumentException(SR.Argument_StreamNotReadable);
  122. _resCache = new Dictionary<string, ResourceLocator>(FastResourceComparer.Default);
  123. _store = new BinaryReader(stream, Encoding.UTF8);
  124. // We have a faster code path for reading resource files from an assembly.
  125. _ums = stream as UnmanagedMemoryStream;
  126. ReadResources();
  127. }
  128. // This is the constructor the RuntimeResourceSet calls,
  129. // passing in the stream to read from and the RuntimeResourceSet's
  130. // internal hash table (hash table of names with file offsets
  131. // and values, coupled to this ResourceReader).
  132. internal ResourceReader(Stream stream, Dictionary<string, ResourceLocator> resCache, bool permitDeserialization)
  133. {
  134. Debug.Assert(stream != null, "Need a stream!");
  135. Debug.Assert(stream.CanRead, "Stream should be readable!");
  136. Debug.Assert(resCache != null, "Need a Dictionary!");
  137. _resCache = resCache;
  138. _store = new BinaryReader(stream, Encoding.UTF8);
  139. _ums = stream as UnmanagedMemoryStream;
  140. _permitDeserialization = permitDeserialization;
  141. ReadResources();
  142. }
  143. public void Close()
  144. {
  145. Dispose(true);
  146. }
  147. public void Dispose()
  148. {
  149. Close();
  150. }
  151. private unsafe void Dispose(bool disposing)
  152. {
  153. if (_store != null)
  154. {
  155. _resCache = null;
  156. if (disposing)
  157. {
  158. // Close the stream in a thread-safe way. This fix means
  159. // that we may call Close n times, but that's safe.
  160. BinaryReader copyOfStore = _store;
  161. _store = null;
  162. if (copyOfStore != null)
  163. copyOfStore.Close();
  164. }
  165. _store = null;
  166. _namePositions = null;
  167. _nameHashes = null;
  168. _ums = null;
  169. _namePositionsPtr = null;
  170. _nameHashesPtr = null;
  171. }
  172. }
  173. internal static unsafe int ReadUnalignedI4(int* p)
  174. {
  175. byte* buffer = (byte*)p;
  176. // Unaligned, little endian format
  177. return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24);
  178. }
  179. private void SkipString()
  180. {
  181. int stringLength = _store.Read7BitEncodedInt();
  182. if (stringLength < 0)
  183. {
  184. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  185. }
  186. _store.BaseStream.Seek(stringLength, SeekOrigin.Current);
  187. }
  188. private unsafe int GetNameHash(int index)
  189. {
  190. Debug.Assert(index >= 0 && index < _numResources, "Bad index into hash array. index: " + index);
  191. Debug.Assert((_ums == null && _nameHashes != null && _nameHashesPtr == null) ||
  192. (_ums != null && _nameHashes == null && _nameHashesPtr != null), "Internal state mangled.");
  193. if (_ums == null)
  194. return _nameHashes[index];
  195. else
  196. return ReadUnalignedI4(&_nameHashesPtr[index]);
  197. }
  198. private unsafe int GetNamePosition(int index)
  199. {
  200. Debug.Assert(index >= 0 && index < _numResources, "Bad index into name position array. index: " + index);
  201. Debug.Assert((_ums == null && _namePositions != null && _namePositionsPtr == null) ||
  202. (_ums != null && _namePositions == null && _namePositionsPtr != null), "Internal state mangled.");
  203. int r;
  204. if (_ums == null)
  205. r = _namePositions[index];
  206. else
  207. r = ReadUnalignedI4(&_namePositionsPtr[index]);
  208. if (r < 0 || r > _dataSectionOffset - _nameSectionOffset)
  209. {
  210. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, r));
  211. }
  212. return r;
  213. }
  214. IEnumerator IEnumerable.GetEnumerator()
  215. {
  216. return GetEnumerator();
  217. }
  218. public IDictionaryEnumerator GetEnumerator()
  219. {
  220. if (_resCache == null)
  221. throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  222. return new ResourceEnumerator(this);
  223. }
  224. internal ResourceEnumerator GetEnumeratorInternal()
  225. {
  226. return new ResourceEnumerator(this);
  227. }
  228. // From a name, finds the associated virtual offset for the data.
  229. // To read the data, seek to _dataSectionOffset + dataPos, then
  230. // read the resource type & data.
  231. // This does a binary search through the names.
  232. internal int FindPosForResource(string name)
  233. {
  234. Debug.Assert(_store != null, "ResourceReader is closed!");
  235. int hash = FastResourceComparer.HashFunction(name);
  236. // Binary search over the hashes. Use the _namePositions array to
  237. // determine where they exist in the underlying stream.
  238. int lo = 0;
  239. int hi = _numResources - 1;
  240. int index = -1;
  241. bool success = false;
  242. while (lo <= hi)
  243. {
  244. index = (lo + hi) >> 1;
  245. // Do NOT use subtraction here, since it will wrap for large
  246. // negative numbers.
  247. int currentHash = GetNameHash(index);
  248. int c;
  249. if (currentHash == hash)
  250. c = 0;
  251. else if (currentHash < hash)
  252. c = -1;
  253. else
  254. c = 1;
  255. if (c == 0)
  256. {
  257. success = true;
  258. break;
  259. }
  260. if (c < 0)
  261. lo = index + 1;
  262. else
  263. hi = index - 1;
  264. }
  265. if (!success)
  266. {
  267. return -1;
  268. }
  269. // index is the location in our hash array that corresponds with a
  270. // value in the namePositions array.
  271. // There could be collisions in our hash function. Check on both sides
  272. // of index to find the range of hash values that are equal to the
  273. // target hash value.
  274. if (lo != index)
  275. {
  276. lo = index;
  277. while (lo > 0 && GetNameHash(lo - 1) == hash)
  278. lo--;
  279. }
  280. if (hi != index)
  281. {
  282. hi = index;
  283. while (hi < _numResources - 1 && GetNameHash(hi + 1) == hash)
  284. hi++;
  285. }
  286. lock (this)
  287. {
  288. for (int i = lo; i <= hi; i++)
  289. {
  290. _store.BaseStream.Seek(_nameSectionOffset + GetNamePosition(i), SeekOrigin.Begin);
  291. if (CompareStringEqualsName(name))
  292. {
  293. int dataPos = _store.ReadInt32();
  294. if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
  295. {
  296. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
  297. }
  298. return dataPos;
  299. }
  300. }
  301. }
  302. return -1;
  303. }
  304. // This compares the String in the .resources file at the current position
  305. // with the string you pass in.
  306. // Whoever calls this method should make sure that they take a lock
  307. // so no one else can cause us to seek in the stream.
  308. private unsafe bool CompareStringEqualsName(string name)
  309. {
  310. Debug.Assert(_store != null, "ResourceReader is closed!");
  311. int byteLen = _store.Read7BitEncodedInt();
  312. if (byteLen < 0)
  313. {
  314. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  315. }
  316. if (_ums != null)
  317. {
  318. byte* bytes = _ums.PositionPointer;
  319. // Skip over the data in the Stream, positioning ourselves right after it.
  320. _ums.Seek(byteLen, SeekOrigin.Current);
  321. if (_ums.Position > _ums.Length)
  322. {
  323. throw new BadImageFormatException(SR.BadImageFormat_ResourcesNameTooLong);
  324. }
  325. // On 64-bit machines, these char*'s may be misaligned. Use a
  326. // byte-by-byte comparison instead.
  327. //return FastResourceComparer.CompareOrdinal((char*)bytes, byteLen/2, name) == 0;
  328. return FastResourceComparer.CompareOrdinal(bytes, byteLen, name) == 0;
  329. }
  330. else
  331. {
  332. // This code needs to be fast
  333. byte[] bytes = new byte[byteLen];
  334. int numBytesToRead = byteLen;
  335. while (numBytesToRead > 0)
  336. {
  337. int n = _store.Read(bytes, byteLen - numBytesToRead, numBytesToRead);
  338. if (n == 0)
  339. throw new BadImageFormatException(SR.BadImageFormat_ResourceNameCorrupted);
  340. numBytesToRead -= n;
  341. }
  342. return FastResourceComparer.CompareOrdinal(bytes, byteLen / 2, name) == 0;
  343. }
  344. }
  345. // This is used in the enumerator. The enumerator iterates from 0 to n
  346. // of our resources and this returns the resource name for a particular
  347. // index. The parameter is NOT a virtual offset.
  348. private unsafe string AllocateStringForNameIndex(int index, out int dataOffset)
  349. {
  350. Debug.Assert(_store != null, "ResourceReader is closed!");
  351. byte[] bytes;
  352. int byteLen;
  353. long nameVA = GetNamePosition(index);
  354. lock (this)
  355. {
  356. _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
  357. // Can't use _store.ReadString, since it's using UTF-8!
  358. byteLen = _store.Read7BitEncodedInt();
  359. if (byteLen < 0)
  360. {
  361. throw new BadImageFormatException(SR.BadImageFormat_NegativeStringLength);
  362. }
  363. if (_ums != null)
  364. {
  365. if (_ums.Position > _ums.Length - byteLen)
  366. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourcesIndexTooLong, index));
  367. string s = null;
  368. char* charPtr = (char*)_ums.PositionPointer;
  369. s = new string(charPtr, 0, byteLen / 2);
  370. _ums.Position += byteLen;
  371. dataOffset = _store.ReadInt32();
  372. if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
  373. {
  374. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
  375. }
  376. return s;
  377. }
  378. bytes = new byte[byteLen];
  379. // We must read byteLen bytes, or we have a corrupted file.
  380. // Use a blocking read in case the stream doesn't give us back
  381. // everything immediately.
  382. int count = byteLen;
  383. while (count > 0)
  384. {
  385. int n = _store.Read(bytes, byteLen - count, count);
  386. if (n == 0)
  387. throw new EndOfStreamException(SR.Format(SR.BadImageFormat_ResourceNameCorrupted_NameIndex, index));
  388. count -= n;
  389. }
  390. dataOffset = _store.ReadInt32();
  391. if (dataOffset < 0 || dataOffset >= _store.BaseStream.Length - _dataSectionOffset)
  392. {
  393. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataOffset));
  394. }
  395. }
  396. return Encoding.Unicode.GetString(bytes, 0, byteLen);
  397. }
  398. // This is used in the enumerator. The enumerator iterates from 0 to n
  399. // of our resources and this returns the resource value for a particular
  400. // index. The parameter is NOT a virtual offset.
  401. private object GetValueForNameIndex(int index)
  402. {
  403. Debug.Assert(_store != null, "ResourceReader is closed!");
  404. long nameVA = GetNamePosition(index);
  405. lock (this)
  406. {
  407. _store.BaseStream.Seek(nameVA + _nameSectionOffset, SeekOrigin.Begin);
  408. SkipString();
  409. int dataPos = _store.ReadInt32();
  410. if (dataPos < 0 || dataPos >= _store.BaseStream.Length - _dataSectionOffset)
  411. {
  412. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dataPos));
  413. }
  414. ResourceTypeCode junk;
  415. if (_version == 1)
  416. return LoadObjectV1(dataPos);
  417. else
  418. return LoadObjectV2(dataPos, out junk);
  419. }
  420. }
  421. // This takes a virtual offset into the data section and reads a String
  422. // from that location.
  423. // Anyone who calls LoadObject should make sure they take a lock so
  424. // no one can cause us to do a seek in here.
  425. internal string LoadString(int pos)
  426. {
  427. Debug.Assert(_store != null, "ResourceReader is closed!");
  428. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  429. string s = null;
  430. int typeIndex = _store.Read7BitEncodedInt();
  431. if (_version == 1)
  432. {
  433. if (typeIndex == -1)
  434. return null;
  435. if (FindType(typeIndex) != typeof(string))
  436. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, FindType(typeIndex).FullName));
  437. s = _store.ReadString();
  438. }
  439. else
  440. {
  441. ResourceTypeCode typeCode = (ResourceTypeCode)typeIndex;
  442. if (typeCode != ResourceTypeCode.String && typeCode != ResourceTypeCode.Null)
  443. {
  444. string typeString;
  445. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  446. typeString = typeCode.ToString();
  447. else
  448. typeString = FindType(typeCode - ResourceTypeCode.StartOfUserTypes).FullName;
  449. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotString_Type, typeString));
  450. }
  451. if (typeCode == ResourceTypeCode.String) // ignore Null
  452. s = _store.ReadString();
  453. }
  454. return s;
  455. }
  456. // Called from RuntimeResourceSet
  457. internal object LoadObject(int pos)
  458. {
  459. if (_version == 1)
  460. return LoadObjectV1(pos);
  461. ResourceTypeCode typeCode;
  462. return LoadObjectV2(pos, out typeCode);
  463. }
  464. internal object LoadObject(int pos, out ResourceTypeCode typeCode)
  465. {
  466. if (_version == 1)
  467. {
  468. object o = LoadObjectV1(pos);
  469. typeCode = (o is string) ? ResourceTypeCode.String : ResourceTypeCode.StartOfUserTypes;
  470. return o;
  471. }
  472. return LoadObjectV2(pos, out typeCode);
  473. }
  474. // This takes a virtual offset into the data section and reads an Object
  475. // from that location.
  476. // Anyone who calls LoadObject should make sure they take a lock so
  477. // no one can cause us to do a seek in here.
  478. internal object LoadObjectV1(int pos)
  479. {
  480. Debug.Assert(_store != null, "ResourceReader is closed!");
  481. Debug.Assert(_version == 1, ".resources file was not a V1 .resources file!");
  482. try
  483. {
  484. // mega try-catch performs exceptionally bad on x64; factored out body into
  485. // _LoadObjectV1 and wrap here.
  486. return _LoadObjectV1(pos);
  487. }
  488. catch (EndOfStreamException eof)
  489. {
  490. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
  491. }
  492. catch (ArgumentOutOfRangeException e)
  493. {
  494. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
  495. }
  496. }
  497. private object _LoadObjectV1(int pos)
  498. {
  499. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  500. int typeIndex = _store.Read7BitEncodedInt();
  501. if (typeIndex == -1)
  502. return null;
  503. Type type = FindType(typeIndex);
  504. // Consider putting in logic to see if this type is a
  505. // primitive or a value type first, so we can reach the
  506. // deserialization code faster for arbitrary objects.
  507. if (type == typeof(string))
  508. return _store.ReadString();
  509. else if (type == typeof(int))
  510. return _store.ReadInt32();
  511. else if (type == typeof(byte))
  512. return _store.ReadByte();
  513. else if (type == typeof(sbyte))
  514. return _store.ReadSByte();
  515. else if (type == typeof(short))
  516. return _store.ReadInt16();
  517. else if (type == typeof(long))
  518. return _store.ReadInt64();
  519. else if (type == typeof(ushort))
  520. return _store.ReadUInt16();
  521. else if (type == typeof(uint))
  522. return _store.ReadUInt32();
  523. else if (type == typeof(ulong))
  524. return _store.ReadUInt64();
  525. else if (type == typeof(float))
  526. return _store.ReadSingle();
  527. else if (type == typeof(double))
  528. return _store.ReadDouble();
  529. else if (type == typeof(DateTime))
  530. {
  531. // Ideally we should use DateTime's ToBinary & FromBinary,
  532. // but we can't for compatibility reasons.
  533. return new DateTime(_store.ReadInt64());
  534. }
  535. else if (type == typeof(TimeSpan))
  536. return new TimeSpan(_store.ReadInt64());
  537. else if (type == typeof(decimal))
  538. {
  539. int[] bits = new int[4];
  540. for (int i = 0; i < bits.Length; i++)
  541. bits[i] = _store.ReadInt32();
  542. return new decimal(bits);
  543. }
  544. else
  545. {
  546. return DeserializeObject(typeIndex);
  547. }
  548. }
  549. internal object LoadObjectV2(int pos, out ResourceTypeCode typeCode)
  550. {
  551. Debug.Assert(_store != null, "ResourceReader is closed!");
  552. Debug.Assert(_version >= 2, ".resources file was not a V2 (or higher) .resources file!");
  553. try
  554. {
  555. // mega try-catch performs exceptionally bad on x64; factored out body into
  556. // _LoadObjectV2 and wrap here.
  557. return _LoadObjectV2(pos, out typeCode);
  558. }
  559. catch (EndOfStreamException eof)
  560. {
  561. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, eof);
  562. }
  563. catch (ArgumentOutOfRangeException e)
  564. {
  565. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch, e);
  566. }
  567. }
  568. private object _LoadObjectV2(int pos, out ResourceTypeCode typeCode)
  569. {
  570. _store.BaseStream.Seek(_dataSectionOffset + pos, SeekOrigin.Begin);
  571. typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
  572. switch (typeCode)
  573. {
  574. case ResourceTypeCode.Null:
  575. return null;
  576. case ResourceTypeCode.String:
  577. return _store.ReadString();
  578. case ResourceTypeCode.Boolean:
  579. return _store.ReadBoolean();
  580. case ResourceTypeCode.Char:
  581. return (char)_store.ReadUInt16();
  582. case ResourceTypeCode.Byte:
  583. return _store.ReadByte();
  584. case ResourceTypeCode.SByte:
  585. return _store.ReadSByte();
  586. case ResourceTypeCode.Int16:
  587. return _store.ReadInt16();
  588. case ResourceTypeCode.UInt16:
  589. return _store.ReadUInt16();
  590. case ResourceTypeCode.Int32:
  591. return _store.ReadInt32();
  592. case ResourceTypeCode.UInt32:
  593. return _store.ReadUInt32();
  594. case ResourceTypeCode.Int64:
  595. return _store.ReadInt64();
  596. case ResourceTypeCode.UInt64:
  597. return _store.ReadUInt64();
  598. case ResourceTypeCode.Single:
  599. return _store.ReadSingle();
  600. case ResourceTypeCode.Double:
  601. return _store.ReadDouble();
  602. case ResourceTypeCode.Decimal:
  603. return _store.ReadDecimal();
  604. case ResourceTypeCode.DateTime:
  605. // Use DateTime's ToBinary & FromBinary.
  606. long data = _store.ReadInt64();
  607. return DateTime.FromBinary(data);
  608. case ResourceTypeCode.TimeSpan:
  609. long ticks = _store.ReadInt64();
  610. return new TimeSpan(ticks);
  611. // Special types
  612. case ResourceTypeCode.ByteArray:
  613. {
  614. int len = _store.ReadInt32();
  615. if (len < 0)
  616. {
  617. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  618. }
  619. if (_ums == null)
  620. {
  621. if (len > _store.BaseStream.Length)
  622. {
  623. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  624. }
  625. return _store.ReadBytes(len);
  626. }
  627. if (len > _ums.Length - _ums.Position)
  628. {
  629. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  630. }
  631. byte[] bytes = new byte[len];
  632. int r = _ums.Read(bytes, 0, len);
  633. Debug.Assert(r == len, "ResourceReader needs to use a blocking read here. (Call _store.ReadBytes(len)?)");
  634. return bytes;
  635. }
  636. case ResourceTypeCode.Stream:
  637. {
  638. int len = _store.ReadInt32();
  639. if (len < 0)
  640. {
  641. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  642. }
  643. if (_ums == null)
  644. {
  645. byte[] bytes = _store.ReadBytes(len);
  646. // Lifetime of memory == lifetime of this stream.
  647. return new PinnedBufferMemoryStream(bytes);
  648. }
  649. // make sure we don't create an UnmanagedMemoryStream that is longer than the resource stream.
  650. if (len > _ums.Length - _ums.Position)
  651. {
  652. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResourceDataLengthInvalid, len));
  653. }
  654. // For the case that we've memory mapped in the .resources
  655. // file, just return a Stream pointing to that block of memory.
  656. unsafe
  657. {
  658. return new UnmanagedMemoryStream(_ums.PositionPointer, len, len, FileAccess.Read);
  659. }
  660. }
  661. default:
  662. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  663. {
  664. throw new BadImageFormatException(SR.BadImageFormat_TypeMismatch);
  665. }
  666. break;
  667. }
  668. // Normal serialized objects
  669. int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
  670. return DeserializeObject(typeIndex);
  671. }
  672. private object DeserializeObject(int typeIndex)
  673. {
  674. if (!_permitDeserialization)
  675. {
  676. throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
  677. }
  678. if (_binaryFormatter == null)
  679. {
  680. InitializeBinaryFormatter();
  681. }
  682. Type type = FindType(typeIndex);
  683. object graph = s_deserializeMethod(_binaryFormatter, _store.BaseStream);
  684. // guard against corrupted resources
  685. if (graph.GetType() != type)
  686. throw new BadImageFormatException(SR.Format(SR.BadImageFormat_ResType_SerBlobMismatch, type.FullName, graph.GetType().FullName));
  687. return graph;
  688. }
  689. private void InitializeBinaryFormatter()
  690. {
  691. LazyInitializer.EnsureInitialized(ref s_binaryFormatterType, () =>
  692. Type.GetType("System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, System.Runtime.Serialization.Formatters, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
  693. throwOnError: true));
  694. LazyInitializer.EnsureInitialized(ref s_deserializeMethod, () =>
  695. {
  696. MethodInfo binaryFormatterDeserialize = s_binaryFormatterType.GetMethod("Deserialize", new Type[] { typeof(Stream) });
  697. // create an unbound delegate that can accept a BinaryFormatter instance as object
  698. return (Func<object, Stream, object>)typeof(ResourceReader)
  699. .GetMethod(nameof(CreateUntypedDelegate), BindingFlags.NonPublic | BindingFlags.Static)
  700. .MakeGenericMethod(s_binaryFormatterType)
  701. .Invoke(null, new object[] { binaryFormatterDeserialize });
  702. });
  703. _binaryFormatter = Activator.CreateInstance(s_binaryFormatterType);
  704. }
  705. // generic method that we specialize at runtime once we've loaded the BinaryFormatter type
  706. // permits creating an unbound delegate so that we can avoid reflection after the initial
  707. // lightup code completes.
  708. private static Func<object, Stream, object> CreateUntypedDelegate<TInstance>(MethodInfo method)
  709. {
  710. Func<TInstance, Stream, object> typedDelegate = (Func<TInstance, Stream, object>)Delegate.CreateDelegate(typeof(Func<TInstance, Stream, object>), null, method);
  711. return (obj, stream) => typedDelegate((TInstance)obj, stream);
  712. }
  713. // Reads in the header information for a .resources file. Verifies some
  714. // of the assumptions about this resource set, and builds the class table
  715. // for the default resource file format.
  716. private void ReadResources()
  717. {
  718. Debug.Assert(_store != null, "ResourceReader is closed!");
  719. try
  720. {
  721. // mega try-catch performs exceptionally bad on x64; factored out body into
  722. // _ReadResources and wrap here.
  723. _ReadResources();
  724. }
  725. catch (EndOfStreamException eof)
  726. {
  727. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, eof);
  728. }
  729. catch (IndexOutOfRangeException e)
  730. {
  731. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted, e);
  732. }
  733. }
  734. private void _ReadResources()
  735. {
  736. // Read ResourceManager header
  737. // Check for magic number
  738. int magicNum = _store.ReadInt32();
  739. if (magicNum != ResourceManager.MagicNumber)
  740. throw new ArgumentException(SR.Resources_StreamNotValid);
  741. // Assuming this is ResourceManager header V1 or greater, hopefully
  742. // after the version number there is a number of bytes to skip
  743. // to bypass the rest of the ResMgr header. For V2 or greater, we
  744. // use this to skip to the end of the header
  745. int resMgrHeaderVersion = _store.ReadInt32();
  746. int numBytesToSkip = _store.ReadInt32();
  747. if (numBytesToSkip < 0 || resMgrHeaderVersion < 0)
  748. {
  749. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  750. }
  751. if (resMgrHeaderVersion > 1)
  752. {
  753. _store.BaseStream.Seek(numBytesToSkip, SeekOrigin.Current);
  754. }
  755. else
  756. {
  757. // We don't care about numBytesToSkip; read the rest of the header
  758. // Read in type name for a suitable ResourceReader
  759. // Note ResourceWriter & InternalResGen use different Strings.
  760. string readerType = _store.ReadString();
  761. readerType = System.CoreLib.FixupCoreLibName(readerType);
  762. AssemblyName mscorlib = new AssemblyName(ResourceManager.MscorlibName);
  763. if (!ResourceManager.CompareNames(readerType, ResourceManager.ResReaderTypeName, mscorlib))
  764. throw new NotSupportedException(SR.Format(SR.NotSupported_WrongResourceReader_Type, readerType));
  765. // Skip over type name for a suitable ResourceSet
  766. SkipString();
  767. }
  768. // Read RuntimeResourceSet header
  769. // Do file version check
  770. int version = _store.ReadInt32();
  771. if (version != RuntimeResourceSet.Version && version != 1)
  772. throw new ArgumentException(SR.Format(SR.Arg_ResourceFileUnsupportedVersion, RuntimeResourceSet.Version, version));
  773. _version = version;
  774. _numResources = _store.ReadInt32();
  775. if (_numResources < 0)
  776. {
  777. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  778. }
  779. // Read type positions into type positions array.
  780. // But delay initialize the type table.
  781. int numTypes = _store.ReadInt32();
  782. if (numTypes < 0)
  783. {
  784. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  785. }
  786. _typeTable = new Type[numTypes];
  787. _typeNamePositions = new int[numTypes];
  788. for (int i = 0; i < numTypes; i++)
  789. {
  790. _typeNamePositions[i] = (int)_store.BaseStream.Position;
  791. // Skip over the Strings in the file. Don't create types.
  792. SkipString();
  793. }
  794. // Prepare to read in the array of name hashes
  795. // Note that the name hashes array is aligned to 8 bytes so
  796. // we can use pointers into it on 64 bit machines. (4 bytes
  797. // may be sufficient, but let's plan for the future)
  798. // Skip over alignment stuff. All public .resources files
  799. // should be aligned No need to verify the byte values.
  800. long pos = _store.BaseStream.Position;
  801. int alignBytes = ((int)pos) & 7;
  802. if (alignBytes != 0)
  803. {
  804. for (int i = 0; i < 8 - alignBytes; i++)
  805. {
  806. _store.ReadByte();
  807. }
  808. }
  809. // Read in the array of name hashes
  810. if (_ums == null)
  811. {
  812. _nameHashes = new int[_numResources];
  813. for (int i = 0; i < _numResources; i++)
  814. {
  815. _nameHashes[i] = _store.ReadInt32();
  816. }
  817. }
  818. else
  819. {
  820. int seekPos = unchecked(4 * _numResources);
  821. if (seekPos < 0)
  822. {
  823. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  824. }
  825. unsafe
  826. {
  827. _nameHashesPtr = (int*)_ums.PositionPointer;
  828. // Skip over the array of nameHashes.
  829. _ums.Seek(seekPos, SeekOrigin.Current);
  830. // get the position pointer once more to check that the whole table is within the stream
  831. byte* junk = _ums.PositionPointer;
  832. }
  833. }
  834. // Read in the array of relative positions for all the names.
  835. if (_ums == null)
  836. {
  837. _namePositions = new int[_numResources];
  838. for (int i = 0; i < _numResources; i++)
  839. {
  840. int namePosition = _store.ReadInt32();
  841. if (namePosition < 0)
  842. {
  843. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  844. }
  845. _namePositions[i] = namePosition;
  846. }
  847. }
  848. else
  849. {
  850. int seekPos = unchecked(4 * _numResources);
  851. if (seekPos < 0)
  852. {
  853. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  854. }
  855. unsafe
  856. {
  857. _namePositionsPtr = (int*)_ums.PositionPointer;
  858. // Skip over the array of namePositions.
  859. _ums.Seek(seekPos, SeekOrigin.Current);
  860. // get the position pointer once more to check that the whole table is within the stream
  861. byte* junk = _ums.PositionPointer;
  862. }
  863. }
  864. // Read location of data section.
  865. _dataSectionOffset = _store.ReadInt32();
  866. if (_dataSectionOffset < 0)
  867. {
  868. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  869. }
  870. // Store current location as start of name section
  871. _nameSectionOffset = _store.BaseStream.Position;
  872. // _nameSectionOffset should be <= _dataSectionOffset; if not, it's corrupt
  873. if (_dataSectionOffset < _nameSectionOffset)
  874. {
  875. throw new BadImageFormatException(SR.BadImageFormat_ResourcesHeaderCorrupted);
  876. }
  877. }
  878. // This allows us to delay-initialize the Type[]. This might be a
  879. // good startup time savings, since we might have to load assemblies
  880. // and initialize Reflection.
  881. private Type FindType(int typeIndex)
  882. {
  883. if (typeIndex < 0 || typeIndex >= _typeTable.Length)
  884. {
  885. throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
  886. }
  887. if (_typeTable[typeIndex] == null)
  888. {
  889. long oldPos = _store.BaseStream.Position;
  890. try
  891. {
  892. _store.BaseStream.Position = _typeNamePositions[typeIndex];
  893. string typeName = _store.ReadString();
  894. _typeTable[typeIndex] = Type.GetType(typeName, true);
  895. }
  896. // If serialization isn't supported, we convert FileNotFoundException to
  897. // NotSupportedException for consistency with v2. This is a corner-case, but the
  898. // idea is that we want to give the user a more accurate error message. Even if
  899. // the dependency were found, we know it will require serialization since it
  900. // can't be one of the types we special case. So if the dependency were found,
  901. // it would go down the serialization code path, resulting in NotSupported for
  902. // SKUs without serialization.
  903. //
  904. // We don't want to regress the expected case by checking the type info before
  905. // getting to Type.GetType -- this is costly with v1 resource formats.
  906. catch (FileNotFoundException)
  907. {
  908. throw new NotSupportedException(SR.NotSupported_ResourceObjectSerialization);
  909. }
  910. finally
  911. {
  912. _store.BaseStream.Position = oldPos;
  913. }
  914. }
  915. Debug.Assert(_typeTable[typeIndex] != null, "Should have found a type!");
  916. return _typeTable[typeIndex];
  917. }
  918. public void GetResourceData(string resourceName, out string resourceType, out byte[] resourceData)
  919. {
  920. if (resourceName == null)
  921. throw new ArgumentNullException(nameof(resourceName));
  922. if (_resCache == null)
  923. throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  924. // Get the type information from the data section. Also,
  925. // sort all of the data section's indexes to compute length of
  926. // the serialized data for this type (making sure to subtract
  927. // off the length of the type code).
  928. int[] sortedDataPositions = new int[_numResources];
  929. int dataPos = FindPosForResource(resourceName);
  930. if (dataPos == -1)
  931. {
  932. throw new ArgumentException(SR.Format(SR.Arg_ResourceNameNotExist, resourceName));
  933. }
  934. lock (this)
  935. {
  936. // Read all the positions of data within the data section.
  937. for (int i = 0; i < _numResources; i++)
  938. {
  939. _store.BaseStream.Position = _nameSectionOffset + GetNamePosition(i);
  940. // Skip over name of resource
  941. int numBytesToSkip = _store.Read7BitEncodedInt();
  942. if (numBytesToSkip < 0)
  943. {
  944. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesNameInvalidOffset, numBytesToSkip));
  945. }
  946. _store.BaseStream.Position += numBytesToSkip;
  947. int dPos = _store.ReadInt32();
  948. if (dPos < 0 || dPos >= _store.BaseStream.Length - _dataSectionOffset)
  949. {
  950. throw new FormatException(SR.Format(SR.BadImageFormat_ResourcesDataInvalidOffset, dPos));
  951. }
  952. sortedDataPositions[i] = dPos;
  953. }
  954. Array.Sort(sortedDataPositions);
  955. int index = Array.BinarySearch(sortedDataPositions, dataPos);
  956. Debug.Assert(index >= 0 && index < _numResources, "Couldn't find data position within sorted data positions array!");
  957. long nextData = (index < _numResources - 1) ? sortedDataPositions[index + 1] + _dataSectionOffset : _store.BaseStream.Length;
  958. int len = (int)(nextData - (dataPos + _dataSectionOffset));
  959. Debug.Assert(len >= 0 && len <= (int)_store.BaseStream.Length - dataPos + _dataSectionOffset, "Length was negative or outside the bounds of the file!");
  960. // Read type code then byte[]
  961. _store.BaseStream.Position = _dataSectionOffset + dataPos;
  962. ResourceTypeCode typeCode = (ResourceTypeCode)_store.Read7BitEncodedInt();
  963. if (typeCode < 0 || typeCode >= ResourceTypeCode.StartOfUserTypes + _typeTable.Length)
  964. {
  965. throw new BadImageFormatException(SR.BadImageFormat_InvalidType);
  966. }
  967. resourceType = TypeNameFromTypeCode(typeCode);
  968. // The length must be adjusted to subtract off the number
  969. // of bytes in the 7 bit encoded type code.
  970. len -= (int)(_store.BaseStream.Position - (_dataSectionOffset + dataPos));
  971. byte[] bytes = _store.ReadBytes(len);
  972. if (bytes.Length != len)
  973. throw new FormatException(SR.BadImageFormat_ResourceNameCorrupted);
  974. resourceData = bytes;
  975. }
  976. }
  977. private string TypeNameFromTypeCode(ResourceTypeCode typeCode)
  978. {
  979. Debug.Assert(typeCode >= 0, "can't be negative");
  980. if (typeCode < ResourceTypeCode.StartOfUserTypes)
  981. {
  982. Debug.Assert(!string.Equals(typeCode.ToString(), "LastPrimitive"), "Change ResourceTypeCode metadata order so LastPrimitive isn't what Enum.ToString prefers.");
  983. return "ResourceTypeCode." + typeCode.ToString();
  984. }
  985. else
  986. {
  987. int typeIndex = typeCode - ResourceTypeCode.StartOfUserTypes;
  988. Debug.Assert(typeIndex >= 0 && typeIndex < _typeTable.Length, "TypeCode is broken or corrupted!");
  989. long oldPos = _store.BaseStream.Position;
  990. try
  991. {
  992. _store.BaseStream.Position = _typeNamePositions[typeIndex];
  993. return _store.ReadString();
  994. }
  995. finally
  996. {
  997. _store.BaseStream.Position = oldPos;
  998. }
  999. }
  1000. }
  1001. internal sealed class ResourceEnumerator : IDictionaryEnumerator
  1002. {
  1003. private const int ENUM_DONE = int.MinValue;
  1004. private const int ENUM_NOT_STARTED = -1;
  1005. private ResourceReader _reader;
  1006. private bool _currentIsValid;
  1007. private int _currentName;
  1008. private int _dataPosition; // cached for case-insensitive table
  1009. internal ResourceEnumerator(ResourceReader reader)
  1010. {
  1011. _currentName = ENUM_NOT_STARTED;
  1012. _reader = reader;
  1013. _dataPosition = -2;
  1014. }
  1015. public bool MoveNext()
  1016. {
  1017. if (_currentName == _reader._numResources - 1 || _currentName == ENUM_DONE)
  1018. {
  1019. _currentIsValid = false;
  1020. _currentName = ENUM_DONE;
  1021. return false;
  1022. }
  1023. _currentIsValid = true;
  1024. _currentName++;
  1025. return true;
  1026. }
  1027. public object Key
  1028. {
  1029. get
  1030. {
  1031. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  1032. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  1033. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  1034. return _reader.AllocateStringForNameIndex(_currentName, out _dataPosition);
  1035. }
  1036. }
  1037. public object Current
  1038. {
  1039. get
  1040. {
  1041. return Entry;
  1042. }
  1043. }
  1044. // Warning: This requires that you call the Key or Entry property FIRST before calling it!
  1045. internal int DataPosition
  1046. {
  1047. get
  1048. {
  1049. return _dataPosition;
  1050. }
  1051. }
  1052. public DictionaryEntry Entry
  1053. {
  1054. get
  1055. {
  1056. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  1057. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  1058. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  1059. string key;
  1060. object value = null;
  1061. lock (_reader)
  1062. { // locks should be taken in the same order as in RuntimeResourceSet.GetObject to avoid deadlock
  1063. lock (_reader._resCache)
  1064. {
  1065. key = _reader.AllocateStringForNameIndex(_currentName, out _dataPosition); // AllocateStringForNameIndex could lock on _reader
  1066. ResourceLocator locator;
  1067. if (_reader._resCache.TryGetValue(key, out locator))
  1068. {
  1069. value = locator.Value;
  1070. }
  1071. if (value == null)
  1072. {
  1073. if (_dataPosition == -1)
  1074. value = _reader.GetValueForNameIndex(_currentName);
  1075. else
  1076. value = _reader.LoadObject(_dataPosition);
  1077. // If enumeration and subsequent lookups happen very
  1078. // frequently in the same process, add a ResourceLocator
  1079. // to _resCache here. But WinForms enumerates and
  1080. // just about everyone else does lookups. So caching
  1081. // here may bloat working set.
  1082. }
  1083. }
  1084. }
  1085. return new DictionaryEntry(key, value);
  1086. }
  1087. }
  1088. public object Value
  1089. {
  1090. get
  1091. {
  1092. if (_currentName == ENUM_DONE) throw new InvalidOperationException(SR.InvalidOperation_EnumEnded);
  1093. if (!_currentIsValid) throw new InvalidOperationException(SR.InvalidOperation_EnumNotStarted);
  1094. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  1095. // Consider using _resCache here, eventually, if
  1096. // this proves to be an interesting perf scenario.
  1097. // But mixing lookups and enumerators shouldn't be
  1098. // particularly compelling.
  1099. return _reader.GetValueForNameIndex(_currentName);
  1100. }
  1101. }
  1102. public void Reset()
  1103. {
  1104. if (_reader._resCache == null) throw new InvalidOperationException(SR.ResourceReaderIsClosed);
  1105. _currentIsValid = false;
  1106. _currentName = ENUM_NOT_STARTED;
  1107. }
  1108. }
  1109. }
  1110. }