0

I want to be able to retrieve a language dependent string during runtime, why I created the class below. It let's me get the value in the preferred language whenever I want with getValue():

internal class ResourceString : IResourceString
    {

        public ResourceString(string id, ResourceManager rm, params IResourceString[] parameters)
        {
            Id = id;
            Rm = rm;
            m_Parameters = parameters ?? Array.Empty<IResourceString>();
        }

        public string Id { get; }
        private ResourceManager Rm { get; }

        private readonly IResourceString[] m_Parameters;

        public string GetValue(CultureInfo cultureInfo)
        {
            if (m_Parameters.Count() > 0)
            {
                return string.Format(Rm.GetString(Id, cultureInfo), m_Parameters.Select(p => p.GetValue(cultureInfo)));
            }
            else
            {
                return Rm.GetString(Id, cultureInfo);
            }
        }
    };

    //Extension to get a specific value downstream 
    internal static class MlExtension
    {
        public static string GetValue(this IResourceString source)
        {
            return source.GetValue(CultureInfo.CurrentCulture);
        }
    }

Problem with that is, when creating the ResourceString, you have to pass the key of the Resource file (using nameof()) as a string, named id here, which is easily overlooked:

correct: new ResourceString(nameof(Guid.Rule_Name), Guid.ResourceManager)

not correct: new ResourceString(Guid.Rule_Name, Guid.ResourceManager)

Is there a smart way to somehow indicate, that the key has to be passed, not the value, or even force it?


Part of a resource file: enter image description here

tickietackie
  • 327
  • 4
  • 12

2 Answers2

1

Assuming that the keys are always the names of public static string properties in some class, you could use the following technique:

Instead if passing the Id as string, pass it as System.Linq.Expressions.Expression and derive the name of the property from it.

    public ResourceString(Expression<Func<string>> expression)
    {
        Id = PropertyNameFromExpression(expression);
    }

    // Helper methods:
    private static string PropertyNameFromExpression<T>(Expression<Func<T>> expression)
    {
        return PropertyNameFromExpression(expression.Body);
    }

    private static string PropertyNameFromExpression(Expression expression)
    {
        return ((MemberExpression)expression).Member.Name;
    }

Usage:

new ResourceString(() => Guid.Rule_Name);

This might imply some runtime performance penalty, however.

Klaus Gütter
  • 11,151
  • 6
  • 31
  • 36
  • Gets rid of my specific problem that you cannot forget using nameof(). Therefore giving this the accepted answer. – tickietackie Jan 19 '23 at 17:06
  • In some cases I get a "Unable to cast object of type 'System.Linq.Expressions.TypedConstantExpression' to type 'System.Linq.Expressions.MemberExpression'" exception in line "return ((MemberExpression)expression).Member.Name" I thought I could solve it that way https://stackoverflow.com/questions/12348472/extracting-method-name-as-string-from-reflection-or-hardcoded-string, but I cannot get it to work. Any idea? – tickietackie Jan 31 '23 at 10:35
  • Don't know how to get this info with ConstantExpression, do I have to use this: https://stackoverflow.com/questions/6998523/how-to-get-the-value-of-a-constantexpression-which-uses-a-local-variable ? – tickietackie Jan 31 '23 at 10:52
  • It's happening for constant values like: It's happening when I am passing something like this: new ResourceString(() => "", default), – tickietackie Jan 31 '23 at 11:00
  • @tickietackie I did not approve your edit just because it did not compile – Klaus Gütter Jan 31 '23 at 12:56
  • That is ok, I wanted to withdraw it anyway, because it is was not solving the issue, as you can read in the comments. – tickietackie Jan 31 '23 at 21:22
  • Could you propose a way to pass the expression down a function so the expression will still be evaluated to the right string? When I pass it down atm its return the name of the parameter of that function, not the value of the wanted string in the resource file. – tickietackie Feb 12 '23 at 13:29
0

I used a generic type parameter ResourceString to make sure that the type of the key passed to the constructor is always a string.

I've implemented the fix to your code, as seen here. Hope this is what you're looking for, if not criticize me.

internal class ResourceString<T> : IResourceString where T : class
{
    public ResourceString(T key, ResourceManager rm, params IResourceString[] parameters)
    {
        Key = key;
        Rm = rm;
        m_Parameters = parameters ?? Array.Empty<IResourceString>();
    }

    public T Key { get; }
    private ResourceManager Rm { get; }

    private readonly IResourceString[] m_Parameters;

    public string GetValue(CultureInfo cultureInfo)
    {
        if (m_Parameters.Count() > 0)
        {
            return string.Format(Rm.GetString((string)Key, cultureInfo), m_Parameters.Select(p => p.GetValue(cultureInfo)));
        }
        else
        {
            return Rm.GetString((string)Key, cultureInfo);
        }
    }
}

internal static class MlExtension
{
    public static string GetValue<T>(this ResourceString<T> source) where T : class
    {
        return source.GetValue(CultureInfo.CurrentCulture);
    }
}

const string RULE_NAME_KEY = "Rule_Name";

// ...

new ResourceString<string>(RULE_NAME_KEY, Guid.ResourceManager)

// or

new ResourceString<string>(nameof(Guid.Rule_Name), Guid.ResourceManager)
Brixton
  • 11
  • 5
  • 1
    Hi, actually I don't understand how this should solve the issue, sorry. In my constructor id is already a string? The key of a Resource file entry is a string, and so is the value. The problem is that somebody could pass the value, which then when trying to get the localized string will result in not finding an entry. I'll try to clarify this in the post. – tickietackie Jan 08 '23 at 11:17