4

I want to make the following class to work with int, double and other additive types without run-time overhead on boxing/unboxing, but with possibility to reuse from another generic type:

public class agg<T>{
    public static T add(T a,T b){return a+b;} // compile error: Operator '+' cannot be applied to operands of type 'T' and 'T'
    public static T add(T a,T b){return (dynamic)a+b;} // compiles, but involves boxing and unboxing at run-time
}

How can I achieve that ?

If I define method for each type explicitly, I can't use that class from another generic type test<T> without defining methods for each type explicitly in that class too:

public class agg<T>{
    //public static T add(T a,T b){return a+b;} // compile error: Operator '+' cannot be applied to operands of type 'T' and 'T'
    //public static T add(T a,T b){return (dynamic)a+b;} // compiles, but involves boxing and unboxing at run-time
    public static int add(int a,int b){return a+b;} // won't be matched by test agg<T>.add(a,b) invokation
}
public class test<T>{
    public test(T a,T b){
        var c=agg<T>.add(a,b); //compile error: The best overloaded method match for 'agg<T>.add(int, int)' has some invalid arguments
    }
}
alpav
  • 2,972
  • 3
  • 37
  • 47
  • Why generics if it is just double and it? You could have two Add methods. double Add(double a, double b), and int Add(int a, int b). – Dilshod Apr 28 '13 at 03:42
  • 1
    Check out http://stackoverflow.com/questions/147646/solution-for-overloaded-operator-constraint-in-net-generics/147656#147656 which discusses exactly this problem with operators. – Alexei Levenkov Apr 28 '13 at 03:45
  • @Alexei: I see 4 things there: 1) reference to downloadable source code, 2) dynamic, 3) ICalc, 4) IL. #2 and #3 are not very good, #4 is interesting if it works, but I wondered more about pure C# solution. #1 - downloadable source code does a lot of things, I would like to have some simple example. – alpav Apr 28 '13 at 04:08

2 Answers2

3

My solution using pure C# (no IL or 3rd party libraries):

internal class agginit{internal static bool started=false;}
public class agg<T>{
    //public static T add(T a,T b){return a+b;} // compile error: Operator '+' cannot be applied to operands of type 'T' and 'T'
    //public static T add(T a,T b){return (dynamic)a+b;} // compiles, but involves boxing and unboxing at run-time
    //public static int add(int a,int b){return a+b;} // won't be matched by test agg<T>.add(a,b) invokation
    public static T add(T a,T b){return _add(a,b);}
    static Func<T,T,T> _add=null;
    public static void setAdd(Func<T,T,T> f){if(_add==null)_add=f;else throw new Exception("Can't init twice");}
    static agg(){
        if(!agginit.started){ // to prevent recursive actions
            agginit.started=true;
            agg<int>._add=(a,b)=>a+b;
            agg<double>._add=(a,b)=>a+b;
            // below we initialize all other potentially used additive types just for fun, if type is not listed here, it's not supported
            agg<string>._add=(a,b)=>a+b;
            agg<byte>._add=(a,b)=>{return (byte)(a+b);}; // dirty down-cast, needs to be enhanced with return type generic parameter
            agg<long>._add=(a,b)=>a+b;
            agg<System.Numerics.BigInteger>._add=(a,b)=>a+b;
            agg<StringBuilder>._add=(a,b)=>{var ret=new StringBuilder();ret.Append(a.ToString());ret.Append(b.ToString());return ret;};
            agg<IEnumerable<T>>._add=(a,b)=>a.Concat(b);
            agg<HashSet<T>>._add=(a,b)=>{var ret=new HashSet<T>(a);ret.UnionWith(b);return ret;};
            agg<SortedSet<T>>._add=(a,b)=>{var ret=new SortedSet<T>(a);ret.UnionWith(b);return ret;};
            agg<byte[]>._add=(a,b)=>{var ret=new byte[a.Length+b.Length];Buffer.BlockCopy(a,0,ret,0,a.Length);Buffer.BlockCopy(b,0,ret,a.Length,b.Length); return ret;};
            agg<System.IO.MemoryStream>._add=(a,b)=>{var ret=new System.IO.MemoryStream(new byte[a.Length+b.Length]);a.WriteTo(ret);b.WriteTo(ret);return ret;};
        }
    }
}
public class test<T>{
    public T res;
    public test(T a,T b){
        res=agg<T>.add(a,b);
    }
}
public class A{
    public int z;
    static A(){
        agg<A>.setAdd((a,b)=>new A{z=a.z+b.z}); // any class can define own add implementation
    }
    public void test(){
        var t1=agg<A>.add(new A{z=1},new A{z=2});
        if(t1.z!=3)throw new Exception("test failed");
        var t2=new test<A>(new A{z=1},new A{z=2});
        if(t2.res.z!=3)throw new Exception("test failed");
    }
}
Community
  • 1
  • 1
alpav
  • 2,972
  • 3
  • 37
  • 47
  • 3
    Hm, interesting approach. I like it. However, your `Func` can simply be a property, instead of explicit getter (`add`) and setter (`setAdd`) methods. This has the benefit of allowing other code to see whether their `add` is null or not before trying to set it. In both cases, it's used the same simple way: `agg.add(a, b)`. Here's that property: `public static Func add { get { return _add; } set { if(_add==null)_add=value;else throw new Exception("Can't init twice"); } }` – Tim S. Apr 28 '13 at 13:21
  • @Tim: I agree, I added setAdd quickly just to demo that classes can register themselves to be additive without thinking a lot about design of it, so as classes test and A, naming, structure, e.t.c. – alpav Apr 28 '13 at 15:14
  • 1
    Excellent solution. Just what I needed to implement type casting functionality to/from generic types. Glad that I find a gem like this. There are many questions on SO regarding how to cast a concrete type to a generic type and back, but no good solution that *avoids boxing*. This solution solves it nicely. Basically change ``add`` to ``CastTo`` and the lambdas become ``x => x``. – Stephen Chung Apr 22 '14 at 03:22
  • Cool! There should be a compiler option to do this internally for a given method/class! Perhaps mark that `started` flag as `volatile`? – l33t May 19 '20 at 16:50
1

I don't think you'll find a good way to do what you're trying to do. Some possibilities:

  1. Use dynamic the way you show.
  2. Have an if/else chain, or switch on full type name, to identify a list of known types as being equal to T (e.g. if (typeof(T) == typeof(int)) add((int)a,(int)b); etc.)
  3. Instead of using new test<int>, create a testInt : test<int> class that calls the correct method.
  4. Call the add methods using dynamic casting in test<T>, not in agg<T>.

Example of 3:

public static class agg{
    public static int add(int a,int b){return a+b;}
    public static byte add(byte a,byte b){return (byte)(a+b);}
    public static decimal add(decimal a,decimal b){return a+b;}
    // etc
}
public class testInt:test<int>
{
    public testInt(int a, int b) : base(a, b) { }
    protected override int add(int a, int b)
    {
        return agg.add(a, b);
    }
}
public abstract class test<T>{
    public test(T a,T b){
        T c = add(a, b);
    }
    protected abstract T add(T a, T b);
}

Example of 4:

public class test<T>{
    public test(T a,T b){
        T c = agg.add((dynamic)a, (dynamic)b);
    }
}

Why are you concerned with boxing/unboxing? Is this a highly performance-sensitive task? If so, anything involving dynamic is likely to be unfeasible. If you're not sure that this code needs to run as fast as possible, don't prematurely optimize: forget about performance for now and solve the problem in the best, most readable way you can.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • I found better way than those 4 mentioned by you (see my answer) that does not involve casting, defining separate classes for each template type, or if statements for each type. – alpav Apr 28 '13 at 06:07
  • 1
    In certain application areas (e.g. embedded system drivers), things may be run within a tight loop every 10ms or so, and so the boxing objects do add up rapidly. In my case, I once removed a substring operation and manipulate it char-by-char, and the program's runtime memory consumption dropped by 70% and CPU by half. But of course, that function runs once every 10ms. – Stephen Chung Apr 22 '14 at 03:28
  • But I agree with you: don't prematurely optimize. – Stephen Chung Apr 22 '14 at 03:30