Fix IDE0025 (use expression body for properties)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Resources / ResourceManager.cs
blobef3c4466c7a18af563874c72d6f014053f689acb
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System.IO;
6 using System.Globalization;
7 using System.Reflection;
8 using System.Collections.Generic;
9 using System.Diagnostics;
11 namespace System.Resources
13 // Resource Manager exposes an assembly's resources to an application for
14 // the correct CultureInfo. An example would be localizing text for a
15 // user-visible message. Create a set of resource files listing a name
16 // for a message and its value, compile them using ResGen, put them in
17 // an appropriate place (your assembly manifest(?)), then create a Resource
18 // Manager and query for the name of the message you want. The Resource
19 // Manager will use CultureInfo.GetCurrentUICulture() to look
20 // up a resource for your user's locale settings.
22 // Users should ideally create a resource file for every culture, or
23 // at least a meaningful subset. The filenames will follow the naming
24 // scheme:
26 // basename.culture name.resources
28 // The base name can be the name of your application, or depending on
29 // the granularity desired, possibly the name of each class. The culture
30 // name is determined from CultureInfo's Name property.
31 // An example file name may be MyApp.en-US.resources for
32 // MyApp's US English resources.
34 // -----------------
35 // Refactoring Notes
36 // -----------------
37 // In Feb 08, began first step of refactoring ResourceManager to improve
38 // maintainability (sd changelist 3012100). This resulted in breaking
39 // apart the InternalGetResourceSet "big loop" so that the file-based
40 // and manifest-based lookup was located in separate methods.
41 // In Apr 08, continued refactoring so that file-based and manifest-based
42 // concerns are encapsulated by separate classes. At construction, the
43 // ResourceManager creates one of these classes based on whether the
44 // RM will need to use file-based or manifest-based resources, and
45 // afterwards refers to this through the interface IResourceGroveler.
47 // Serialization Compat: Ideally, we could have refactored further but
48 // this would have broken serialization compat. For example, the
49 // ResourceManager member UseManifest and UseSatelliteAssem are no
50 // longer relevant on ResourceManager. Similarly, other members could
51 // ideally be moved to the file-based or manifest-based classes
52 // because they are only relevant for those types of lookup.
54 // Solution now / in the future:
55 // For now, we simply use a mediator class so that we can keep these
56 // members on ResourceManager but allow the file-based and manifest-
57 // based classes to access/set these members in a uniform way. See
58 // ResourceManagerMediator.
59 // We encapsulate fallback logic in a fallback iterator class, so that
60 // this logic isn't duplicated in several methods.
62 // In the future, we can also look into further factoring and better
63 // design of IResourceGroveler interface to accommodate unused parameters
64 // that don't make sense for either file-based or manifest-based lookup paths.
66 // Benefits of this refactoring:
67 // - Makes it possible to understand what the ResourceManager does,
68 // which is key for maintainability.
69 // - Makes the ResourceManager more extensible by identifying and
70 // encapsulating what varies
71 // - Unearthed a bug that's been lurking a while in file-based
72 // lookup paths for InternalGetResourceSet if createIfNotExists is
73 // false.
74 // - Reuses logic, e.g. by breaking apart the culture fallback into
75 // the fallback iterator class, we don't have to repeat the
76 // sometimes confusing fallback logic across multiple methods
77 // - Fxcop violations reduced to 1/5th of original count. Most
78 // importantly, code complexity violations disappeared.
79 // - Finally, it got rid of dead code paths. Because the big loop was
80 // so confusing, it masked unused chunks of code. Also, dividing
81 // between file-based and manifest-based allowed functionaliy
82 // unused in silverlight to fall out.
84 // Note: this type is integral to the construction of exception objects,
85 // and sometimes this has to be done in low memory situtations (OOM) or
86 // to create TypeInitializationExceptions due to failure of a static class
87 // constructor. This type needs to be extremely careful and assume that
88 // any type it references may have previously failed to construct, so statics
89 // belonging to that type may not be initialized. FrameworkEventSource.Log
90 // is one such example.
93 public partial class ResourceManager
95 internal class CultureNameResourceSetPair
97 public string? lastCultureName;
98 public ResourceSet? lastResourceSet;
101 protected string BaseNameField;
102 protected Assembly? MainAssembly; // Need the assembly manifest sometimes.
104 private Dictionary<string, ResourceSet>? _resourceSets;
105 private readonly string? _moduleDir; // For assembly-ignorant directory location
106 private readonly Type? _locationInfo; // For Assembly or type-based directory layout
107 private readonly Type? _userResourceSet; // Which ResourceSet instance to create
108 private CultureInfo? _neutralResourcesCulture; // For perf optimizations.
110 private CultureNameResourceSetPair? _lastUsedResourceCache;
112 private bool _ignoreCase; // Whether case matters in GetString & GetObject
114 private bool _useManifest; // Use Assembly manifest, or grovel disk.
116 // Whether to fall back to the main assembly or a particular
117 // satellite for the neutral resources.
118 private UltimateResourceFallbackLocation _fallbackLoc;
119 // Version number of satellite assemblies to look for. May be null.
120 private Version? _satelliteContractVersion;
121 private bool _lookedForSatelliteContractVersion;
123 private IResourceGroveler _resourceGroveler = null!;
125 public static readonly int MagicNumber = unchecked((int)0xBEEFCACE); // If only hex had a K...
127 // Version number so ResMgr can get the ideal set of classes for you.
128 // ResMgr header is:
129 // 1) MagicNumber (little endian Int32)
130 // 2) HeaderVersionNumber (little endian Int32)
131 // 3) Num Bytes to skip past ResMgr header (little endian Int32)
132 // 4) IResourceReader type name for this file (bytelength-prefixed UTF-8 String)
133 // 5) ResourceSet type name for this file (bytelength-prefixed UTF8 String)
134 public static readonly int HeaderVersionNumber = 1;
137 //It would be better if we could use _neutralCulture instead of calling
138 //CultureInfo.InvariantCulture everywhere, but we run into problems with the .cctor. CultureInfo
139 //initializes assembly, which initializes ResourceManager, which tries to get a CultureInfo which isn't
140 //there yet because CultureInfo's class initializer hasn't finished. If we move SystemResMgr off of
141 //Assembly (or at least make it an internal property) we should be able to circumvent this problem.
143 // private static CultureInfo _neutralCulture = null;
145 // This is our min required ResourceSet type.
146 private static readonly Type s_minResourceSet = typeof(ResourceSet);
147 // These Strings are used to avoid using Reflection in CreateResourceSet.
148 internal const string ResReaderTypeName = "System.Resources.ResourceReader";
149 internal const string ResSetTypeName = "System.Resources.RuntimeResourceSet";
150 internal const string ResFileExtension = ".resources";
151 internal const int ResFileExtensionLength = 10;
153 protected ResourceManager()
155 _lastUsedResourceCache = new CultureNameResourceSetPair();
156 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
157 _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
158 BaseNameField = string.Empty;
161 // Constructs a Resource Manager for files beginning with
162 // baseName in the directory specified by resourceDir
163 // or in the current directory. This Assembly-ignorant constructor is
164 // mostly useful for testing your own ResourceSet implementation.
166 // A good example of a baseName might be "Strings". BaseName
167 // should not end in ".resources".
169 // Note: System.Windows.Forms uses this method at design time.
171 private ResourceManager(string baseName, string resourceDir, Type? userResourceSet)
173 if (null == baseName)
174 throw new ArgumentNullException(nameof(baseName));
175 if (null == resourceDir)
176 throw new ArgumentNullException(nameof(resourceDir));
178 BaseNameField = baseName;
180 _moduleDir = resourceDir;
181 _userResourceSet = userResourceSet;
182 _resourceSets = new Dictionary<string, ResourceSet>();
183 _lastUsedResourceCache = new CultureNameResourceSetPair();
184 _useManifest = false;
186 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
187 _resourceGroveler = new FileBasedResourceGroveler(mediator);
190 public ResourceManager(string baseName, Assembly assembly)
192 if (null == baseName)
193 throw new ArgumentNullException(nameof(baseName));
194 if (null == assembly)
195 throw new ArgumentNullException(nameof(assembly));
196 if (!assembly.IsRuntimeImplemented())
197 throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
199 MainAssembly = assembly;
200 BaseNameField = baseName;
202 CommonAssemblyInit();
205 public ResourceManager(string baseName, Assembly assembly, Type? usingResourceSet)
207 if (null == baseName)
208 throw new ArgumentNullException(nameof(baseName));
209 if (null == assembly)
210 throw new ArgumentNullException(nameof(assembly));
211 if (!assembly.IsRuntimeImplemented())
212 throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
214 MainAssembly = assembly;
215 BaseNameField = baseName;
217 if (usingResourceSet != null && (usingResourceSet != s_minResourceSet) && !(usingResourceSet.IsSubclassOf(s_minResourceSet)))
218 throw new ArgumentException(SR.Arg_ResMgrNotResSet, nameof(usingResourceSet));
219 _userResourceSet = usingResourceSet;
221 CommonAssemblyInit();
224 public ResourceManager(Type resourceSource)
226 if (null == resourceSource)
227 throw new ArgumentNullException(nameof(resourceSource));
228 if (!resourceSource.IsRuntimeImplemented())
229 throw new ArgumentException(SR.Argument_MustBeRuntimeType);
231 _locationInfo = resourceSource;
232 MainAssembly = _locationInfo.Assembly;
233 BaseNameField = resourceSource.Name;
235 CommonAssemblyInit();
238 // Trying to unify code as much as possible, even though having to do a
239 // security check in each constructor prevents it.
240 private void CommonAssemblyInit()
242 #if FEATURE_APPX || ENABLE_WINRT
243 SetUapConfiguration();
244 #endif
246 // Now we can use the managed resources even when using PRI's to support the APIs GetObject, GetStream...etc.
247 _useManifest = true;
249 _resourceSets = new Dictionary<string, ResourceSet>();
250 _lastUsedResourceCache = new CultureNameResourceSetPair();
252 ResourceManagerMediator mediator = new ResourceManagerMediator(this);
253 _resourceGroveler = new ManifestBasedResourceGroveler(mediator);
255 Debug.Assert(MainAssembly != null);
256 _neutralResourcesCulture = ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(MainAssembly, out _fallbackLoc);
259 // Gets the base name for the ResourceManager.
260 public virtual string BaseName => BaseNameField;
262 // Whether we should ignore the capitalization of resources when calling
263 // GetString or GetObject.
264 public virtual bool IgnoreCase
266 get { return _ignoreCase; }
267 set { _ignoreCase = value; }
270 // Returns the Type of the ResourceSet the ResourceManager uses
271 // to construct ResourceSets.
272 public virtual Type ResourceSetType => _userResourceSet ?? typeof(RuntimeResourceSet);
274 protected UltimateResourceFallbackLocation FallbackLocation
276 get { return _fallbackLoc; }
277 set { _fallbackLoc = value; }
280 // Tells the ResourceManager to call Close on all ResourceSets and
281 // release all resources. This will shrink your working set by
282 // potentially a substantial amount in a running application. Any
283 // future resource lookups on this ResourceManager will be as
284 // expensive as the very first lookup, since it will need to search
285 // for files and load resources again.
287 // This may be useful in some complex threading scenarios, where
288 // creating a new ResourceManager isn't quite the correct behavior.
289 public virtual void ReleaseAllResources()
291 Debug.Assert(_resourceSets != null);
292 Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
294 // If any calls to Close throw, at least leave ourselves in a
295 // consistent state.
296 _resourceSets = new Dictionary<string, ResourceSet>();
297 _lastUsedResourceCache = new CultureNameResourceSetPair();
299 lock (localResourceSets)
301 foreach ((_, ResourceSet resourceSet) in localResourceSets)
303 resourceSet.Close();
308 public static ResourceManager CreateFileBasedResourceManager(string baseName, string resourceDir, Type? usingResourceSet)
310 return new ResourceManager(baseName, resourceDir, usingResourceSet);
313 // Given a CultureInfo, GetResourceFileName generates the name for
314 // the binary file for the given CultureInfo. This method uses
315 // CultureInfo's Name property as part of the file name for all cultures
316 // other than the invariant culture. This method does not touch the disk,
317 // and is used only to construct what a resource file name (suitable for
318 // passing to the ResourceReader constructor) or a manifest resource file
319 // name should look like.
321 // This method can be overriden to look for a different extension,
322 // such as ".ResX", or a completely different format for naming files.
323 protected virtual string GetResourceFileName(CultureInfo culture)
325 // If this is the neutral culture, don't include the culture name.
326 if (culture.HasInvariantCultureName)
328 return BaseNameField + ResFileExtension;
330 else
332 CultureInfo.VerifyCultureName(culture.Name, throwException: true);
333 return BaseNameField + "." + culture.Name + ResFileExtension;
337 // WARNING: This function must be kept in sync with ResourceFallbackManager.GetEnumerator()
338 // Return the first ResourceSet, based on the first culture ResourceFallbackManager would return
339 internal ResourceSet? GetFirstResourceSet(CultureInfo culture)
341 // Logic from ResourceFallbackManager.GetEnumerator()
342 if (_neutralResourcesCulture != null && culture.Name == _neutralResourcesCulture.Name)
344 culture = CultureInfo.InvariantCulture;
347 if (_lastUsedResourceCache != null)
349 lock (_lastUsedResourceCache)
351 if (culture.Name == _lastUsedResourceCache.lastCultureName)
352 return _lastUsedResourceCache.lastResourceSet;
356 // Look in the ResourceSet table
357 Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
358 ResourceSet? rs = null;
359 if (localResourceSets != null)
361 lock (localResourceSets)
363 localResourceSets.TryGetValue(culture.Name, out rs);
367 if (rs != null)
369 // update the cache with the most recent ResourceSet
370 if (_lastUsedResourceCache != null)
372 lock (_lastUsedResourceCache)
374 _lastUsedResourceCache.lastCultureName = culture.Name;
375 _lastUsedResourceCache.lastResourceSet = rs;
378 return rs;
381 return null;
384 // Looks up a set of resources for a particular CultureInfo. This is
385 // not useful for most users of the ResourceManager - call
386 // GetString() or GetObject() instead.
388 // The parameters let you control whether the ResourceSet is created
389 // if it hasn't yet been loaded and if parent CultureInfos should be
390 // loaded as well for resource inheritance.
392 public virtual ResourceSet? GetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
394 if (null == culture)
395 throw new ArgumentNullException(nameof(culture));
397 Dictionary<string, ResourceSet>? localResourceSets = _resourceSets;
398 ResourceSet? rs;
399 if (localResourceSets != null)
401 lock (localResourceSets)
403 if (localResourceSets.TryGetValue(culture.Name, out rs))
404 return rs;
408 if (_useManifest && culture.HasInvariantCultureName)
410 string fileName = GetResourceFileName(culture);
411 Debug.Assert(MainAssembly != null);
412 Stream? stream = MainAssembly.GetManifestResourceStream(_locationInfo!, fileName);
413 if (createIfNotExists && stream != null)
415 rs = ((ManifestBasedResourceGroveler)_resourceGroveler).CreateResourceSet(stream, MainAssembly);
416 Debug.Assert(localResourceSets != null);
417 AddResourceSet(localResourceSets, culture.Name, ref rs);
418 return rs;
422 return InternalGetResourceSet(culture, createIfNotExists, tryParents);
425 // InternalGetResourceSet is a non-threadsafe method where all the logic
426 // for getting a resource set lives. Access to it is controlled by
427 // threadsafe methods such as GetResourceSet, GetString, & GetObject.
428 // This will take a minimal number of locks.
429 protected virtual ResourceSet? InternalGetResourceSet(CultureInfo culture, bool createIfNotExists, bool tryParents)
431 Debug.Assert(culture != null, "culture != null");
432 Debug.Assert(_resourceSets != null);
434 Dictionary<string, ResourceSet> localResourceSets = _resourceSets;
435 ResourceSet? rs = null;
436 CultureInfo? foundCulture = null;
437 lock (localResourceSets)
439 if (localResourceSets.TryGetValue(culture.Name, out rs))
441 return rs;
445 ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, tryParents);
447 foreach (CultureInfo currentCultureInfo in mgr)
449 lock (localResourceSets)
451 if (localResourceSets.TryGetValue(currentCultureInfo.Name, out rs))
453 // we need to update the cache if we fellback
454 if (culture != currentCultureInfo) foundCulture = currentCultureInfo;
455 break;
459 // InternalGetResourceSet will never be threadsafe. However, it must
460 // be protected against reentrancy from the SAME THREAD. (ie, calling
461 // GetSatelliteAssembly may send some window messages or trigger the
462 // Assembly load event, which could fail then call back into the
463 // ResourceManager). It's happened.
465 rs = _resourceGroveler.GrovelForResourceSet(currentCultureInfo, localResourceSets,
466 tryParents, createIfNotExists);
468 // found a ResourceSet; we're done
469 if (rs != null)
471 foundCulture = currentCultureInfo;
472 break;
476 if (rs != null && foundCulture != null)
478 // add entries to the cache for the cultures we have gone through
480 // currentCultureInfo now refers to the culture that had resources.
481 // update cultures starting from requested culture up to the culture
482 // that had resources.
483 foreach (CultureInfo updateCultureInfo in mgr)
485 AddResourceSet(localResourceSets, updateCultureInfo.Name, ref rs);
487 // stop when we've added current or reached invariant (top of chain)
488 if (updateCultureInfo == foundCulture)
490 break;
495 return rs;
498 // Simple helper to ease maintenance and improve readability.
499 private static void AddResourceSet(Dictionary<string, ResourceSet> localResourceSets, string cultureName, ref ResourceSet rs)
501 // InternalGetResourceSet is both recursive and reentrant -
502 // assembly load callbacks in particular are a way we can call
503 // back into the ResourceManager in unexpectedly on the same thread.
504 lock (localResourceSets)
506 // If another thread added this culture, return that.
507 ResourceSet? lostRace;
508 if (localResourceSets.TryGetValue(cultureName, out lostRace))
510 if (!object.ReferenceEquals(lostRace, rs))
512 // Note: In certain cases, we can be trying to add a ResourceSet for multiple
513 // cultures on one thread, while a second thread added another ResourceSet for one
514 // of those cultures. If there is a race condition we must make sure our ResourceSet
515 // isn't in our dictionary before closing it.
516 if (!localResourceSets.ContainsValue(rs))
517 rs.Dispose();
518 rs = lostRace;
521 else
523 localResourceSets.Add(cultureName, rs);
528 protected static Version? GetSatelliteContractVersion(Assembly a)
530 // Ensure that the assembly reference is not null
531 if (a == null)
533 throw new ArgumentNullException(nameof(a), SR.ArgumentNull_Assembly);
536 string? v = a.GetCustomAttribute<SatelliteContractVersionAttribute>()?.Version;
537 if (v == null)
539 // Return null. The calling code will use the assembly version instead to avoid potential type
540 // and library loads caused by CA lookup.
541 return null;
544 if (!Version.TryParse(v, out Version? version))
546 throw new ArgumentException(SR.Format(SR.Arg_InvalidSatelliteContract_Asm_Ver, a, v));
549 return version;
552 protected static CultureInfo GetNeutralResourcesLanguage(Assembly a)
554 // This method should be obsolete - replace it with the one below.
555 // Unfortunately, we made it protected.
556 return ManifestBasedResourceGroveler.GetNeutralResourcesLanguage(a, out _);
559 // IGNORES VERSION
560 internal static bool IsDefaultType(string asmTypeName,
561 string typeName)
563 Debug.Assert(asmTypeName != null, "asmTypeName was unexpectedly null");
565 // First, compare type names
566 int comma = asmTypeName.IndexOf(',');
567 if (((comma == -1) ? asmTypeName.Length : comma) != typeName.Length)
568 return false;
570 // case sensitive
571 if (string.Compare(asmTypeName, 0, typeName, 0, typeName.Length, StringComparison.Ordinal) != 0)
572 return false;
573 if (comma == -1)
574 return true;
576 // Now, compare assembly display names (IGNORES VERSION AND PROCESSORARCHITECTURE)
577 // also, for mscorlib ignores everything, since that's what the binder is going to do
578 while (char.IsWhiteSpace(asmTypeName[++comma])) ;
580 // case insensitive
581 AssemblyName an = new AssemblyName(asmTypeName.Substring(comma));
583 // to match IsMscorlib() in VM
584 return string.Equals(an.Name, "mscorlib", StringComparison.OrdinalIgnoreCase);
587 // Looks up a resource value for a particular name. Looks in the
588 // current thread's CultureInfo, and if not found, all parent CultureInfos.
589 // Returns null if the resource wasn't found.
591 public virtual string? GetString(string name)
593 return GetString(name, null);
596 // Looks up a resource value for a particular name. Looks in the
597 // specified CultureInfo, and if not found, all parent CultureInfos.
598 // Returns null if the resource wasn't found.
600 public virtual string? GetString(string name, CultureInfo? culture)
602 if (null == name)
603 throw new ArgumentNullException(nameof(name));
605 #if FEATURE_APPX || ENABLE_WINRT
606 if (_useUapResourceManagement)
608 // Throws WinRT hresults.
609 Debug.Assert(_neutralResourcesCulture != null);
610 return GetStringFromPRI(name, culture, _neutralResourcesCulture.Name);
612 #endif
614 if (culture == null)
616 culture = CultureInfo.CurrentUICulture;
619 ResourceSet? last = GetFirstResourceSet(culture);
621 if (last != null)
623 string? value = last.GetString(name, _ignoreCase);
624 if (value != null)
625 return value;
628 // This is the CultureInfo hierarchy traversal code for resource
629 // lookups, similar but necessarily orthogonal to the ResourceSet
630 // lookup logic.
631 ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
632 foreach (CultureInfo currentCultureInfo in mgr)
634 ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
635 if (rs == null)
636 break;
638 if (rs != last)
640 string? value = rs.GetString(name, _ignoreCase);
641 if (value != null)
643 // update last used ResourceSet
644 if (_lastUsedResourceCache != null)
646 lock (_lastUsedResourceCache)
648 _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
649 _lastUsedResourceCache.lastResourceSet = rs;
652 return value;
655 last = rs;
659 return null;
662 // Looks up a resource value for a particular name. Looks in the
663 // current thread's CultureInfo, and if not found, all parent CultureInfos.
664 // Returns null if the resource wasn't found.
666 public virtual object? GetObject(string name)
668 return GetObject(name, null, true);
671 // Looks up a resource value for a particular name. Looks in the
672 // specified CultureInfo, and if not found, all parent CultureInfos.
673 // Returns null if the resource wasn't found.
674 public virtual object? GetObject(string name, CultureInfo? culture)
676 return GetObject(name, culture, true);
679 private object? GetObject(string name, CultureInfo? culture, bool wrapUnmanagedMemStream)
681 if (null == name)
682 throw new ArgumentNullException(nameof(name));
684 if (null == culture)
686 culture = CultureInfo.CurrentUICulture;
689 ResourceSet? last = GetFirstResourceSet(culture);
690 if (last != null)
692 object? value = last.GetObject(name, _ignoreCase);
694 if (value != null)
696 if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
697 return new UnmanagedMemoryStreamWrapper(stream);
698 else
699 return value;
703 // This is the CultureInfo hierarchy traversal code for resource
704 // lookups, similar but necessarily orthogonal to the ResourceSet
705 // lookup logic.
706 ResourceFallbackManager mgr = new ResourceFallbackManager(culture, _neutralResourcesCulture, true);
708 foreach (CultureInfo currentCultureInfo in mgr)
710 ResourceSet? rs = InternalGetResourceSet(currentCultureInfo, true, true);
711 if (rs == null)
712 break;
714 if (rs != last)
716 object? value = rs.GetObject(name, _ignoreCase);
717 if (value != null)
719 // update the last used ResourceSet
720 if (_lastUsedResourceCache != null)
722 lock (_lastUsedResourceCache)
724 _lastUsedResourceCache.lastCultureName = currentCultureInfo.Name;
725 _lastUsedResourceCache.lastResourceSet = rs;
729 if (value is UnmanagedMemoryStream stream && wrapUnmanagedMemStream)
730 return new UnmanagedMemoryStreamWrapper(stream);
731 else
732 return value;
735 last = rs;
739 return null;
742 public UnmanagedMemoryStream? GetStream(string name)
744 return GetStream(name, null);
747 public UnmanagedMemoryStream? GetStream(string name, CultureInfo? culture)
749 object? obj = GetObject(name, culture, false);
750 UnmanagedMemoryStream? ums = obj as UnmanagedMemoryStream;
751 if (ums == null && obj != null)
752 throw new InvalidOperationException(SR.Format(SR.InvalidOperation_ResourceNotStream_Name, name));
753 return ums;
756 internal class ResourceManagerMediator
758 private readonly ResourceManager _rm;
760 internal ResourceManagerMediator(ResourceManager rm)
762 if (rm == null)
764 throw new ArgumentNullException(nameof(rm));
766 _rm = rm;
769 // NEEDED ONLY BY FILE-BASED
770 internal string? ModuleDir => _rm._moduleDir;
772 // NEEDED BOTH BY FILE-BASED AND ASSEMBLY-BASED
773 internal Type? LocationInfo => _rm._locationInfo;
775 internal Type? UserResourceSet => _rm._userResourceSet;
777 internal string? BaseNameField => _rm.BaseNameField;
779 internal CultureInfo? NeutralResourcesCulture
781 get { return _rm._neutralResourcesCulture; }
782 set { _rm._neutralResourcesCulture = value; }
785 internal string GetResourceFileName(CultureInfo culture)
787 return _rm.GetResourceFileName(culture);
790 // NEEDED ONLY BY ASSEMBLY-BASED
791 internal bool LookedForSatelliteContractVersion
793 get { return _rm._lookedForSatelliteContractVersion; }
794 set { _rm._lookedForSatelliteContractVersion = value; }
797 internal Version? SatelliteContractVersion
799 get { return _rm._satelliteContractVersion; }
800 set { _rm._satelliteContractVersion = value; }
803 internal Version? ObtainSatelliteContractVersion(Assembly a)
805 return ResourceManager.GetSatelliteContractVersion(a);
808 internal UltimateResourceFallbackLocation FallbackLoc
810 get { return _rm.FallbackLocation; }
811 set { _rm._fallbackLoc = value; }
814 internal Assembly? MainAssembly => _rm.MainAssembly;
816 // this is weird because we have BaseNameField accessor above, but we're sticking
817 // with it for compat.
818 internal string BaseName => _rm.BaseName;