Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I capture the name of a C# static string resource property without changing the caller?

Tags:

c#

Having string resources in Strings.resx/Strings.Designer.cs, used like this:

myObject.DoSomething(Strings.HelloWorld);

can I capture the resource id (i.e. "Strings.HelloWorld") in DoSomething without changing the source line that calls DoSomething? I want the name of the static property defined in Strings.Designer.cs, along with the string value.

What I've figured out is:

myObject.DoSomething(() => Strings.HelloWorld);

and then:

void DoSomething(Expression<Func<string>> expr)
{
    string id = expr.Body.ToString();
    string value = (expr.Compile())();
    // do something with these...
}

That changes the original line slightly, but it's still readable and the compiler is checking the property name for me.

I was wondering if there is any C# magic to do this without changing that original line.

EDIT: Really, I just want unique ints for each string resource that I use to format a TraceEvent message, to use as the TraceEvent id. I don't want to maintain a list of ints myself when the compiler can do that for me, adding one every time I insert a new source line to trace something else interesting. I get my ints once in initialization by mapping the ResourceSet keys to a sequence of ints, and I don't mind if the numbers shift around from build to build.

EDIT2: Referring to LBushkin's answer, I did initially map strings to int, like this:

var resourceSet = resourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);

var sorted =
    from i in resourceSet.Cast<DictionaryEntry>()
    orderby i.Key
    select i.Value as string;

var valueToId = new Dictionary<string, int>();

foreach (string value in sorted)
{
    valueToId[value] = valueToId.Count + 100; // whatever range you like
}

...but I don't like relying on a static CurrentCulture. I want a simple low-tech, low-maintenance mapping of the resource strings to unique ints, where all I need to do is add strings in the designer when I feel like it.

like image 336
Jim Flood Avatar asked Mar 09 '11 20:03

Jim Flood


1 Answers

No, I'm afraid there isn't anything that will do this. The lambda expression is the closest you'll get.

EDIT: Warning - smelly code ahead

At least, that's the case if you don't want to add extra methods in another Strings class in the same namespace as the caller. Here's a really grotty hack:

// You'd want to autogenerate this class based on the designer file.
public class Strings
{
    public static string HelloWorld()
    {
        return global::OriginalNS.Strings.HelloWorld;
    }
}

then...

void DoSomething(Func<string> provider)
{
    string id = provider.Method.Name;
    string value = provider();
}

Now this:

myObject.DoSomething(Strings.HelloWorld);

is compiled into a method group conversion using the method Strings.HelloWorld. DoSomething finds the name of that method to get the ID, and calls the delegate to get the actual value.

This will only work if Strings is in a different namespace to the method calling DoSomething, so that you can hijack the use of the name Strings. I suppose another alternative would be a using directive:

using Strings = EvilStrings;

In general though: urgh! Don't do this! It's fun to think about, of course, but I wouldn't wish it on production code...

like image 162
Jon Skeet Avatar answered Sep 20 '22 03:09

Jon Skeet