Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic indexer overload specialization

I know that there isn't template specialization in C#, but this works(at least in compile time):

    public T test0<T>()
    {
        return default(T);
    }

    // handling T=float case
    public float test0()
    {
        return 0;
    }

and this doesn't even compile:

    public T this[int i]
    {
        get { return default(T); }
        set { }
    }

    public float this[int i]
    {
        get { return 0; }
        set { }
    }

nor this

    public float this<float>[int i]
    {
        get { return 0; }
        set { }
    }

it says ".. already defines a member called 'this' with the same parameter types" on the float version. I could check for all types in the generic [] accessor but too many if-elseifs and typeof()s would decrease performance(I'm going to use this as an unmanaged array instead of a managed array) and dynamic is not an option as I'm using .Net 3.5 mainly. In another question, I've learned that I can't get pointer of a generic type so I decided to use multiple overloads per float,double,int and byte type and use those overloads in this array access overload(but not in same method but different ones to not lose performance.)

Can a generic [] be overloaded by some of primitive types like float? More specifically, can a class by only itself be used like below example, ever?

MyArr<float> arr = new MyArr<float>();

// specifically rounds to nearest for floats
arr[100]+=500f;

MyArr<int> arr = new MyArr<int>();

// specifically adds 1 to some performance counter variable
arr[100]+=500;

MyArr<byte> arr = new MyArr<byte>();

// does nothing special
arr[100]+=50;

if answer is no, then I'm going to use an extra Interface to implement this feature, but I'm not sure it's okay to add another interface to project just for a single feature. (should I ask "is adding another interface just for a single method okay?" in another question?)

like image 441
huseyin tugrul buyukisik Avatar asked Dec 30 '25 14:12

huseyin tugrul buyukisik


2 Answers

I think the following might be helpful:

public class GenericType<T>
{
    public virtual T Test0()
    {
        return default(T);
    }

    public virtual T this[int i]
    {
        get { return default(T); }
        set { }
    }
}

public class FloatType : GenericType<float>
{
    public override float Test0()
    {
        return 0;
    }

    public override float this[int i]
    {
        get { return 0; }
        set {  }
    }
}


GenericType<float> nonOptimizedFloat = new GenericType<float>();
var defVal = nonOptimizedFloat[3]; // will use the non-optimized version

GenericType<float> optimizedFloat = new FloatType();
defVal = optimizedFloat[3]; // will use the optimized version

You can have as many optimized types as you'd like and still keep some common logic in the base class.

You can also consider defining the base class as abstract and ensure that the optimized versions will always be used.

like image 124
Ofir Winegarten Avatar answered Jan 01 '26 03:01

Ofir Winegarten


You can use a helper class which contains a nested generic class with Func/Action delegate to implement specialization of Indexer/Property/Method.

internal static class IndexerImpl
{
    private static T IndexerDefaultImpl<T>(int i) => default(T); //default implementation

    private static T IndexerImpl2<T>(int i) => default(T); //another implementation for short/int/long

    private static string IndexerForString(int i) => (i * i).ToString(); //specialization for T=string
    private static DateTime IndexerForDateTime(int i) => new DateTime(i * i * i); //specialization for T=DateTime

    static IndexerImpl() //install the specializations
    {
        Specializer<string>.Fun = IndexerForString;
        Specializer<DateTime>.Fun = IndexerForDateTime;

        Specializer<short>.Fun = IndexerImpl2<short>;
        Specializer<int>.Fun = IndexerImpl2<int>;
        Specializer<long>.Fun = IndexerImpl2<long>;
    }

    internal static class Specializer<T> //specialization dispatcher
    {
        internal static Func<int, T> Fun;
        internal static T Call(int i)
            => null != Fun
                ? Fun(i)
                : IndexerDefaultImpl<T>(i);
    }
}

public class YourClass<T>
{
    public T this[int i] => IndexerImpl.Specializer<T>.Call(i);
}

If you need the instance of YourClass to calculate the return value, you can add a paramter to pass the needed information:

internal static class IndexerImpl
{
    private static T IndexerDefaultImpl<T>(int i, YourClass<T> yourClass) => default(T); //default implementation

    private static T IndexerImpl2<T>(int i, YourClass<T> yourClass) => default(T); //another implementation for short/int/long

    private static string IndexerForString<T>(int i, YourClass<T> yourClass) => (i * i).ToString(); //specialization for T=string
    private static DateTime IndexerForDateTime<T>(int i, YourClass<T> yourClass) => new DateTime(i * i * i); //specialization for T=DateTime

    static IndexerImpl() //install the specializations
    {
        Specializer<string>.Fun = IndexerForString;
        Specializer<DateTime>.Fun = IndexerForDateTime;

        Specializer<short>.Fun = IndexerImpl2;
        Specializer<int>.Fun = IndexerImpl2;
        Specializer<long>.Fun = IndexerImpl2;
    }

    internal static class Specializer<T> //specialization dispatcher
    {
        internal static Func<int, YourClass<T>, T> Fun;
        internal static T Call(int i, YourClass<T> yourClass)
            => null != Fun
                ? Fun(i, yourClass)
                : IndexerDefaultImpl(i, yourClass);
    }
}

public class YourClass<T>
{
    public T this[int i] => IndexerImpl.Specializer<T>.Call(i, this);
}

When specializing a generic Indexer, a non-generic helper class is needed.

When specializing a generic Property/Method of non-generic class, the default implementation/specializations and nested specialization dispatcher class can be put directly in your class.

What you have to pay attention is, do not put installation of specializations into static constructor of a generic class.


Additionally, if you need partial specialization, you can use a generic helper class, with parameterized types which do not need to specialize:

internal static class GetValueImpl<R, S>
{
    private static T DefImpl<T>(R r, S s) => default(T);
    private static int IntRet(R r, S s) => int.MaxValue;

    internal static class Specializer<T>
    {
        internal static Func<R, S, T> Fun;
        internal static T Call(R r, S s) => null != Fun ? Fun(r, s) : DefImpl<T>(r, s);
    }

    static GetValueImpl()
    {
        Specializer<int>.Fun = IntRet;
    }
}

public class TestClass
{
    //R and S are not specialized, we are specializing T
    public T GetValue<R, S, T>(R r, S s) => GetValueImpl<R, S>.Specializer<T>.Call(r, s);
}

Using this way to implement partial specialization, you have to aware that, the static constructor of helper class will run multiple times if you call nested specializer class multiple times with different non-specialized types:

public void Test()
{
     var foo = new TestClass();

     //GetValueImpl<long, long> will be created at runtime
     var v1 = foo.GetValue<long, long, int>(1, 2);    //GetValueImpl<long, long>.Specializer<int> will be called, specialized
     var v2 = foo.GetValue<long, long, string>(1, 2); //GetValueImpl<long, long>.Specializer<string> will be called
     var v3 = foo.GetValue<long, long, long>(1, 2);   //GetValueImpl<long, long>.Specializer<long> will be called

     //GetValueImpl<long, int> will be created at runtime
     var v4 = foo.GetValue<long, int, int>(1, 2);     //GetValueImpl<long, int>.Specializer<int> will be called, specialized
     var v5 = foo.GetValue<long, int, double>(1, 2);  //GetValueImpl<long, int>.Specializer<double> will be called
}

Note that the static constructor will run 2 times for GetValueImpl<long, long> and GetValueImpl<long, int>, so do not put extra code in static constructor of helper class.

like image 43
qaqz111 Avatar answered Jan 01 '26 03:01

qaqz111



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!