TJHeuvel
TJHeuvel

Reputation: 12608

Dictionary values by reference

In our application we have some strings coming from translation that can contain variables. For example in Can i have a {beverage}? the {beverage} part should be replaced with a variable. My current implementation works by having a Dictionary of the name and value of all variables, and just replacing the correct string. However i'd like to register the variables by reference, so that if the value is ever changed the resulting string is changed as well. Usually passing a parameter with the ref keyword would do the trick, but i'm unsure on how to store those in a Dictionary.

TranslationParser:

static class TranslationParser
{
    private const string regex = "{([a-z]+)}";
    private static Dictionary<string, object> variables = new Dictionary<string,object>();

    public static void RegisterVariable(string name, object value)
    {
        if (variables.ContainsKey(name))
            variables[name] = value;
        else
            variables.Add(name, value);
    }

    public static string ParseText(string text)
    {
        return Regex.Replace(text, regex, match =>
        {
            string varName = match.Groups[1].Value;

            if (variables.ContainsKey(varName))
                return variables[varName].ToString();
            else
                return match.Value;
        });
    }
}

main.cs

        string bev = "cola";
        TranslationParser.RegisterVariable("beverage", bev);
        //Expected: "Can i have a cola?"
        Console.WriteLine(TranslationParser.ParseText("Can i have a {beverage}?")); 
        bev = "fanta";
        //Expected: "Can i have a fanta?"
        Console.WriteLine(TranslationParser.ParseText("Can i have a {beverage}?")); 

Is this possible at all, or am i just approaching the problem incorrectly? I fear that the only solution would involve unsafe code (pointers).

So in short, i'd like to store a variable in a Dictionary, change the original variable and get the changed value from the Dictionary. Like you would do with the ref keyword.

Upvotes: 5

Views: 11282

Answers (2)

Tigran
Tigran

Reputation: 62256

In the code provided I see

string bev = "cola";
TranslationParser.RegisterVariable("beverage", bev);
//Expected: "Can I have a cola?"
Console.WriteLine(TranslationParser.ParseText("Can I have a {beverage}?")); 
bev = "fanta";
//Expected: "Can I have a fanta?"

that first you register the substitute of the {beverage} like a "cola", but after want to change it at runtime to another one: "fanta". Thie leads me to think: why do not just a ParseText function to accept an optional parameter, which will "win" against saved one ?

Like this:

public static string ParseText(string text, string preferedValue=null)
{
        return Regex.Replace(text, regex, match =>
        {
            string varName = match.Groups[1].Value;

            if (variables.ContainsKey(varName))
            {
                if(!string.IsNullOrEmpty(preferedValue)) //IF THERE ISPREFERED VALUE                                                         
                       return preferedValue;             //RETURN THAT ONE

                return variables[varName].ToString();
            }
            else
                return match.Value;
        });
}

Upvotes: 0

tukaef
tukaef

Reputation: 9214

Another way to use wrappers. You can wrap your variables every time when you regiester them.

class ObjectWrapper
{
    private object _value;

    public ObjectWrapper(object value) 
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value.ToString();
    }
}

static class TranslationParser
{
    private const string regex = "{([a-z]+)}";
    private static Dictionary<string, ObjectWrapper> variables = new Dictionary<string, ObjectWrapper>();

    public static void RegisterVariable(string name, object value)
    {
        var wrapped = new ObjectWrapper(value);
        if (variables.ContainsKey(name))
            variables[name] = wrapped;
        else
            variables.Add(name, wrapped);
    }

    public static string ParseText(string text)
    {
        return Regex.Replace(text, regex, match =>
        {
            string varName = match.Groups[1].Value;

            if (variables.ContainsKey(varName))
                return variables[varName].ToString();
            else
                return match.Value;
        });
    }
}

Edit:

But actually, I think it's impossible without unsafe code to track variables in a way that you want to do. Value-types and references to reference-type stored in stack, and if you just replace reference, it will not affect to real object in heap (reference to that object you store in the dictionary). So that you need to have references (say pointers) to stack memory.

Edit again: I was wrong!

It is possible to track any varaible using expressions:

class Wrapper
{
    private readonly Dictionary<string, MemberExpression> _registrations = 
        new Dictionary<string, MemberExpression>();

    public void Register<T>(string name, Expression<Func<T>> expr)
    {
        _registrations[name] = (MemberExpression)expr.Body;
    }

    public object GetValue(string name)
    {
        var expr = _registrations[name];
        var fieldInfo = (FieldInfo)expr.Member;
        var obj = ((ConstantExpression)expr.Expression).Value;
        return fieldInfo.GetValue(obj);
    }
}
private static void Main(string[] args)
{
    var wrapper = new Wrapper();
    int x = 0;
    storage.Register("x", () => x);
    Console.WriteLine(wrapper.GetValue("x")); //0
    x = 1;
    Console.WriteLine(wrapper.GetValue("x")); //1
}

Upvotes: 2

Related Questions