Thursday, October 13, 2005

Resources and Performance

Recently, David Gutierrez blogged about some Performance tips for using resources on the BCL team blog.

He focuses on ways to prevent the ResourceManager from probing for language or culture specific resources that aren't installed in order to improve the load time performance for resource assemblies. The two techniques he cites are the NeutralResourcesLanguageAttribute and specifying the installed cultures in your application config file. These both work in .Net 1.X and 2.0.

This reminds me of another resource performance issue which is common in localized Windows Forms applications -- the unnecessary loading of property values from the resx file.

Resources in the .Net Framework 1.1
For example, if I create a new Form and set its design time Localizable property to true, I get the following in the InitializeComponent() method:

private void InitializeComponent()
System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form2));
this.button1 = new System.Windows.Forms.Button();
// button1
this.button1.AccessibleDescription = resources.GetString("button1.AccessibleDescription");
this.button1.AccessibleName = resources.GetString("button1.AccessibleName");
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)(resources.GetObject("button1.Anchor")));
this.button1.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("button1.BackgroundImage")));
this.button1.Dock = ((System.Windows.Forms.DockStyle)(resources.GetObject("button1.Dock")));
this.button1.Enabled = ((bool)(resources.GetObject("button1.Enabled")));
this.button1.FlatStyle = ((System.Windows.Forms.FlatStyle)(resources.GetObject("button1.FlatStyle")));
this.button1.Font = ((System.Drawing.Font)(resources.GetObject("button1.Font")));
this.button1.Image = ((System.Drawing.Image)(resources.GetObject("button1.Image")));
this.button1.ImageAlign = ((System.Drawing.ContentAlignment)(resources.GetObject("button1.ImageAlign")));
this.button1.ImageIndex = ((int)(resources.GetObject("button1.ImageIndex")));
this.button1.ImeMode = ((System.Windows.Forms.ImeMode)(resources.GetObject("button1.ImeMode")));
this.button1.Location = ((System.Drawing.Point)(resources.GetObject("button1.Location")));
this.button1.Name = "button1";
this.button1.RightToLeft = ((System.Windows.Forms.RightToLeft)(resources.GetObject("button1.RightToLeft")));
this.button1.Size = ((System.Drawing.Size)(resources.GetObject("button1.Size")));
this.button1.TabIndex = ((int)(resources.GetObject("button1.TabIndex")));
this.button1.Text = resources.GetString("button1.Text");
this.button1.TextAlign = ((System.Drawing.ContentAlignment)(resources.GetObject("button1.TextAlign")));
this.button1.Visible = ((bool)(resources.GetObject("button1.Visible")));
// Form2
this.AccessibleDescription = resources.GetString("$this.AccessibleDescription");
this.AccessibleName = resources.GetString("$this.AccessibleName");
this.AutoScaleBaseSize = ((System.Drawing.Size)(resources.GetObject("$this.AutoScaleBaseSize")));
this.AutoScroll = ((bool)(resources.GetObject("$this.AutoScroll")));
this.AutoScrollMargin = ((System.Drawing.Size)(resources.GetObject("$this.AutoScrollMargin")));
this.AutoScrollMinSize = ((System.Drawing.Size)(resources.GetObject("$this.AutoScrollMinSize")));
this.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("$this.BackgroundImage")));
this.ClientSize = ((System.Drawing.Size)(resources.GetObject("$this.ClientSize")));
this.Enabled = ((bool)(resources.GetObject("$this.Enabled")));
this.Font = ((System.Drawing.Font)(resources.GetObject("$this.Font")));
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.ImeMode = ((System.Windows.Forms.ImeMode)(resources.GetObject("$this.ImeMode")));
this.Location = ((System.Drawing.Point)(resources.GetObject("$this.Location")));
this.MaximumSize = ((System.Drawing.Size)(resources.GetObject("$this.MaximumSize")));
this.MinimumSize = ((System.Drawing.Size)(resources.GetObject("$this.MinimumSize")));
this.Name = "Form2";
this.RightToLeft = ((System.Windows.Forms.RightToLeft)(resources.GetObject("$this.RightToLeft")));
this.StartPosition = ((System.Windows.Forms.FormStartPosition)(resources.GetObject("$this.StartPosition")));
this.Text = resources.GetString("$this.Text");

You have to ask yourself if all of these properties really need to be localized. For example:
  • AutoScroll
  • AutoScrollMinSize
  • AutoScrollMargin
  • Enabled
  • Anchor
  • Dock

Chances are, you won't be changing those values on a per language basis. Having those values in the resources have a performance cost and a size cost. The size may seem inconsequential but if you have a scenario where you are translating to 21 or more languages, the size can be a factor very quickly since the resources get multiplied by the number of languages.

As it turns out, there is an article on WindowsForms.Net that addresses this issue. It's called Improving Performance of Localized Forms by Brian Pepin.

He solves the problem through a Localization Filter component which is dropped in the designer for any UIs in which you want to filter out the extra localized properties. It's a very interesting article that also shows how to use many of the Windows Forms technologies like designers, code serialization and TypeDescriptors.

It certainly does the job, however I wanted to discuss an alternate approach which gives the same result and may be more suitable for your situation.

The approach is to subclass all of the controls and apply the Localizable(false) attribute to the properties which you don't want to show up in the resx file. For example:

public class Button : System.Windows.Forms.Button
public new string
get { return base.AccessibleDescription; }
{ base.AccessibleDescription = value; }

new string AccessibleName
get { return base.AccessibleName; }
set { base.AccessibleName = value; }

Note that the "new" keyword needs to be used because those properties are not virtual.

By subclassing all of the .Net Windows Forms provided controls including UserControl and Control, and having all user defined controls derive from those subclasses, you can remove all of the the extraneous properties from your resx files.

.Net Framework 2.0

Raghavendra Prabhu from the .Net Client Team talks about the changes to the resource model in Whidbey here. Note that the issues above are specifically addressed by the new PropertyReflection localization model in Visual Studio 2005.

The use of reflection to get around the problem is interesting since it is inherently slower on a per property basis.

Both models are supported for backwards compatibility reasons.


As we move forward in the Visual Studio 2005 era, its good to know the history behind how some of the technologies have evolved.

For resources, if you are working with Visual Studio 2003, you have a couple of different options for optimizing the size and performance of your resources. In Visual Studio 2005, you have a new localization model which will help with the resource size issue and hopefully also have benefits on the performance side.


Post a Comment

<< Home