Casting C# out parameters?

2020-05-29 17:26发布

问题:

Is it possible to cast out param arguments in C#? I have:

Dictionary<string,object> dict;  // but I know all values are strings
string key, value;

Roughly speaking (and if I didn't have static typing) I want to do:

dict.TryGetValue(key, out value);

but this obviously won't compile because it "cannot convert from 'out string' to 'out object'".

The workaround I'm using is:

object valueAsObject;
dict.TryGetValue(key, out valueAsObject);
value = (string) valueAsObject;

but that seems rather awkward.

Is there any kind of language feature to let me cast an out param in the method call, so it does this switcheroo for me? I can't figure out any syntax that'll help, and I can't seem to find anything with google.

回答1:

I don't know if it is a great idea, but you could add a generic extension method:

    static bool TryGetTypedValue<TKey, TValue, TActual>(
        this IDictionary<TKey, TValue> data,
        TKey key,
        out TActual value) where TActual : TValue
    {
        TValue tmp;
        if (data.TryGetValue(key, out tmp))
        {
            value = (TActual)tmp;
            return true;
        }
        value = default(TActual);
        return false;
    }
    static void Main()
    {
        Dictionary<string,object> dict
            = new Dictionary<string,object>();
        dict.Add("abc","def");
        string key = "abc", value;
        dict.TryGetTypedValue(key, out value);
    }


回答2:

No, there is no way around that. The out parameter must have a variable that matches exactly.

Using a string reference is not safe, as the dictionary can contain other things than strings. However if you had a dictionary of strings and tried to use an object variable in the TryGetValue call, that won't work either even though that would be safe. The variable type has to match exactly.



回答3:

I used Marc's extension method but added a bit to it.

My problem with the original was that in some cases my dictionary would contain an int64 whereas I would expect an int 32. In other cases the dictionary would contain a string (for example "42") while I would like to get it as an int.

There is no way to handle conversion in Marc's method so I added the ability to pass in a delegate to a conversion method:

internal static bool TryGetTypedValue<TKey, TValue, TActual>(
        this IDictionary<TKey, TValue> data,
        TKey key,
        out TActual value, Func<TValue, TActual> converter = null) where TActual : TValue
    {
        TValue tmp;
        if (data.TryGetValue(key, out tmp))
        {
            if (converter != null)
            {
                value = converter(tmp);
                return true;
            }
            if (tmp is TActual)
            {
                value = (TActual) tmp;
                return true;
            }
            value = default(TActual);
            return false;
        }
        value = default(TActual);
        return false;
    }

Which you can call like this:

int limit;
myParameters.TryGetTypedValue("limitValue", out limit, Convert.ToInt32)


回答4:

If you know all values are strings use Dictionary<string, string> instead. The out parameter type is set by the type of the second generic type parameter. Since yours is currently object, it will return an object when retrieving from the dictionary. If you change it to string, it will return strings.



回答5:

No, you can't. The code inside the method is directly modifying the variable passed to it, it is not passed a copy of the content of the variable.