Search code examples
c#genericsrefactoring

How to combine two methods with the same logic into a generic method


I've two method:

        static int SumInt(List<string> list)
        {
            return list.Select(o => int.Parse(o)).Sum();
        }

        static float SumFloat(List<string> list)
        {
            return list.Select(o => float.Parse(o)).Sum();
        }

How can i get a generic method such as static T Sum<T>(List<string> list) where T:struct{},or a method with a ValueType return: static ValueType Sum(List<string> list){} The key point is not how to convert, but how to sum a struct or ValueType.


Solution

  • I would use a Dictionary<Type, Delegate> to hold the delegate that can do the computation. You then just have to register the correct delegates at run-time and it works quite easily.

    private static Dictionary<Type, Delegate> _registry = new Dictionary<Type, Delegate>();
    
    public static void Register<S, T>(Func<string, S> parse, Func<IEnumerable<S>, T> aggregate)
    {
        _registry[typeof(T)] = (Func<IEnumerable<string>, T>)(xs => aggregate(xs.Select(parse)));
    }
    
    public static T Sum<T>(List<string> list) =>
        ((Func<IEnumerable<string>, T>)_registry[typeof(T)])(list);
    

    Now I can write this code:

    Register(int.Parse, Enumerable.Sum);
    Register(float.Parse, Enumerable.Sum);
    
    var sourceIntegers = new List<string>() { "1", "2", "3" };
    var sourceFloats = new List<string>() { "1.1", "2.2", "3.3" };
    
    Console.WriteLine(Sum<int>(sourceIntegers));
    Console.WriteLine(Sum<float>(sourceFloats));
    

    The output I get is this:

    6
    6.6
    

    What makes this approach interesting is that I can do this:

    Register(
        x => new XElement("value", x),
        xs => new XDocument(new XElement("list", xs)));
        
    Console.WriteLine(Sum<XDocument>(sourceIntegers));
    

    That outputs:

    <list>
      <value>1</value>
      <value>2</value>
      <value>3</value>
    </list>