One of my colleagues came up with the answer yesterday; it's one of those methods that you can never find when you're looking for it, but once discovered it seems blindingly obvious:

   object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

So you can write the following kind of code:

   enum Colour
{
Red,
Green,
Blue
}

Colour c = (Colour) Enum.Parse(typeof(Colour), "Red", true);
Console.WriteLine("Colour Value: {0}", c.ToString());

// Picking an invalid colour throws an ArgumentException. To
// avoid this, call Enum.IsDefined() first, as follows:
string nonColour = "Polkadot";

if (Enum.IsDefined(typeof(Colour), nonColour))
c = (Colour) Enum.Parse(typeof(Colour), nonColour, true);
else
MessageBox.Show("Uh oh!");

What a time saver - Thanks, Simon!

Footnote: interestingly, whilst writing this up I noticed that Enum.IsDefined() doesn’t offer the ignoreCase parameter. If you don’t know whether the casing is right, it seems the only way to do the conversion is using the Parse method and catching the ArgumentException. That's not ideal, since it runs a lot slower. I wonder if this is a loophole in the design; no doubt someone like Brad could cast light on it...