ResourceManager.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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. using System.IO;
  5. using System.Globalization;
  6. using System.Reflection;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. namespace System.Resources
  10. {
  11. // Resource Manager exposes an assembly's resources to an application for
  12. // the correct CultureInfo. An example would be localizing text for a
  13. // user-visible message. Create a set of resource files listing a name
  14. // for a message and its value, compile them using ResGen, put them in
  15. // an appropriate place (your assembly manifest(?)), then create a Resource
  16. // Manager and query for the name of the message you want. The Resource
  17. // Manager will use CultureInfo.GetCurrentUICulture() to look
  18. // up a resource for your user's locale settings.
  19. //
  20. // Users should ideally create a resource file for every culture, or
  21. // at least a meaningful subset. The filenames will follow the naming
  22. // scheme:
  23. //
  24. // basename.culture name.resources
  25. //
  26. // The base name can be the name of your application, or depending on
  27. // the granularity desired, possibly the name of each class. The culture
  28. // name is determined from CultureInfo's Name property.
  29. // An example file name may be MyApp.en-US.resources for
  30. // MyApp's US English resources.
  31. //
  32. // -----------------
  33. // Refactoring Notes
  34. // -----------------
  35. // In Feb 08, began first step of refactoring ResourceManager to improve
  36. // maintainability (sd changelist 3012100). This resulted in breaking
  37. // apart the InternalGetResourceSet "big loop" so that the file-based
  38. // and manifest-based lookup was located in separate methods.
  39. // In Apr 08, continued refactoring so that file-based and manifest-based
  40. // concerns are encapsulated by separate classes. At construction, the
  41. // ResourceManager creates one of these classes based on whether the
  42. // RM will need to use file-based or manifest-based resources, and
  43. // afterwards refers to this through the interface IResourceGroveler.
  44. //
  45. // Serialization Compat: Ideally, we could have refactored further but
  46. // this would have broken serialization compat. For example, the
  47. // ResourceManager member UseManifest and UseSatelliteAssem are no
  48. // longer relevant on ResourceManager. Similarly, other members could
  49. // ideally be moved to the file-based or manifest-based classes
  50. // because they are only relevant for those types of lookup.
  51. //
  52. // Solution now / in the future:
  53. // For now, we simply use a mediator class so that we can keep these
  54. // members on ResourceManager but allow the file-based and manifest-
  55. // based classes to access/set these members in a uniform way. See
  56. // ResourceManagerMediator.
  57. // We encapsulate fallback logic in a fallback iterator class, so that
  58. // this logic isn't duplicated in several methods.
  59. //
  60. // In the future, we can also look into further factoring and better
  61. // design of IResourceGroveler interface to accommodate unused parameters
  62. // that don't make sense for either file-based or manifest-based lookup paths.
  63. //
  64. // Benefits of this refactoring:
  65. // - Makes it possible to understand what the ResourceManager does,
  66. // which is key for maintainability.
  67. // - Makes the ResourceManager more extensible by identifying and
  68. // encapsulating what varies
  69. // - Unearthed a bug that's been lurking a while in file-based
  70. // lookup paths for InternalGetResourceSet if createIfNotExists is
  71. // false.
  72. // - Reuses logic, e.g. by breaking apart the culture fallback into
  73. // the fallback iterator class, we don't have to repeat the
  74. // sometimes confusing fallback logic across multiple methods
  75. // - Fxcop violations reduced to 1/5th of original count. Most
  76. // importantly, code complexity violations disappeared.
  77. // - Finally, it got rid of dead code paths. Because the big loop was
  78. // so confusing, it masked unused chunks of code. Also, dividing
  79. // between file-based and manifest-based allowed functionaliy
  80. // unused in silverlight to fall out.
  81. //
  82. // Note: this type is integral to the construction of exception objects,
  83. // and sometimes this has to be done in low memory situtations (OOM) or
  84. // to create TypeInitializationExceptions due to failure of a static class
  85. // constructor. This type needs to be extremely careful and assume that
  86. // any type it references may have previously failed to construct, so statics
  87. // belonging to that type may not be initialized. FrameworkEventSource.Log
  88. // is one such example.
  89. //
  90. public partial class ResourceManager
  91. {
  92. internal class CultureNameResourceSetPair
  93. {
  94. public string? lastCultureName;
  95. public ResourceSet? lastResourceSet;
  96. }
  97. protected string BaseNameField;
  98. protected Assembly? MainAssembly; // Need the assembly manifest sometimes.
  99. private Dictionary<string, ResourceSet>? _resourceSets;
  100. private readonly string? _moduleDir; // For assembly-ignorant directory location
  101. private readonly Type? _locationInfo; // For Assembly or type-based directory layout
  102. private readonly Type? _userResourceSet; // Which ResourceSet instance to create
  103. private CultureInfo? _neutralResourcesCulture; // For perf optimizations.
  104. private CultureNameResourceSetPair? _lastUsedResourceCache;
  105. private bool _ignoreCase; // Whether case matters in GetString & GetObject
  106. private bool _useManifest; // Use Assembly manifest, or grovel disk.
  107. // Whether to fall back to the main assembly or a particular
  108. // satellite for the neutral resources.
  109. private UltimateResourceFallbackLocation _fallbackLoc;
  110. // Version number of satellite assemblies to look for. May be null.
  111. private Version? _satelliteContractVersion;
  112. private bool _lookedForSatelliteContractVersion;
  113. private IResourceGroveler _resourceGroveler = null!;
  114. public static readonly int MagicNumber = unchecked((int)0xBEEFCACE); // If only hex had a K...
  115. // Version number so ResMgr can get the ideal set of classes for you.
  116. // ResMgr header is:
  117. // 1) MagicNumber (little endian Int32)
  118. // 2) HeaderVersionNumber (little endian Int32)
  119. // 3) Num Bytes to skip past ResMgr header (little endian Int32)
  120. // 4) IResourceReader type name for this file (bytelength-prefixed UTF-8 String)
  121. // 5) ResourceSet type name for this file (bytelength-prefixed UTF8 String)
  122. public static readonly int HeaderVersionNumber = 1;
  123. // It would be better if we could use a _neutralCulture instead of calling
  124. // CultureInfo.InvariantCulture everywhere, but we run into problems with the .cctor. CultureInfo
  125. // initializes assembly, which initializes ResourceManager, which tries to get a CultureInfo which isn't
  126. // there yet because CultureInfo's class initializer hasn't finished. If we move SystemResMgr off of
  127. // Assembly (or at least make it an internal property) we should be able to circumvent this problem.
  128. // This is our min required ResourceSet type.
  129. private static readonly Type s_minResourceSet = typeof(ResourceSet);
  130. // These Strings are used to avoid using Reflection in CreateResourceSet.
  131. internal const string ResReaderTypeName = "System.Resources.ResourceReader";
  132. internal const string ResSetTypeName = "System.Resources.RuntimeResourceSet";
  133. internal const string ResFileExtension = ".resources";
  134. internal const int ResFileExtensionLength = 10;
  135. protected ResourceManager()
  136. {
  137. _lastUsedResourceCache = new CultureNameResourceSetPair();
  138. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  139. _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
  140. BaseNameField = string.Empty;
  141. }
  142. // Constructs a Resource Manager for files beginning with
  143. // baseName in the directory specified by resourceDir
  144. // or in the current directory. This Assembly-ignorant constructor is
  145. // mostly useful for testing your own ResourceSet implementation.
  146. //
  147. // A good example of a baseName might be "Strings". BaseName
  148. // should not end in ".resources".
  149. //
  150. // Note: System.Windows.Forms uses this method at design time.
  151. //
  152. private ResourceManager(string baseName, string resourceDir, Type? userResourceSet)
  153. {
  154. if (null == baseName)
  155. throw new ArgumentNullException(nameof(baseName));
  156. if (null == resourceDir)
  157. throw new ArgumentNullException(nameof(resourceDir));
  158. BaseNameField = baseName;
  159. _moduleDir = resourceDir;
  160. _userResourceSet = userResourceSet;
  161. _resourceSets = new Dictionary<string, ResourceSet>();
  162. _lastUsedResourceCache = new CultureNameResourceSetPair();
  163. _useManifest = false;
  164. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  165. _resourceGroveler = new FileBasedResourceGroveler(mediator);
  166. }
  167. public ResourceManager(string baseName, Assembly assembly)
  168. {
  169. if (null == baseName)
  170. throw new ArgumentNullException(nameof(baseName));
  171. if (null == assembly)
  172. throw new ArgumentNullException(nameof(assembly));
  173. if (!assembly.IsRuntimeImplemented())
  174. throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
  175. MainAssembly = assembly;
  176. BaseNameField = baseName;
  177. CommonAssemblyInit();
  178. }
  179. public ResourceManager(string baseName, Assembly assembly, Type? usingResourceSet)
  180. {
  181. if (null == baseName)
  182. throw new ArgumentNullException(nameof(baseName));
  183. if (null == assembly)
  184. throw new ArgumentNullException(nameof(assembly));
  185. if (!assembly.IsRuntimeImplemented())
  186. throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
  187. MainAssembly = assembly;
  188. BaseNameField = baseName;
  189. if (usingResourceSet != null && (usingResourceSet != s_minResourceSet) && !usingResourceSet.IsSubclassOf(s_minResourceSet))
  190. throw new ArgumentException(SR.Arg_ResMgrNotResSet, nameof(usingResourceSet));
  191. _userResourceSet = usingResourceSet;
  192. CommonAssemblyInit();
  193. }
  194. public ResourceManager(Type resourceSource)
  195. {
  196. if (null == resourceSource)
  197. throw new ArgumentNullException(nameof(resourceSource));
  198. if (!resourceSource.IsRuntimeImplemented())
  199. throw new ArgumentException(SR.Argument_MustBeRuntimeType);
  200. _locationInfo = resourceSource;
  201. MainAssembly = _locationInfo.Assembly;
  202. BaseNameField = resourceSource.Name;
  203. CommonAssemblyInit();
  204. }
  205. // Trying to unify code as much as possible, even though having to do a
  206. // security check in each constructor prevents it.
  207. private void CommonAssemblyInit()
  208. {
  209. #if FEATURE_APPX
  210. SetUapConfiguration();
  211. #endif
  212. // Now we can use the managed resources even when using PRI's to support the APIs GetObject, GetStream...etc.
  213. _useManifest = true;
  214. _resourceSets = new Dictionary<string, ResourceSet>();
  215. _lastUsedResourceCache = new CultureNameResourceSetPair();
  216. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  217. _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
  218. Debug.Assert(MainAssembly != null);
  219. _neutralResourcesCulture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(MainAssembly, out _fallbackLoc);
  220. }
  221. // Gets the base name for the ResourceManager.
  222. public virtual string BaseName => BaseNameField;
  223. // Whether we should ignore the capitalization of resources when calling
  224. // GetString or GetObject.
  225. public virtual bool IgnoreCase
  226. {
  227. get => _ignoreCase;
  228. set => _ignoreCase = value;
  229. }
  230. // Returns the Type of the ResourceSet the ResourceManager uses
  231. // to construct ResourceSets.
  232. public virtual Type ResourceSetType => _userResourceSet ?? typeof(RuntimeResourceSet);
  233. protected UltimateResourceFallbackLocation FallbackLocation
  234. {
  235. get => _fallbackLoc;
  236. set => _fallbackLoc = value;
  237. }
  238. // Tells the ResourceManager to call Close on all ResourceSets and
  239. // release all resources. This will shrink your working set by
  240. // potentially a substantial amount in a running application. Any
  241. // future resource lookups on this ResourceManager will be as
  242. // expensive as the very first lookup, since it will need to search
  243. // for files and load resources again.
  244. //
  245. // This may be useful in some complex threading scenarios, where
  246. // creating a new ResourceManager isn't quite the correct behavior.
  247. public virtual void ReleaseAllResources()
  248. {
  249. Debug.Assert(_resourceSets != null);
  250. Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
  251. // If any calls to Close throw, at least leave ourselves in a
  252. // consistent state.
  253. _resourceSets = new Dictionary<string, ResourceSet>();
  254. _lastUsedResourceCache = new CultureNameResourceSetPair();
  255. lock (localResourceSets)
  256. {
  257. foreach ((_, ResourceSet resourceSet) in localResourceSets)
  258. {
  259. resourceSet.Close();
  260. }
  261. }
  262. }
  263. public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type? usingResourceSet)
  264. {
  265. return new ResourceManager(baseName, resourceDir, usingResourceSet);
  266. }
  267. // Given a CultureInfo, GetResourceFileName generates the name for
  268. // the binary file for the given CultureInfo. This method uses
  269. // CultureInfo's Name property as part of the file name for all cultures
  270. // other than the invariant culture. This method does not touch the disk,
  271. // and is used only to construct what a resource file name (suitable for
  272. // passing to the ResourceReader constructor) or a manifest resource file
  273. // name should look like.
  274. //
  275. // This method can be overriden to look for a different extension,
  276. // such as ".ResX", or a completely different format for naming files.
  277. protected virtual string GetResourceFileName(CultureInfo culture)
  278. {
  279. // If this is the neutral culture, don't include the culture name.
  280. if (culture.HasInvariantCultureName)
  281. {
  282. return BaseNameField + ResFileExtension;
  283. }
  284. else
  285. {
  286. CultureInfo.VerifyCultureName(culture.Name, throwException: true);
  287. return BaseNameField + "." + culture.Name + ResFileExtension;
  288. }
  289. }
  290. // WARNING: This function must be kept in sync with ResourceFallbackManager.GetEnumerator()
  291. // Return the first ResourceSet, based on the first culture ResourceFallbackManager would return
  292. internal ResourceSet? GetFirstResourceSet(CultureInfo culture)
  293. {
  294. // Logic from ResourceFallbackManager.GetEnumerator()
  295. if (_neutralResourcesCulture != null && culture.Name == _neutralResourcesCulture.Name)
  296. {
  297. culture = CultureInfo.InvariantCulture;
  298. }
  299. if (_lastUsedResourceCache != null)
  300. {
  301. lock (_lastUsedResourceCache)
  302. {
  303. if (culture.Name == _lastUsedResourceCache.lastCultureName)
  304. return _lastUsedResourceCache.lastResourceSet;
  305. }
  306. }
  307. // Look in the ResourceSet table
  308. Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
  309. ResourceSet? rs = null;
  310. if (localResourceSets != null)
  311. {
  312. lock (localResourceSets)
  313. {
  314. localResourceSets.TryGetValue(culture.Name, out rs);
  315. }
  316. }
  317. if (rs != null)
  318. {
  319. // update the cache with the most recent ResourceSet
  320. if (_lastUsedResourceCache != null)
  321. {
  322. lock (_lastUsedResourceCache)
  323. {
  324. _lastUsedResourceCache.lastCultureName = culture.Name;
  325. _lastUsedResourceCache.lastResourceSet = rs;
  326. }
  327. }
  328. return rs;
  329. }
  330. return null;
  331. }
  332. // Looks up a set of resources for a particular CultureInfo. This is
  333. // not useful for most users of the ResourceManager - call
  334. // GetString() or GetObject() instead.
  335. //
  336. // The parameters let you control whether the ResourceSet is created
  337. // if it hasn't yet been loaded and if parent CultureInfos should be
  338. // loaded as well for resource inheritance.
  339. //
  340. public virtual ResourceSet? GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
  341. {
  342. if (null == culture)
  343. throw new ArgumentNullException(nameof(culture));
  344. Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
  345. ResourceSet? rs;
  346. if (localResourceSets != null)
  347. {
  348. lock (localResourceSets)
  349. {
  350. if (localResourceSets.TryGetValue(culture.Name, out rs))
  351. return rs;
  352. }
  353. }
  354. if (_useManifest && culture.HasInvariantCultureName)
  355. {
  356. string fileName = GetResourceFileName(culture);
  357. Debug.Assert(MainAssembly != null);
  358. Stream? stream = MainAssembly.GetManifestResourceStream(_locationInfo!, fileName);
  359. if (createIfNotExists && stream != null)
  360. {
  361. rs = ((ManifestBasedResourceGroveler)_resourceGroveler).CreateResourceSet(stream, MainAssembly);
  362. Debug.Assert(localResourceSets != null);
  363. AddResourceSet(localResourceSets, culture.Name, ref rs);
  364. return rs;
  365. }
  366. }
  367. return InternalGetResourceSet(culture, createIfNotExists, tryParents);
  368. }
  369. // InternalGetResourceSet is a non-threadsafe method where all the logic
  370. // for getting a resource set lives. Access to it is controlled by
  371. // threadsafe methods such as GetResourceSet, GetString, & GetObject.
  372. // This will take a minimal number of locks.
  373. protected virtual ResourceSet? InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
  374. {
  375. Debug.Assert(culture != null, "culture != null");
  376. Debug.Assert(_resourceSets != null);
  377. Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
  378. ResourceSet? rs = null;
  379. CultureInfo? foundCulture = null;
  380. lock (localResourceSets)
  381. {
  382. if (localResourceSets.TryGetValue(culture.Name, out rs))
  383. {
  384. return rs;
  385. }
  386. }
  387. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, tryParents);
  388. foreach (CultureInfo currentCultureInfo in mgr)
  389. {
  390. lock (localResourceSets)
  391. {
  392. if (localResourceSets.TryGetValue(currentCultureInfo.Name, out rs))
  393. {
  394. // we need to update the cache if we fellback
  395. if (culture != currentCultureInfo) foundCulture = currentCultureInfo;
  396. break;
  397. }
  398. }
  399. // InternalGetResourceSet will never be threadsafe. However, it must
  400. // be protected against reentrancy from the SAME THREAD. (ie, calling
  401. // GetSatelliteAssembly may send some window messages or trigger the
  402. // Assembly load event, which could fail then call back into the
  403. // ResourceManager). It's happened.
  404. rs = _resourceGroveler.GrovelForResourceSet(currentCultureInfo, localResourceSets,
  405. tryParents, createIfNotExists);
  406. // found a ResourceSet; we're done
  407. if (rs != null)
  408. {
  409. foundCulture = currentCultureInfo;
  410. break;
  411. }
  412. }
  413. if (rs != null && foundCulture != null)
  414. {
  415. // add entries to the cache for the cultures we have gone through
  416. // currentCultureInfo now refers to the culture that had resources.
  417. // update cultures starting from requested culture up to the culture
  418. // that had resources.
  419. foreach (CultureInfo updateCultureInfo in mgr)
  420. {
  421. AddResourceSet(localResourceSets, updateCultureInfo.Name, ref rs);
  422. // stop when we've added current or reached invariant (top of chain)
  423. if (updateCultureInfo == foundCulture)
  424. {
  425. break;
  426. }
  427. }
  428. }
  429. return rs;
  430. }
  431. // Simple helper to ease maintenance and improve readability.
  432. private static void AddResourceSet(Dictionary<string, ResourceSet> localResourceSets, string cultureName, ref ResourceSet rs)
  433. {
  434. // InternalGetResourceSet is both recursive and reentrant -
  435. // assembly load callbacks in particular are a way we can call
  436. // back into the ResourceManager in unexpectedly on the same thread.
  437. lock (localResourceSets)
  438. {
  439. // If another thread added this culture, return that.
  440. ResourceSet? lostRace;
  441. if (localResourceSets.TryGetValue(cultureName, out lostRace))
  442. {
  443. if (!object.ReferenceEquals(lostRace, rs))
  444. {
  445. // Note: In certain cases, we can be trying to add a ResourceSet for multiple
  446. // cultures on one thread, while a second thread added another ResourceSet for one
  447. // of those cultures. If there is a race condition we must make sure our ResourceSet
  448. // isn't in our dictionary before closing it.
  449. if (!localResourceSets.ContainsValue(rs))
  450. rs.Dispose();
  451. rs = lostRace;
  452. }
  453. }
  454. else
  455. {
  456. localResourceSets.Add(cultureName, rs);
  457. }
  458. }
  459. }
  460. protected static Version? GetSatelliteContractVersion(Assembly a)
  461. {
  462. // Ensure that the assembly reference is not null
  463. if (a == null)
  464. {
  465. throw new ArgumentNullException(nameof(a), SR.ArgumentNull_Assembly);
  466. }
  467. string? v = a.GetCustomAttribute<SatelliteContractVersionAttribute>()?.Version;
  468. if (v == null)
  469. {
  470. // Return null. The calling code will use the assembly version instead to avoid potential type
  471. // and library loads caused by CA lookup.
  472. return null;
  473. }
  474. if (!Version.TryParse(v, out Version? version))
  475. {
  476. throw new ArgumentException(SR.Format(SR.Arg_InvalidSatelliteContract_Asm_Ver, a, v));
  477. }
  478. return version;
  479. }
  480. protected static CultureInfo GetNeutralResourcesLanguage(Assembly a)
  481. {
  482. // This method should be obsolete - replace it with the one below.
  483. // Unfortunately, we made it protected.
  484. return ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(a, out _);
  485. }
  486. // IGNORES VERSION
  487. internal static bool IsDefaultType(string asmTypeName,
  488. string typeName)
  489. {
  490. Debug.Assert(asmTypeName != null, "asmTypeName was unexpectedly null");
  491. // First, compare type names
  492. int comma = asmTypeName.IndexOf(',');
  493. if (((comma == -1) ? asmTypeName.Length : comma) != typeName.Length)
  494. return false;
  495. // case sensitive
  496. if (string.Compare(asmTypeName, 0, typeName, 0, typeName.Length, StringComparison.Ordinal) != 0)
  497. return false;
  498. if (comma == -1)
  499. return true;
  500. // Now, compare assembly display names (IGNORES VERSION AND PROCESSORARCHITECTURE)
  501. // also, for mscorlib ignores everything, since that's what the binder is going to do
  502. while (char.IsWhiteSpace(asmTypeName[++comma])) ;
  503. // case insensitive
  504. AssemblyName an = new AssemblyName(asmTypeName.Substring(comma));
  505. // to match IsMscorlib() in VM
  506. return string.Equals(an.Name, "mscorlib", StringComparison.OrdinalIgnoreCase);
  507. }
  508. // Looks up a resource value for a particular name. Looks in the
  509. // current thread's CultureInfo, and if not found, all parent CultureInfos.
  510. // Returns null if the resource wasn't found.
  511. //
  512. public virtual string? GetString(string name)
  513. {
  514. return GetString(name, null);
  515. }
  516. // Looks up a resource value for a particular name. Looks in the
  517. // specified CultureInfo, and if not found, all parent CultureInfos.
  518. // Returns null if the resource wasn't found.
  519. //
  520. public virtual string? GetString(string name, CultureInfo? culture)
  521. {
  522. if (null == name)
  523. throw new ArgumentNullException(nameof(name));
  524. #if FEATURE_APPX
  525. if (_useUapResourceManagement)
  526. {
  527. // Throws WinRT hresults.
  528. Debug.Assert(_neutralResourcesCulture != null);
  529. return GetStringFromPRI(name, culture, _neutralResourcesCulture.Name);
  530. }
  531. #endif
  532. culture ??= CultureInfo.CurrentUICulture;
  533. ResourceSet? last = GetFirstResourceSet(culture);
  534. if (last != null)
  535. {
  536. string? value = last.GetString(name, _ignoreCase);
  537. if (value != null)
  538. return value;
  539. }
  540. // This is the CultureInfo hierarchy traversal code for resource
  541. // lookups, similar but necessarily orthogonal to the ResourceSet
  542. // lookup logic.
  543. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
  544. foreach (CultureInfo currentCultureInfo in mgr)
  545. {
  546. ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
  547. if (rs == null)
  548. break;
  549. if (rs != last)
  550. {
  551. string? value = rs.GetString(name, _ignoreCase);
  552. if (value != null)
  553. {
  554. // update last used ResourceSet
  555. if (_lastUsedResourceCache != null)
  556. {
  557. lock (_lastUsedResourceCache)
  558. {
  559. _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
  560. _lastUsedResourceCache.lastResourceSet = rs;
  561. }
  562. }
  563. return value;
  564. }
  565. last = rs;
  566. }
  567. }
  568. return null;
  569. }
  570. // Looks up a resource value for a particular name. Looks in the
  571. // current thread's CultureInfo, and if not found, all parent CultureInfos.
  572. // Returns null if the resource wasn't found.
  573. //
  574. public virtual object? GetObject(string name)
  575. {
  576. return GetObject(name, null, true);
  577. }
  578. // Looks up a resource value for a particular name. Looks in the
  579. // specified CultureInfo, and if not found, all parent CultureInfos.
  580. // Returns null if the resource wasn't found.
  581. public virtual object? GetObject(string name, CultureInfo? culture)
  582. {
  583. return GetObject(name, culture, true);
  584. }
  585. private object? GetObject(string name, CultureInfo? culture, bool wrapUnmanagedMemStream)
  586. {
  587. if (null == name)
  588. throw new ArgumentNullException(nameof(name));
  589. if (null == culture)
  590. {
  591. culture = CultureInfo.CurrentUICulture;
  592. }
  593. ResourceSet? last = GetFirstResourceSet(culture);
  594. if (last != null)
  595. {
  596. object? value = last.GetObject(name, _ignoreCase);
  597. if (value != null)
  598. {
  599. if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
  600. return new UnmanagedMemoryStreamWrapper(stream);
  601. else
  602. return value;
  603. }
  604. }
  605. // This is the CultureInfo hierarchy traversal code for resource
  606. // lookups, similar but necessarily orthogonal to the ResourceSet
  607. // lookup logic.
  608. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
  609. foreach (CultureInfo currentCultureInfo in mgr)
  610. {
  611. ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
  612. if (rs == null)
  613. break;
  614. if (rs != last)
  615. {
  616. object? value = rs.GetObject(name, _ignoreCase);
  617. if (value != null)
  618. {
  619. // update the last used ResourceSet
  620. if (_lastUsedResourceCache != null)
  621. {
  622. lock (_lastUsedResourceCache)
  623. {
  624. _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
  625. _lastUsedResourceCache.lastResourceSet = rs;
  626. }
  627. }
  628. if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
  629. return new UnmanagedMemoryStreamWrapper(stream);
  630. else
  631. return value;
  632. }
  633. last = rs;
  634. }
  635. }
  636. return null;
  637. }
  638. public UnmanagedMemoryStream? GetStream(string name)
  639. {
  640. return GetStream(name, null);
  641. }
  642. public UnmanagedMemoryStream? GetStream(string name, CultureInfo? culture)
  643. {
  644. object? obj = GetObject(name, culture, false);
  645. UnmanagedMemoryStream? ums = obj as UnmanagedMemoryStream;
  646. if (ums == null && obj != null)
  647. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotStream_Name, name));
  648. return ums;
  649. }
  650. internal class ResourceManagerMediator
  651. {
  652. private readonly ResourceManager _rm;
  653. internal ResourceManagerMediator(ResourceManager rm)
  654. {
  655. if (rm == null)
  656. {
  657. throw new ArgumentNullException(nameof(rm));
  658. }
  659. _rm = rm;
  660. }
  661. // NEEDED ONLY BY FILE-BASED
  662. internal string? ModuleDir => _rm._moduleDir;
  663. // NEEDED BOTH BY FILE-BASED AND ASSEMBLY-BASED
  664. internal Type? LocationInfo => _rm._locationInfo;
  665. internal Type? UserResourceSet => _rm._userResourceSet;
  666. internal string? BaseNameField => _rm.BaseNameField;
  667. internal CultureInfo? NeutralResourcesCulture
  668. {
  669. get => _rm._neutralResourcesCulture;
  670. set => _rm._neutralResourcesCulture = value;
  671. }
  672. internal string GetResourceFileName(CultureInfo culture) =>
  673. _rm.GetResourceFileName(culture);
  674. // NEEDED ONLY BY ASSEMBLY-BASED
  675. internal bool LookedForSatelliteContractVersion
  676. {
  677. get => _rm._lookedForSatelliteContractVersion;
  678. set => _rm._lookedForSatelliteContractVersion = value;
  679. }
  680. internal Version? SatelliteContractVersion
  681. {
  682. get => _rm._satelliteContractVersion;
  683. set => _rm._satelliteContractVersion = value;
  684. }
  685. internal static Version? ObtainSatelliteContractVersion(Assembly a) =>
  686. ResourceManager.GetSatelliteContractVersion(a);
  687. internal UltimateResourceFallbackLocation FallbackLoc
  688. {
  689. get => _rm.FallbackLocation;
  690. set => _rm._fallbackLoc = value;
  691. }
  692. internal Assembly? MainAssembly => _rm.MainAssembly;
  693. // this is weird because we have BaseNameField accessor above, but we're sticking
  694. // with it for compat.
  695. internal string BaseName => _rm.BaseName;
  696. }
  697. }
  698. }