ResX comments and random access

If you are working with .resx files in .net, you will no doubt be familar with the ResXResourceReader, ResXResourceWriter and possibly ResXResourceSet. These three classes allow reading and writing of resx files, but they have some annoying limitations:

  1. ResXResourceReader does not provide random access to values (no method taking a string name/key – you have to traverse all entries sequentially to find a value).
  2. ResXResourceReader requires use of resx data nodes to access value comments.
  3. ResXResourceSet provides random access (via the GetObject() method) but has no support for comments via resx data nodes.

These classes have been available since .net 1.0 and it is another example of a software development principle I believe in more each day: ‘Your first attempt at designing [software entity] will be sub-optimal.’.

Unfortunately Microsoft has not done much work in this area other than in .net 2.0 to introduce resx data node support for the ResXResourceReader that enable you to work with entry comments.

If you want to work with resx comments as well as have random access to values, here is a class that derives from ResXResourceReader and provides that functionality:

using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Resources;

namespace ResxUtils
{
	/// <summary>
	/// Extends the ResXResourceReader class with value and comment support.
	/// </summary>
	public class ResXReader : ResXResourceReader
	{
		/// <summary>
		/// Container for a ResX value and comment.
		/// </summary>
		private class ResXValue
		{
			public string Value;
			public string Comment;
		}

		Dictionary<string, ResXValue> _resxEntries;

		/// <summary>
		/// Initializes a new instance of the <see cref="ResXReader"/> class.
		/// </summary>
		/// <param name="resXStream">The resX stream.</param>
		public ResXReader(System.IO.Stream resXStream)
			: base(resXStream)
		{
			Initialize();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ResXReader"/> class.
		/// </summary>
		/// <param name="filePath">The file path.</param>
		public ResXReader(string filePath)
			: base(filePath)
		{
			Initialize();
		}

		/// <summary>
		/// Initializes this instance.
		/// </summary>
		private void Initialize()
		{
			UseResXDataNodes = true; // must turn this on to use access resx comments

			_resxEntries = new Dictionary<string, ResXValue>();

			foreach (DictionaryEntry entry in this)
			{
				var node = (ResXDataNode)entry.Value;
				var value = (string)node.GetValue(null as ITypeResolutionService);
				_resxEntries[node.Name] = new ResXValue { Value = value, Comment = node.Comment };
			}
		}

		/// <summary>
		/// Gets the value.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>The value, or null if there is no entry matching the supplied key.</returns>
		public string GetValue(string key)
		{
			if (_resxEntries.ContainsKey(key))
			{
				return _resxEntries[key].Value;
			}

			return null;
		}

		/// <summary>
		/// Gets the comment.
		/// </summary>
		/// <param name="key">The key.</param>
		/// <returns>The comment, or null if there is no entry matching the supplied key.</returns>
		public string GetComment(string key)
		{
			if (_resxEntries.ContainsKey(key))
			{
				return _resxEntries[key].Comment;
			}

			return null;
		}
	}
}

Note: If you run Code Analysis / FxCop over this in .net 4.0, it will complain about several things, in particular LinkDemands are skipped, the class is not named correctly. The class naming rule is a side effect of the base ResXResourceReader class implementing the generic interface IEnumerable, (so can just be suppressed) and the security warnings are due to the CAS policy changes in .net 4.0. I’m not sure there is any way to code around deriving from a class with link demands in .net 4.0 without suppressing security warnings (even adding the SecurityCritical attribute fails, and should be unnecessary anyway).