ResourceManager.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845
  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 string? _moduleDir; // For assembly-ignorant directory location
  101. private Type? _locationInfo; // For Assembly or type-based directory layout
  102. private 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. //
  124. //It would be better if we could use _neutralCulture instead of calling
  125. //CultureInfo.InvariantCulture everywhere, but we run into problems with the .cctor. CultureInfo
  126. //initializes assembly, which initializes ResourceManager, which tries to get a CultureInfo which isn't
  127. //there yet because CultureInfo's class initializer hasn't finished. If we move SystemResMgr off of
  128. //Assembly (or at least make it an internal property) we should be able to circumvent this problem.
  129. //
  130. // private static CultureInfo _neutralCulture = null;
  131. // This is our min required ResourceSet type.
  132. private static readonly Type s_minResourceSet = typeof(ResourceSet);
  133. // These Strings are used to avoid using Reflection in CreateResourceSet.
  134. internal const string ResReaderTypeName = "System.Resources.ResourceReader";
  135. internal const string ResSetTypeName = "System.Resources.RuntimeResourceSet";
  136. internal const string ResFileExtension = ".resources";
  137. internal const int ResFileExtensionLength = 10;
  138. protected ResourceManager()
  139. {
  140. _lastUsedResourceCache = new CultureNameResourceSetPair();
  141. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  142. _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
  143. BaseNameField = string.Empty;
  144. }
  145. // Constructs a Resource Manager for files beginning with
  146. // baseName in the directory specified by resourceDir
  147. // or in the current directory. This Assembly-ignorant constructor is
  148. // mostly useful for testing your own ResourceSet implementation.
  149. //
  150. // A good example of a baseName might be "Strings". BaseName
  151. // should not end in ".resources".
  152. //
  153. // Note: System.Windows.Forms uses this method at design time.
  154. //
  155. private ResourceManager(string baseName, string resourceDir, Type? userResourceSet)
  156. {
  157. if (null == baseName)
  158. throw new ArgumentNullException(nameof(baseName));
  159. if (null == resourceDir)
  160. throw new ArgumentNullException(nameof(resourceDir));
  161. BaseNameField = baseName;
  162. _moduleDir = resourceDir;
  163. _userResourceSet = userResourceSet;
  164. _resourceSets = new Dictionary<string, ResourceSet>();
  165. _lastUsedResourceCache = new CultureNameResourceSetPair();
  166. _useManifest = false;
  167. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  168. _resourceGroveler = new FileBasedResourceGroveler(mediator);
  169. }
  170. public ResourceManager(string baseName, Assembly assembly)
  171. {
  172. if (null == baseName)
  173. throw new ArgumentNullException(nameof(baseName));
  174. if (null == assembly)
  175. throw new ArgumentNullException(nameof(assembly));
  176. if (!assembly.IsRuntimeImplemented())
  177. throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
  178. MainAssembly = assembly;
  179. BaseNameField = baseName;
  180. CommonAssemblyInit();
  181. }
  182. public ResourceManager(string baseName, Assembly assembly, Type? usingResourceSet)
  183. {
  184. if (null == baseName)
  185. throw new ArgumentNullException(nameof(baseName));
  186. if (null == assembly)
  187. throw new ArgumentNullException(nameof(assembly));
  188. if (!assembly.IsRuntimeImplemented())
  189. throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
  190. MainAssembly = assembly;
  191. BaseNameField = baseName;
  192. if (usingResourceSet != null && (usingResourceSet != s_minResourceSet) && !(usingResourceSet.IsSubclassOf(s_minResourceSet)))
  193. throw new ArgumentException(SR.Arg_ResMgrNotResSet, nameof(usingResourceSet));
  194. _userResourceSet = usingResourceSet;
  195. CommonAssemblyInit();
  196. }
  197. public ResourceManager(Type resourceSource)
  198. {
  199. if (null == resourceSource)
  200. throw new ArgumentNullException(nameof(resourceSource));
  201. if (!resourceSource.IsRuntimeImplemented())
  202. throw new ArgumentException(SR.Argument_MustBeRuntimeType);
  203. _locationInfo = resourceSource;
  204. MainAssembly = _locationInfo.Assembly;
  205. BaseNameField = resourceSource.Name;
  206. CommonAssemblyInit();
  207. }
  208. // Trying to unify code as much as possible, even though having to do a
  209. // security check in each constructor prevents it.
  210. private void CommonAssemblyInit()
  211. {
  212. #if FEATURE_APPX || ENABLE_WINRT
  213. SetUapConfiguration();
  214. #endif
  215. // Now we can use the managed resources even when using PRI's to support the APIs GetObject, GetStream...etc.
  216. _useManifest = true;
  217. _resourceSets = new Dictionary<string, ResourceSet>();
  218. _lastUsedResourceCache = new CultureNameResourceSetPair();
  219. ResourceManagerMediator mediator = new ResourceManagerMediator(this);
  220. _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
  221. Debug.Assert(MainAssembly != null);
  222. _neutralResourcesCulture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(MainAssembly, out _fallbackLoc);
  223. }
  224. // Gets the base name for the ResourceManager.
  225. public virtual string BaseName
  226. {
  227. get { return BaseNameField; }
  228. }
  229. // Whether we should ignore the capitalization of resources when calling
  230. // GetString or GetObject.
  231. public virtual bool IgnoreCase
  232. {
  233. get { return _ignoreCase; }
  234. set { _ignoreCase = value; }
  235. }
  236. // Returns the Type of the ResourceSet the ResourceManager uses
  237. // to construct ResourceSets.
  238. public virtual Type ResourceSetType
  239. {
  240. get { return (_userResourceSet == null) ? typeof(RuntimeResourceSet) : _userResourceSet; }
  241. }
  242. protected UltimateResourceFallbackLocation FallbackLocation
  243. {
  244. get { return _fallbackLoc; }
  245. set { _fallbackLoc = value; }
  246. }
  247. // Tells the ResourceManager to call Close on all ResourceSets and
  248. // release all resources. This will shrink your working set by
  249. // potentially a substantial amount in a running application. Any
  250. // future resource lookups on this ResourceManager will be as
  251. // expensive as the very first lookup, since it will need to search
  252. // for files and load resources again.
  253. //
  254. // This may be useful in some complex threading scenarios, where
  255. // creating a new ResourceManager isn't quite the correct behavior.
  256. public virtual void ReleaseAllResources()
  257. {
  258. Debug.Assert(_resourceSets != null);
  259. Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
  260. // If any calls to Close throw, at least leave ourselves in a
  261. // consistent state.
  262. _resourceSets = new Dictionary<string, ResourceSet>();
  263. _lastUsedResourceCache = new CultureNameResourceSetPair();
  264. lock (localResourceSets)
  265. {
  266. foreach ((_, ResourceSet resourceSet) in localResourceSets)
  267. {
  268. resourceSet.Close();
  269. }
  270. }
  271. }
  272. public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type? usingResourceSet)
  273. {
  274. return new ResourceManager(baseName, resourceDir, usingResourceSet);
  275. }
  276. // Given a CultureInfo, GetResourceFileName generates the name for
  277. // the binary file for the given CultureInfo. This method uses
  278. // CultureInfo's Name property as part of the file name for all cultures
  279. // other than the invariant culture. This method does not touch the disk,
  280. // and is used only to construct what a resource file name (suitable for
  281. // passing to the ResourceReader constructor) or a manifest resource file
  282. // name should look like.
  283. //
  284. // This method can be overriden to look for a different extension,
  285. // such as ".ResX", or a completely different format for naming files.
  286. protected virtual string GetResourceFileName(CultureInfo culture)
  287. {
  288. // If this is the neutral culture, don't include the culture name.
  289. if (culture.HasInvariantCultureName)
  290. {
  291. return BaseNameField + ResFileExtension;
  292. }
  293. else
  294. {
  295. CultureInfo.VerifyCultureName(culture.Name, throwException: true);
  296. return BaseNameField + "." + culture.Name + ResFileExtension;
  297. }
  298. }
  299. // WARNING: This function must be kept in sync with ResourceFallbackManager.GetEnumerator()
  300. // Return the first ResourceSet, based on the first culture ResourceFallbackManager would return
  301. internal ResourceSet? GetFirstResourceSet(CultureInfo culture)
  302. {
  303. // Logic from ResourceFallbackManager.GetEnumerator()
  304. if (_neutralResourcesCulture != null && culture.Name == _neutralResourcesCulture.Name)
  305. {
  306. culture = CultureInfo.InvariantCulture;
  307. }
  308. if (_lastUsedResourceCache != null)
  309. {
  310. lock (_lastUsedResourceCache)
  311. {
  312. if (culture.Name == _lastUsedResourceCache.lastCultureName)
  313. return _lastUsedResourceCache.lastResourceSet;
  314. }
  315. }
  316. // Look in the ResourceSet table
  317. Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
  318. ResourceSet? rs = null;
  319. if (localResourceSets != null)
  320. {
  321. lock (localResourceSets)
  322. {
  323. localResourceSets.TryGetValue(culture.Name, out rs);
  324. }
  325. }
  326. if (rs != null)
  327. {
  328. // update the cache with the most recent ResourceSet
  329. if (_lastUsedResourceCache != null)
  330. {
  331. lock (_lastUsedResourceCache)
  332. {
  333. _lastUsedResourceCache.lastCultureName = culture.Name;
  334. _lastUsedResourceCache.lastResourceSet = rs;
  335. }
  336. }
  337. return rs;
  338. }
  339. return null;
  340. }
  341. // Looks up a set of resources for a particular CultureInfo. This is
  342. // not useful for most users of the ResourceManager - call
  343. // GetString() or GetObject() instead.
  344. //
  345. // The parameters let you control whether the ResourceSet is created
  346. // if it hasn't yet been loaded and if parent CultureInfos should be
  347. // loaded as well for resource inheritance.
  348. //
  349. public virtual ResourceSet? GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
  350. {
  351. if (null == culture)
  352. throw new ArgumentNullException(nameof(culture));
  353. Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
  354. ResourceSet? rs;
  355. if (localResourceSets != null)
  356. {
  357. lock (localResourceSets)
  358. {
  359. if (localResourceSets.TryGetValue(culture.Name, out rs))
  360. return rs;
  361. }
  362. }
  363. if (_useManifest && culture.HasInvariantCultureName)
  364. {
  365. string fileName = GetResourceFileName(culture);
  366. Debug.Assert(MainAssembly != null);
  367. Stream? stream = MainAssembly.GetManifestResourceStream(_locationInfo!, fileName);
  368. if (createIfNotExists && stream != null)
  369. {
  370. rs = ((ManifestBasedResourceGroveler)_resourceGroveler).CreateResourceSet(stream, MainAssembly);
  371. Debug.Assert(localResourceSets != null);
  372. AddResourceSet(localResourceSets, culture.Name, ref rs);
  373. return rs;
  374. }
  375. }
  376. return InternalGetResourceSet(culture, createIfNotExists, tryParents);
  377. }
  378. // InternalGetResourceSet is a non-threadsafe method where all the logic
  379. // for getting a resource set lives. Access to it is controlled by
  380. // threadsafe methods such as GetResourceSet, GetString, & GetObject.
  381. // This will take a minimal number of locks.
  382. protected virtual ResourceSet? InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
  383. {
  384. Debug.Assert(culture != null, "culture != null");
  385. Debug.Assert(_resourceSets != null);
  386. Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
  387. ResourceSet? rs = null;
  388. CultureInfo? foundCulture = null;
  389. lock (localResourceSets)
  390. {
  391. if (localResourceSets.TryGetValue(culture.Name, out rs))
  392. {
  393. return rs;
  394. }
  395. }
  396. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, tryParents);
  397. foreach (CultureInfo currentCultureInfo in mgr)
  398. {
  399. lock (localResourceSets)
  400. {
  401. if (localResourceSets.TryGetValue(currentCultureInfo.Name, out rs))
  402. {
  403. // we need to update the cache if we fellback
  404. if (culture != currentCultureInfo) foundCulture = currentCultureInfo;
  405. break;
  406. }
  407. }
  408. // InternalGetResourceSet will never be threadsafe. However, it must
  409. // be protected against reentrancy from the SAME THREAD. (ie, calling
  410. // GetSatelliteAssembly may send some window messages or trigger the
  411. // Assembly load event, which could fail then call back into the
  412. // ResourceManager). It's happened.
  413. rs = _resourceGroveler.GrovelForResourceSet(currentCultureInfo, localResourceSets,
  414. tryParents, createIfNotExists);
  415. // found a ResourceSet; we're done
  416. if (rs != null)
  417. {
  418. foundCulture = currentCultureInfo;
  419. break;
  420. }
  421. }
  422. if (rs != null && foundCulture != null)
  423. {
  424. // add entries to the cache for the cultures we have gone through
  425. // currentCultureInfo now refers to the culture that had resources.
  426. // update cultures starting from requested culture up to the culture
  427. // that had resources.
  428. foreach (CultureInfo updateCultureInfo in mgr)
  429. {
  430. AddResourceSet(localResourceSets, updateCultureInfo.Name, ref rs);
  431. // stop when we've added current or reached invariant (top of chain)
  432. if (updateCultureInfo == foundCulture)
  433. {
  434. break;
  435. }
  436. }
  437. }
  438. return rs;
  439. }
  440. // Simple helper to ease maintenance and improve readability.
  441. private static void AddResourceSet(Dictionary<string, ResourceSet> localResourceSets, string cultureName, ref ResourceSet rs)
  442. {
  443. // InternalGetResourceSet is both recursive and reentrant -
  444. // assembly load callbacks in particular are a way we can call
  445. // back into the ResourceManager in unexpectedly on the same thread.
  446. lock (localResourceSets)
  447. {
  448. // If another thread added this culture, return that.
  449. ResourceSet? lostRace;
  450. if (localResourceSets.TryGetValue(cultureName, out lostRace))
  451. {
  452. if (!object.ReferenceEquals(lostRace, rs))
  453. {
  454. // Note: In certain cases, we can be trying to add a ResourceSet for multiple
  455. // cultures on one thread, while a second thread added another ResourceSet for one
  456. // of those cultures. If there is a race condition we must make sure our ResourceSet
  457. // isn't in our dictionary before closing it.
  458. if (!localResourceSets.ContainsValue(rs))
  459. rs.Dispose();
  460. rs = lostRace;
  461. }
  462. }
  463. else
  464. {
  465. localResourceSets.Add(cultureName, rs);
  466. }
  467. }
  468. }
  469. protected static Version? GetSatelliteContractVersion(Assembly a)
  470. {
  471. // Ensure that the assembly reference is not null
  472. if (a == null)
  473. {
  474. throw new ArgumentNullException(nameof(a), SR.ArgumentNull_Assembly);
  475. }
  476. string? v = a.GetCustomAttribute<SatelliteContractVersionAttribute>()?.Version;
  477. if (v == null)
  478. {
  479. // Return null. The calling code will use the assembly version instead to avoid potential type
  480. // and library loads caused by CA lookup.
  481. return null;
  482. }
  483. if (!Version.TryParse(v, out Version? version))
  484. {
  485. throw new ArgumentException(SR.Format(SR.Arg_InvalidSatelliteContract_Asm_Ver, a, v));
  486. }
  487. return version;
  488. }
  489. protected static CultureInfo GetNeutralResourcesLanguage(Assembly a)
  490. {
  491. // This method should be obsolete - replace it with the one below.
  492. // Unfortunately, we made it protected.
  493. return ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(a, out _);
  494. }
  495. // IGNORES VERSION
  496. internal static bool IsDefaultType(string asmTypeName,
  497. string typeName)
  498. {
  499. Debug.Assert(asmTypeName != null, "asmTypeName was unexpectedly null");
  500. // First, compare type names
  501. int comma = asmTypeName.IndexOf(',');
  502. if (((comma == -1) ? asmTypeName.Length : comma) != typeName.Length)
  503. return false;
  504. // case sensitive
  505. if (string.Compare(asmTypeName, 0, typeName, 0, typeName.Length, StringComparison.Ordinal) != 0)
  506. return false;
  507. if (comma == -1)
  508. return true;
  509. // Now, compare assembly display names (IGNORES VERSION AND PROCESSORARCHITECTURE)
  510. // also, for mscorlib ignores everything, since that's what the binder is going to do
  511. while (char.IsWhiteSpace(asmTypeName[++comma])) ;
  512. // case insensitive
  513. AssemblyName an = new AssemblyName(asmTypeName.Substring(comma));
  514. // to match IsMscorlib() in VM
  515. return string.Equals(an.Name, "mscorlib", StringComparison.OrdinalIgnoreCase);
  516. }
  517. // Looks up a resource value for a particular name. Looks in the
  518. // current thread's CultureInfo, and if not found, all parent CultureInfos.
  519. // Returns null if the resource wasn't found.
  520. //
  521. public virtual string? GetString(string name)
  522. {
  523. return GetString(name, null);
  524. }
  525. // Looks up a resource value for a particular name. Looks in the
  526. // specified CultureInfo, and if not found, all parent CultureInfos.
  527. // Returns null if the resource wasn't found.
  528. //
  529. public virtual string? GetString(string name, CultureInfo? culture)
  530. {
  531. if (null == name)
  532. throw new ArgumentNullException(nameof(name));
  533. #if FEATURE_APPX || ENABLE_WINRT
  534. if (_useUapResourceManagement)
  535. {
  536. // Throws WinRT hresults.
  537. Debug.Assert(_neutralResourcesCulture != null);
  538. return GetStringFromPRI(name, culture, _neutralResourcesCulture.Name);
  539. }
  540. #endif
  541. if (culture == null)
  542. {
  543. culture = CultureInfo.CurrentUICulture;
  544. }
  545. ResourceSet? last = GetFirstResourceSet(culture);
  546. if (last != null)
  547. {
  548. string? value = last.GetString(name, _ignoreCase);
  549. if (value != null)
  550. return value;
  551. }
  552. // This is the CultureInfo hierarchy traversal code for resource
  553. // lookups, similar but necessarily orthogonal to the ResourceSet
  554. // lookup logic.
  555. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
  556. foreach (CultureInfo currentCultureInfo in mgr)
  557. {
  558. ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
  559. if (rs == null)
  560. break;
  561. if (rs != last)
  562. {
  563. string? value = rs.GetString(name, _ignoreCase);
  564. if (value != null)
  565. {
  566. // update last used ResourceSet
  567. if (_lastUsedResourceCache != null)
  568. {
  569. lock (_lastUsedResourceCache)
  570. {
  571. _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
  572. _lastUsedResourceCache.lastResourceSet = rs;
  573. }
  574. }
  575. return value;
  576. }
  577. last = rs;
  578. }
  579. }
  580. return null;
  581. }
  582. // Looks up a resource value for a particular name. Looks in the
  583. // current thread's CultureInfo, and if not found, all parent CultureInfos.
  584. // Returns null if the resource wasn't found.
  585. //
  586. public virtual object? GetObject(string name)
  587. {
  588. return GetObject(name, null, true);
  589. }
  590. // Looks up a resource value for a particular name. Looks in the
  591. // specified CultureInfo, and if not found, all parent CultureInfos.
  592. // Returns null if the resource wasn't found.
  593. public virtual object? GetObject(string name, CultureInfo? culture)
  594. {
  595. return GetObject(name, culture, true);
  596. }
  597. private object? GetObject(string name, CultureInfo? culture, bool wrapUnmanagedMemStream)
  598. {
  599. if (null == name)
  600. throw new ArgumentNullException(nameof(name));
  601. if (null == culture)
  602. {
  603. culture = CultureInfo.CurrentUICulture;
  604. }
  605. ResourceSet? last = GetFirstResourceSet(culture);
  606. if (last != null)
  607. {
  608. object? value = last.GetObject(name, _ignoreCase);
  609. if (value != null)
  610. {
  611. if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
  612. return new UnmanagedMemoryStreamWrapper(stream);
  613. else
  614. return value;
  615. }
  616. }
  617. // This is the CultureInfo hierarchy traversal code for resource
  618. // lookups, similar but necessarily orthogonal to the ResourceSet
  619. // lookup logic.
  620. ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
  621. foreach (CultureInfo currentCultureInfo in mgr)
  622. {
  623. ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
  624. if (rs == null)
  625. break;
  626. if (rs != last)
  627. {
  628. object? value = rs.GetObject(name, _ignoreCase);
  629. if (value != null)
  630. {
  631. // update the last used ResourceSet
  632. if (_lastUsedResourceCache != null)
  633. {
  634. lock (_lastUsedResourceCache)
  635. {
  636. _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
  637. _lastUsedResourceCache.lastResourceSet = rs;
  638. }
  639. }
  640. if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
  641. return new UnmanagedMemoryStreamWrapper(stream);
  642. else
  643. return value;
  644. }
  645. last = rs;
  646. }
  647. }
  648. return null;
  649. }
  650. public UnmanagedMemoryStream? GetStream(string name)
  651. {
  652. return GetStream(name, null);
  653. }
  654. public UnmanagedMemoryStream? GetStream(string name, CultureInfo? culture)
  655. {
  656. object? obj = GetObject(name, culture, false);
  657. UnmanagedMemoryStream? ums = obj as UnmanagedMemoryStream;
  658. if (ums == null && obj != null)
  659. throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotStream_Name, name));
  660. return ums;
  661. }
  662. internal class ResourceManagerMediator
  663. {
  664. private ResourceManager _rm;
  665. internal ResourceManagerMediator(ResourceManager rm)
  666. {
  667. if (rm == null)
  668. {
  669. throw new ArgumentNullException(nameof(rm));
  670. }
  671. _rm = rm;
  672. }
  673. // NEEDED ONLY BY FILE-BASED
  674. internal string? ModuleDir
  675. {
  676. get { return _rm._moduleDir; }
  677. }
  678. // NEEDED BOTH BY FILE-BASED AND ASSEMBLY-BASED
  679. internal Type? LocationInfo
  680. {
  681. get { return _rm._locationInfo; }
  682. }
  683. internal Type? UserResourceSet
  684. {
  685. get { return _rm._userResourceSet; }
  686. }
  687. internal string? BaseNameField
  688. {
  689. get { return _rm.BaseNameField; }
  690. }
  691. internal CultureInfo? NeutralResourcesCulture
  692. {
  693. get { return _rm._neutralResourcesCulture; }
  694. set { _rm._neutralResourcesCulture = value; }
  695. }
  696. internal string GetResourceFileName(CultureInfo culture)
  697. {
  698. return _rm.GetResourceFileName(culture);
  699. }
  700. // NEEDED ONLY BY ASSEMBLY-BASED
  701. internal bool LookedForSatelliteContractVersion
  702. {
  703. get { return _rm._lookedForSatelliteContractVersion; }
  704. set { _rm._lookedForSatelliteContractVersion = value; }
  705. }
  706. internal Version? SatelliteContractVersion
  707. {
  708. get { return _rm._satelliteContractVersion; }
  709. set { _rm._satelliteContractVersion = value; }
  710. }
  711. internal Version? ObtainSatelliteContractVersion(Assembly a)
  712. {
  713. return ResourceManager.GetSatelliteContractVersion(a);
  714. }
  715. internal UltimateResourceFallbackLocation FallbackLoc
  716. {
  717. get { return _rm.FallbackLocation; }
  718. set { _rm._fallbackLoc = value; }
  719. }
  720. internal Assembly? MainAssembly
  721. {
  722. get { return _rm.MainAssembly; }
  723. }
  724. // this is weird because we have BaseNameField accessor above, but we're sticking
  725. // with it for compat.
  726. internal string BaseName
  727. {
  728. get { return _rm.BaseName; }
  729. }
  730. }
  731. }
  732. }