The benefit to ResourceManager is that it can smartly load differently resource based on the current UI culture. ResourceManager basically it is used to read embedded resource or file based resource. But it is extensible. It has three constructor, and one static factory method

public ResourceManager(String baseName, Assembly assembly) 
public ResourceManager(Type resourceSource);


public ResourceManager(String baseName, Assembly assembly, Type usingResourceSet);
public static ResourceManager CreateFileBasedResourceManager(String baseName, String resourceDir, Type usingResourceSet) 

The most commonly used constructor is the first one. It try to read the embedded resource in the assembly, and the baseName is used to search inside of assembly. The second constructor basically does the same thing of first one. The type of resourceSource is the strongly type resource that is automatically generated by tool. This type has some static method to wrap the resource. Here is used to calcuated the base name, and assembly. Both of these two method use default RuntimeResourceSet to cache the resource. The third one allowed you specify the type of ResourceSet to cached the resource. This is useful if you have resource in different format. For example, if you baseName is "base", and dll name is "asm", the Resource manager will search in that dll for a resource named "asm.base.resources". By default it will use RuntimeResourceSet to cache the resource. But RuntimeResourceSet is internal, so that you can not use that. Following is clone of RuntimeResourceSet.

public RuntimeResourceSetClone(IResourceReader reader) : base(reader) { }

public RuntimeResourceSetClone(string fileName)
{
    ResourceReader reader = new ResourceReader(fileName);
    this.Reader = reader;
}

public RuntimeResourceSetClone(Stream stream)
{
    ResourceReader reader = new ResourceReader(stream);
    this.Reader = reader;
}

//we can use this ResourceSet to cache the default resource format
ResourceManager manager = new ResourceManager("UI", Assembly.GetExecutingAssembly(), typeof(RuntimeResourceSetClone));
Console.WriteLine(manager.GetString("Name"));

When you use resx file to manage resources, the compiler does not actually create resx format resource , and embed it in the assembly. What it does is to generate resource in defautlt resource format. Say you have UI.resx file, and the embedded resource name will be assemblyName.ui.resources. In case, you really want to use it in resources, you have to rename the extension to resources. The following shows how to read that. Please note the you need put the dll name with file name together.

//the loose file name is filename.resources
//the dll name is dllname
ResourceManager manager = new ResourceManager("dllname.filename", Assembly.GetExecutingAssembly(), typeof(ResXResourceSet));
Console.WriteLine(manager.GetString("Type"));

For embedded file resource, the neutral resource will be embedded in the main dll, for other resource, an assembly will be put in sub folder in the name of the culture (eg. zh-CN), the name of assembly is like mainAssemblyName.resources.dll.

The static factory method is used to read loose file in file system. One of the benefit using loose file is that saving memory, because resource is not loaded in the memory. All your resources file need to follow format, baseName.cultureName.resources . Yes, the extension has to be resources, even thought the content is not in resx format. Below is some example.

//if the resource format is default resource format, set usingResourceSet type to null,
var man = ResourceManager.CreateFileBasedResourceManager("baseName", "folderName", null);
Console.WriteLine(man.GetString("Name"));

//if resource file is in resx format, (the extension need to be ressources)
var man = ResourceManager.CreateFileBasedResourceManager("baseName", "folderName", typeof(ResXResourceSet));

The algorithm of ResourceManager depends on the UICulture heavily. First it locate the resource for current culture, if found return the value, if not fall back, to neutral culture, then invariant culture. For example, if current culture is zh-CN, it will try in the path of zh-CN, zh-CHS, Invariant. So to implement that, resource manager holds a ResourceSet for each availabe culture, depending how much culture the resources support. For each ResourceSet, it holds a Dictionary<string, object> object to cache to resource. The dictionary is filled by IResourceReader in the consturctor of ResourceSet. After that resource will be disconnected because all resource is cached. The default RuntimeResourceSet is optimized, so it does not use the dictionary.

To extend ResourceManager, we just need implement how the ResourceSet is created, and how the resource is read by IResourceReader. The default ResourceSet interface is designed for file based resource.

public ResourceSet(Stream stream) 
public ResourceSet(IResourceReader reader) 
public ResourceSet(String fileName)

Because of ResourceManager will use the same interface to create ResourceSet, if your solution is also file based, you just need a ResoursSet follow this constructor pattern. And you don't need to create a new ResourceManager, that is how Resx file is implemented. But if your solution is not file based, you need to SubClass your ResourceManager as well. ResourceSet will use IResourceReader to build its dictionary. IResourceReader is actually very simple Another drawback of ResourceManager is lack of creating or updating resource.

public interface IResourceReader : IEnumerable, IDisposable
{
        void Close();
        IDictionaryEnumerator GetEnumerator();
}