通用索引器重载专业化

3

我知道C#中没有模板特化,但是这个方法可以(至少在编译时):

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

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

这个甚至无法编译:

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

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

也不是这样

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

在浮点数版本上,它说“...已经定义了一个与相同参数类型的'member'”。我可以在通用的[]访问器中检查所有类型,但太多的if-elseiftypeof()会降低性能(我将使用这个作为非托管数组而不是托管数组),而dynamic不是一个选择,因为我主要使用.Net 3.5。在另一个问题中,我了解到我无法获取泛型类型的指针,所以我决定使用多个过载,每个过载用于float,double,int和byte类型,并在此数组访问过载中使用这些过载(但不在同一个方法中,以不损失性能)。
一个通用的[]是否可以由一些基本类型(如float)进行重载?更具体地说,一个类是否只能像下面的例子一样被使用?
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;

如果答案是否定的,那么我将使用一个额外的“接口”来实现这个功能,但我不确定仅为了一个单一的功能添加另一个接口是否合适。(我应该在另一个问题中问“仅为了一个方法而添加另一个接口是否可以?”)

3
第一种情况有两个不同的重载,其中一个带有通用类型参数,而对于索引器,您有相同的签名,除了返回类型不同,因此无法重载。 - Ehsan Sajjad
我不完全确定,也许有办法,让我们看看是否有人回答。 - Ehsan Sajjad
1
@dlatikay 当添加更多类型时,将会有20-30个不同的版本,因此为了执行c[i]=a[i]+b[i]而检查30次if-else语句将比使用数组更耗时。我之所以问这个问题是因为C++端已经被优化,gpgpu部分也已经准备好并且被优化,只剩下这个C#访问部分需要优化。我正在优化瓶颈部分。例如:某人用2秒钟初始化数组,但gpgpu只需要0.5秒钟。但是使用C++数组和指针,两者都只需要0.5秒钟。 - huseyin tugrul buyukisik
1
当您访问myObject[42]时,您期望调用哪个索引器重载? - O. R. Mapper
索引器不能包含泛型参数(就像属性一样)。一个选项是删除索引器,只使用常规方法:Get<T>(int index) {} Set<T>(int index, T value);。是的,不太方便,但如果您正在进行性能优化-那可能是一个选项。 - Evk
显示剩余8条评论
2个回答

1
你可以使用一个包含嵌套泛型类的helper class,该类包含Func/Action委托,以实现索引器/属性/方法的特化。
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);
}

如果你需要YourClass的实例来计算返回值,你可以添加一个参数来传递必需的信息:
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);
}

当专门化一个通用的Indexer时,需要一个非通用的帮助类。
当专门化一个非通用类的通用属性/方法时,可以直接将默认实现/专门化和嵌套的专门化分派类放在您的类中。
需要注意的是,不要将专门化的安装放入通用类的静态构造函数中。
此外,如果您需要部分特化,可以使用一个通用的辅助类,其中参数化类型不需要进行特化:
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);
}

使用这种方式实现部分特化时,您需要注意,如果您使用不同的非特化类型多次调用嵌套的特化类,则助手类的静态构造函数将运行多次:
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
}

请注意,对于GetValueImpl<long, long>GetValueImpl<long, int>,静态构造函数将运行2次,因此不要在帮助类的静态构造函数中放置额外的代码。

1
我认为以下内容可能会有帮助:

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

你可以拥有尽可能多的优化类型,同时仍然保留基类中的一些通用逻辑。
你还可以考虑将基类定义为抽象类,并确保始终使用优化版本。

1
或者我只需将 this [int i] 设为抽象,而不是整个类,然后使其他方法虚拟化,这样其他人就不需要为每种类型重新编写了?(类似于使用接口,强制只实现子类中的某些方法)。当类型在运行时更改时,接口是否与您的解决方案行为相同? - huseyin tugrul buyukisik
当然,这取决于您。如果您不想让其他函数被覆盖,那么您不必将它们声明为虚函数。无论如何,该类本身也必须是抽象的。 - Ofir Winegarten
由于这个规则,如果任何一个方法是抽象的,那么类本身也必须是抽象的吗? - huseyin tugrul buyukisik
是的,没错。至于接口——如果你需要一些常见的实现,它们并没有什么帮助。 - Ofir Winegarten
抽象类无法实例化,因此无法以“相同编写”+“不同优化”的方式实现。 - huseyin tugrul buyukisik
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接