使用约束泛型而不是接口 - 缺点?

时间:2011-12-03 20:33:06

标签: c# generics interface constraints boxing

假设我有

interface IMatrix {
    double this[int r, int c] { get; }
}

struct Matrix2x2 : IMatrix  {
    double a1, a2, b1, b2;
    double this[int r, int c] { get { ... } }
}

struct Matrix3x3 : IMatrix {
    double a1, a2, a3, b1, b2, b3, c1, c2, c3;
    double this[int r, int c] { get { ... } }
}

class Matrix : IMatrix {    // Any size
    double[,] cells;
    double this[int r, int c] { get { ... } }
}

有时候,而不仅仅是说

static class Matrices {
    static IMatrix Multiply(IMatrix a, IMatrix b) { ... }
}

我最终做了

static class Matrices {
    static IMatrix Multiply<T1, T2>(T1 a, T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

或者甚至

static class Matrices {
    static IMatrix Multiply<T1, T2>([In] ref T1 a, [In] ref T2 b)
        where T1 : IMatrix
        where T2 : IMatrix { ... }
}

避免装箱或复制struct s。

它工作正常,但是有什么缺点我不知道(除了可忽略不计的内存使用量增加)?这是一种公认​​的做法,还是因为我可能不知道的任何原因而气馁?

2 个答案:

答案 0 :(得分:4)

泛型带来的成本很低,主要是围绕更大的代码大小。 recent blog post from Joe Duffy给出了相当详细的信息。但是,一般来说,避免对经常调用的代码进行装箱是一件好事,并且可能更值得生成字节代码(实际上,这意味着内存使用量略高,JIT工作量也更多)。

答案 1 :(得分:1)

接口约束很棒,但有一个重要的警告:虽然结构可能具有不可变语义,可变值语义或可变引用语义(),但是盒装结构(如果是可变的)总是具有引用语义。虽然可变值语义通常很有用( *),但是很难使它们与泛型一起很好地工作。甚至考虑一个问题,例如矩阵的参数乘以列表是应该通过引用还是通过值传递:如果泛型类型是值类型,则参数应该通过引用传递;如果它是一个类类型,它们应该按值传递。

作为参考/值区分的另一个例子,假设一个方法有一个矩阵,它想要的是一个矩阵,就像旧的一样,除了元素(0,0)和(1,1)归零,它不再需要原始矩阵了。如果矩阵是具有可设置索引属性的可变值类型,则该方法可以简单地写入元素(0,0)和(1,1)而没有不必要的副作用;如果它是一个可变的参考类型,那么例程可能必须首先制作一个防御性克隆(ick);如果它是不可变类型,则可能必须为每个修改创建一个新实例(ick)。使用值类型可以提供更清晰的语义,但如果系统执行像装箱这样的代码转换,将值类型语义更改为引用语义或破坏语义,则会导致意外的副作用。

(*)简单地公开可变字段的结构表现出非常干净的可变值类型语义。鉴于声明:

public struct valueStruct {public int value; ... }
public interface IManipulateValueStruct {public void manipulate(ref valueStruct it);}
public void someMethod(IManipulateValueStruct manipulator1, manipulator2)
{
  valueStruct x,y;
  ...
  manipulator1.Manipulate(ref x);
  manipulator2.Manipulate(ref y);  // What does this method do with x and y?
  ...
}

只看上面的代码,可以确定指示的方法调用可能会影响y.value,但不会影响x.value,也不会在返回方法调用后的任何时候导致y.value更改。相比之下,如果valueStruct是一个类,则无法确定指示的调用对x.value可以做什么,也无法告诉它是否可能导致y.value在某个未来任意时间发生变化。

尽管具有公开字段的结构实现了可变值语义,但如果结构具有类类型的不可变字段,并且它们公开的唯一更改器作用于该字段,则结构可能实现可变引用语义。这有时可能是一个有用的模式,虽然有些限制,因为C#和vb编译器将禁止看起来像他们可能试图改变结构的操作(但实际上会改变它持有引用的类对象)。请注意,必须使用非默认构造函数初始化具有可变引用语义的不可变结构,以便有用。通过默认初始化创建的这种结构通常会被破坏并且无用;充其量他们将无法区分。