Frage

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
    }
}
War es hilfreich?

Lösung

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");
    }
}

Andere Tipps

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top