2

I have a few methods like this:

public string GetStringValue(string field) { /* ... */ }
public int GetIntValue(string field) { /* ... */ }

Now I want to write a generic method which has the following signature:

public bool CopyValue<T>(string field, Action<T> copyAction)

Depending on the type parameter I want to invoke copyAction with the return value of one of the non-generic methods. My first try was

public bool CopyValue<T>(string field, Action<T> copyAction)
{
    if (typeof(T) == typeof(string))
        copyAction((GetStringValue(field));
    else if (typeof(T) == typof(int))
        copyAction(GetIntValue(field));
    else
        return false;

    return true;
}

But this doesn't even compile. I then tried to wrap my non-generic metods in generic ones like

public string GetValue<string>(string field)
{
    return GetStringValue(field);
}

which obviously doesn't compile either.

Can this be done or do i have to explicitly implement CopyValue for each type?

Soner Gönül
  • 97,193
  • 102
  • 206
  • 364

2 Answers2

5

You can get it to work with casting, but it's ugly:

if (typeof(T) == typeof(string))
{
    copyAction((T)(object) GetStringValue(field));
}

(etc)

This sort of thing always ends up being fairly ugly, to be honest. One option would be to create a Dictionary<Type, Delegate> like this:

Dictionary<Type, Delegate> fieldExtractors = new Dictionary<Type, Delegate>
{
    { typeof(string), (Func<string, string>) field => GetStringValue(field) },
    { typeof(int), (Func<string, int>) field => GetIntValue(field) },
};

Then you can use:

public bool CopyValue<T>(string field, Action<T> copyAction)
{
    Delegate fieldExtractor;
    if (fieldExtractors.TryGetValue(typeof(T), out fieldExtractor))
    {
        var extractor = (Func<string, T>) fieldExtractor;
        copyAction(extractor(field));
        return true;
    }
    return false;
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

In case you can refactor a bit your code, you could use this trick. The big plus is that this is done at compile-time instead of at runtime:

public class JsonDictionary
{
    public static readonly Key<int> Foo = new Key<int> { Name = "FOO" };
    public static readonly Key<string> Bar = new Key<string> { Name = "BAR" };
        
    IDictionary<string, object> _data;
    public JsonDictionary()
    {
        _data = new Dictionary<string, object>();
    }
    
    public void Set<T>(Key<T> key, T obj)
    {
        _data[key.Name] = obj;
    }

    public T Get<T>(Key<T> key)
    {
        return (T)_data[key.Name];
    }
    
    public class Key<T>
    {
        public string Name { get; init; }
    }
}

In my case I was able to replace a :

Dictionary<Type, Delegate> jsonElementDelegates = new Dictionary<Type, Delegate>
{
    { typeof(string), (Func<JsonElement, string>) (arrayItem => arrayItem.GetString()!) },
    { typeof(float), (Func<JsonElement, float>)(arrayItem => arrayItem.GetSingle()) },
    { typeof(double), (Func<JsonElement, double>) (arrayItem => arrayItem.GetDouble()) },
    { typeof(short), (Func<JsonElement, short>)(arrayItem => arrayItem.GetInt16()) },
    { typeof(ushort), (Func<JsonElement, ushort>) (arrayItem => arrayItem.GetUInt16()) },
    { typeof(int), (Func<JsonElement, int>)(arrayItem => arrayItem.GetInt32()) },
    { typeof(uint), (Func<JsonElement, uint>) (arrayItem => arrayItem.GetUInt32()) },
    { typeof(long), (Func<JsonElement, long>)(arrayItem => arrayItem.GetInt64()) },
    { typeof(ulong), (Func<JsonElement, ulong>) (arrayItem => arrayItem.GetUInt64()) },
};

With the compile-time equivalent:

public static readonly Converter<string> ConvString = new Converter<string> { Fun = arrayItem => arrayItem.GetString()! };
public static readonly Converter<float> ConvFloat = new Converter<float> { Fun = arrayItem => arrayItem.GetSingle() };
public static readonly Converter<double> ConvDouble = new Converter<double> { Fun = arrayItem => arrayItem.GetDouble() };
public static readonly Converter<short> ConvShort = new Converter<short> { Fun = arrayItem => arrayItem.GetInt16() };
public static readonly Converter<ushort> ConvUShort = new Converter<ushort> { Fun = arrayItem => arrayItem.GetUInt16() };
public static readonly Converter<int> ConvInt = new Converter<int> { Fun = arrayItem => arrayItem.GetInt32() };
public static readonly Converter<uint> ConvUInt = new Converter<uint> { Fun = arrayItem => arrayItem.GetUInt32() };
public static readonly Converter<long> ConvLong = new Converter<long> { Fun = arrayItem => arrayItem.GetInt64() };
public static readonly Converter<ulong> ConvULong= new Converter<ulong> { Fun = arrayItem => arrayItem.GetUInt64() };
public class Converter<T>
{
    public Func<JsonElement, T> Fun { get; init; }            
}

reference:

malat
  • 12,152
  • 13
  • 89
  • 158