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.
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
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.
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
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.
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();
246 // Now we can use the managed resources even when using PRI's to support the APIs GetObject, GetStream...etc.
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
296 _resourceSets
= new Dictionary
<string, ResourceSet
>();
297 _lastUsedResourceCache
= new CultureNameResourceSetPair();
299 lock (localResourceSets
)
301 foreach ((_
, ResourceSet resourceSet
) in localResourceSets
)
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
;
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
);
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
;
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
)
395 throw new ArgumentNullException(nameof(culture
));
397 Dictionary
<string, ResourceSet
>? localResourceSets
= _resourceSets
;
399 if (localResourceSets
!= null)
401 lock (localResourceSets
)
403 if (localResourceSets
.TryGetValue(culture
.Name
, out 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
);
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
))
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
;
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
471 foundCulture
= currentCultureInfo
;
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
)
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
))
523 localResourceSets
.Add(cultureName
, rs
);
528 protected static Version
? GetSatelliteContractVersion(Assembly a
)
530 // Ensure that the assembly reference is not null
533 throw new ArgumentNullException(nameof(a
), SR
.ArgumentNull_Assembly
);
536 string? v
= a
.GetCustomAttribute
<SatelliteContractVersionAttribute
>()?.Version
;
539 // Return null. The calling code will use the assembly version instead to avoid potential type
540 // and library loads caused by CA lookup.
544 if (!Version
.TryParse(v
, out Version
? version
))
546 throw new ArgumentException(SR
.Format(SR
.Arg_InvalidSatelliteContract_Asm_Ver
, a
, v
));
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 _
);
560 internal static bool IsDefaultType(string asmTypeName
,
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
)
571 if (string.Compare(asmTypeName
, 0, typeName
, 0, typeName
.Length
, StringComparison
.Ordinal
) != 0)
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
])) ;
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
)
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
);
616 culture
= CultureInfo
.CurrentUICulture
;
619 ResourceSet
? last
= GetFirstResourceSet(culture
);
623 string? value = last
.GetString(name
, _ignoreCase
);
628 // This is the CultureInfo hierarchy traversal code for resource
629 // lookups, similar but necessarily orthogonal to the ResourceSet
631 ResourceFallbackManager mgr
= new ResourceFallbackManager(culture
, _neutralResourcesCulture
, true);
632 foreach (CultureInfo currentCultureInfo
in mgr
)
634 ResourceSet
? rs
= InternalGetResourceSet(currentCultureInfo
, true, true);
640 string? value = rs
.GetString(name
, _ignoreCase
);
643 // update last used ResourceSet
644 if (_lastUsedResourceCache
!= null)
646 lock (_lastUsedResourceCache
)
648 _lastUsedResourceCache
.lastCultureName
= currentCultureInfo
.Name
;
649 _lastUsedResourceCache
.lastResourceSet
= rs
;
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
)
682 throw new ArgumentNullException(nameof(name
));
686 culture
= CultureInfo
.CurrentUICulture
;
689 ResourceSet
? last
= GetFirstResourceSet(culture
);
692 object? value = last
.GetObject(name
, _ignoreCase
);
696 if (value is UnmanagedMemoryStream stream
&& wrapUnmanagedMemStream
)
697 return new UnmanagedMemoryStreamWrapper(stream
);
703 // This is the CultureInfo hierarchy traversal code for resource
704 // lookups, similar but necessarily orthogonal to the ResourceSet
706 ResourceFallbackManager mgr
= new ResourceFallbackManager(culture
, _neutralResourcesCulture
, true);
708 foreach (CultureInfo currentCultureInfo
in mgr
)
710 ResourceSet
? rs
= InternalGetResourceSet(currentCultureInfo
, true, true);
716 object? value = rs
.GetObject(name
, _ignoreCase
);
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
);
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
));
756 internal class ResourceManagerMediator
758 private readonly ResourceManager _rm
;
760 internal ResourceManagerMediator(ResourceManager rm
)
764 throw new ArgumentNullException(nameof(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
;