Share via


ResourceReader.GetResourceData and ResourceTypeCodes [Mike Rousos]

Intro

ResourceReader is a useful type for quickly enumerating through resources in a resources file, but it has trouble if it cannot deserialize one of the resources. This limitation is compensated for, in v2.0 of the framework, with the GetResourceData method. This method allows the caller to retrieve the bytes of a resource (and its type) directly from a resources file without having to deserialize the object (which could fail). In this article, I want to briefly touch on how this method could be used effectively and also on some strange types (ResourceTypeCode types) you may encounter while using it.

 

How to use GetResourceData

A common use of the ResourceReader, might look something like this:

string name;

object value;

ResourceReader myReader = new ResourceReader(openFileDialog1.FileName);

IDictionaryEnumerator readerWalker = myReader.GetEnumerator();

while (readerWalker.MoveNext())

{

      name = (string)(readerWalker.Key);

      value = readerWalker.Value;

      // Do something with resource name and value

}

There are situations, though, where this system will fail. For example, consider a resources file that contains three resources: an integer, a string, and an object of type ‘myClass’ (a custom type I created). Now, imagine that the myClass dll is not available when we’re reading the resources. The line that reads the value will fail when that resource is reached. Hence, we’ve added the GetResourceData method. GetResourceData takes a string parameter that is the resource’s name and it has two out parameters – one is set to the resource’s type (as a string), the other is an array of bytes that represent the resource in the resources file. So, a better implementation of reading a resources file might look like this:

 

string name;

object value;

string type;

byte[] bytes;

ResourceReader myReader = new ResourceReader(openFileDialog1.FileName);

IDictionaryEnumerator readerWalker = myReader.GetEnumerator();

while (readerWalker.MoveNext())

{

      name = (string)(readerWalker.Key);

      try

{

            value = readerWalker.Value;

            // Do something with resource name and value

}

      catch (FileNotFoundException)

{

myReader.GetResourceData(name, out type, out bytes);

// Do something with the resource name, type and bytes

// Also, value can be set to some default value

}

      catch (TypeLoadException)

{

myReader.GetResourceData(name, out type, out bytes);

// Do something with the resource name, type and bytes

// Also, value can be set to some default value

      }

}

This system allows all resources to be handled, even those that cannot be deserialized. Also, for those of you wondering why we catch two different types of exceptions – a type load exception will be thrown if the framework can find an assembly that it thinks should contain the offending resource’s type but cannot find the actual type itself (due to a versioning problem, for example); a file not found exception will be thrown if the framework cannot even find an appropriate library (due to a missing dll file, for example).

ResourceTypeCodes

One of the motivating factors for this article was this section. One of the items that GetResourceData returns is a string representing the resource’s type. For example, in the above code, when looking for the ‘myClass’ resource, the type string comes back: “myClass, ResBlog, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”.

 

This makes sense. However, suppose we were to call GetResourceData on the same resources file and look for the integer resource. The type reads as follows: “ResourceTypeCode.Int32”.

 

Why not System.Int32? What is a ResourceTypeCode.Int32? It has to due with how resources are stored in resources files. Remember that the GetResourceData method returns to us exactly what it finds in the file.

 

So, how are resources stored in resources files? Well, for most types (all except 19 special cases), the resource’s type is saved in a string format (very much like what GetResourceData returns) and the resource itself is serialized with a binary serializer and its bytes are stored in the file. So, when you call GetResourceData on the ‘myClass’ type resource, the bytes that are returned represent a serialized myClass object (whose type is represented in the string).

 

Not all resources are stored like this, though. Using the binary serializer to convert an object to bytes is usually not the most efficient method of storing that item in binary. For example, serializing a bool will result in a stream of bytes well over a hundred long. Clearly, this would be a very poor storage mechanism to use in our resources files. Therefore, 19 of the most common resource types are special cased when they are added to a resources file. Instead of saving their serialized bytes, the bytes are saved in a much more economical method (for example, we only set aside four bytes for an integer’s data). However, we can’t deserialize this sort of special cased information with the binary serializer. Therefore, we don’t want to call these resources’ types by their original type names. Instead, we mark them as a ResourceTypeCode type. This says to the ResourceReader that the resource is specially cased and should not be deserialized with a binary formatter. Instead, it knows to go look for special logic of its own for restoring the resource information.

 

So, in essence, there are two ways of storing an integer (for example) in a resources file. First, the type could be System.Int32 and the bytes could be a serialized integer. Or, the resource type could be ResourceTypeCode.Int32 and the bytes could be the four bytes that comprise the integer’s value. The resource reader will understand either of these encodings. Of course, under normal circumstances, the resource writer will only ever produce the latter because it is far more economical. The former could be produced using AddResourceData (see below), and in some cases may be as a user may be adding a large number of resources with this method and not want to try and separate out all the ones that could be special cased by ResourceWriter.AddResource.

 

So, what ResourceTypeCodes might you encounter while using GetResourceData? The list is as follows:

  1. ResourceTypeCode.Null
  2. ResourceTypeCode.String
  3. ResourceTypeCode.Boolean
  4. ResourceTypeCode.Char
  5. ResourceTypeCode.Byte
  6. ResourceTypeCode.SByte
  7. ResourceTypeCode.Int16
  8. ResourceTypeCode.UInt16
  9. ResourceTypeCode.Int32
  10. ResourceTypeCode.UInt32
  11. ResourceTypeCode.Int64
  12. ResourceTypeCode.UInt64
  13. ResourceTypeCode.Single
  14. ResourceTypeCode.Double
  15. ResourceTypeCode.Decimal
  16. ResourceTypeCode.DateTime
  17. ResourceTypeCode.TimeSpan
  18. ResourceTypeCode.ByteArray
  19. ResourceTypeCode.Stream

 

So, without the ability to deserialize, how can one understand the bytes that will accompany one of these types? Except for String, Byte[], and Stream, all of the corresponding bytes retrieved from GetResourceData can be converted to the appropriate system types using System.BitConverter. Strings, Byte[]s, and Streams are simply encoded as their count followed by the bytes. For example, the string “Testing” encodes into these bytes: 07 54 65 73 74 69 6e 67.

 

AddResourceData

It is also worth mentioning in this article that there is another method that parallels GetResourceData. This new method is called AddResourceData. It exists on ResourceWriter and will write bytes verbatim into a resources file. It takes, as you may have guessed, a name for the resource, a string representing its type, and an array of bytes that represents the resource itself. This data is just inserted directly into the resources file. Although AddResource can add most types just fine, if for whatever reason you are using AddResourceData to add one of the 19 resource types enumerated above, remember that serialized primitive types should be paired with strings that look liked ‘System.Int32’ or something like that and data that is encoded using the more efficient algorithms that ResourceWriter.AddResource would use, should have types beginning with ResourceTypeCode. Keeping these items in sync will make it possible for the resources added in this way to be read back out in the future without the use of GetResourceData.

 

All this and more in the Documentation

Just a quick plug, the code snippets in this article were taken out of my ResourceViewer project, a sample win-forms application that can open resources files and display all the resources contained therein. For each resource, it lists the resource’s name, type, value (if it can determine it), and shows the binary representation of the resource.

 

This sample is shipping with the v2.0 documentation, so that is another place to turn if you would like to see a full application that uses GetResourceData in conjunction with more traditional uses of the ResourceReader.

Comments

  • Anonymous
    June 07, 2005
    I did some work with RESX files a while ago and I was surprised to see that the CLR reader does not support setting the comment/type/mimetype members in the RESX data element schema. I ended up coding my own parser, though I know 2.0 will support this.
  • Anonymous
    June 14, 2005
    Interesting finds this morning