使用受约束的泛型而不是接口——有缺点吗?
假设我
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 { ... }
}
会避免装箱或复制结构。
它工作得很好,一切都很好,但是有什么我不知道的缺点吗(除了内存使用量的增加可以忽略不计)?这是一种可以接受的做法,还是因为我可能不知道的任何原因而不鼓励这样做?
Let's say I have
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 { ... } }
}
Sometimes, instead of just saying
static class Matrices {
static IMatrix Multiply(IMatrix a, IMatrix b) { ... }
}
I end up doing
static class Matrices {
static IMatrix Multiply<T1, T2>(T1 a, T2 b)
where T1 : IMatrix
where T2 : IMatrix { ... }
}
or maybe even
static class Matrices {
static IMatrix Multiply<T1, T2>([In] ref T1 a, [In] ref T2 b)
where T1 : IMatrix
where T2 : IMatrix { ... }
}
to avoid boxing or copying struct
s.
It works fine and everything, but are there any downsides I don't know about (other than a negligible increase in memory usage)? Is this an accepted practice, or is it discouraged for any reason I might not be aware of?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
泛型的成本很小,主要是围绕较大的代码大小。 Joe Duffy 最近的博客文章 相当详细地介绍了这。然而,一般来说,避免对频繁调用的代码进行装箱是一件好事,并且可能值得生成更多字节代码(实际上,这意味着稍微更高的内存使用量和 JIT 的更多工作)。
Generics come with a small cost, mostly around larger code size. A recent blog post from Joe Duffy gives a fairly detailed look at this. However, in general, avoiding boxing for frequently called code is a good thing and probably worth more generated byte code (in practice, this means slightly higher memory usage and more work for the JIT).
接口约束很棒,但有一个重要的警告:虽然结构可能具有不可变语义、可变值语义或可变引用语义(),但装箱结构如果可变,则始终具有引用语义。虽然可变值语义通常很有用(*),但要让它们与泛型很好地配合工作可能很困难。甚至考虑一个问题,比如矩阵相乘的参数是否应该通过引用传递或通过值传递:如果泛型类型是值类型,则参数可能应该通过引用传递;如果是类类型,则应按值传递。
作为参考/值区别的另一个例子,假设一个方法有一个矩阵,它想要的是一个与旧矩阵一样的矩阵,除了元素 (0,0) 和 (1,1) 归零,并且它不再需要原始矩阵。如果矩阵是具有可设置索引属性的可变值类型,则该方法可以简单地写入元素 (0,0) 和 (1,1),而不会产生不需要的副作用;如果它是可变引用类型,则例程可能必须首先进行防御性克隆(ick);如果它是不可变类型,则可能必须为每次修改创建一个新实例(ick)。使用值类型可以提供更清晰的语义,但如果系统执行诸如装箱之类的代码转换,将值类型语义更改为引用语义或损坏的语义,则可能会导致意外的副作用。
(*) 简单地公开可变字段的结构表现出非常干净的可变值类型语义。给出声明:
只要看上面的代码,就可以确定指示的方法调用可能会影响 y.value,但不会影响 x.value,也不会导致 y.value 在方法调用返回后随时发生变化。相比之下,如果 valueStruct 是一个类,则无法判断指示的调用会对 x.value 执行什么操作,也无法判断它是否会导致 y.value 在某个任意未来时间发生更改。
尽管具有公开字段的结构实现可变值语义,但如果结构持有类类型的不可变字段,并且它们公开的唯一变元作用于该字段,则结构可以实现可变引用语义。有时,这可能是一种有用的模式,尽管有些限制,因为 C# 和 vb 编译器将禁止看起来可能试图改变结构的操作(但实际上会改变它所引用的类对象)。请注意,具有可变引用语义的不可变结构必须使用非默认构造函数初始化才能有用。通过默认初始化创建的此类结构通常会被破坏且无用;最好的情况下,它们彼此无法区分。
Interface constraints are wonderful, but there is an important caveat: while it's possible for structures to have either immutable semantics, mutable value semantics, or mutable reference semantics(), boxed structures, if mutable, always have reference semantics. While mutable value semantics are often useful(*), it can be difficult to make them work nicely with generics. Consider even a question like whether the parameters to the matrix multiply you list should be passed by reference or by value: if the generic type is a value type, the parameters should probably be passed by reference; if it's a class type, they should be passed by value.
As another example of the reference/value distinction, suppose a method has a matrix, and what it wants to have is a matrix which is just like the old one except with elements (0,0) and (1,1) zeroed, and it won't need the original matrix anymore. If the matrix is a mutable value type with a settable indexed property, the method can simply write to elements (0,0) and (1,1) without unwanted side-effects; if it's a mutable reference type the routine would likely have to make a defensive clone first (ick); if it's an immutable type, it may have to create a new instance for each modification (ick). Using a value type can offer much cleaner semantics, but can cause unexpected side-effects if the system does code transformations like boxing which change value-type semantics into reference semantics or broken semantics.
(*) A struct which simply exposes mutable fields exhibits very clean mutable value-type semantics. Given the declarations:
Looking just at the above code, one can determine that indicated method call may affect y.value, but will not affect x.value, nor will it cause y.value to change at any time after the method call has returned. By contrast, if valueStruct were instead a class, there's no telling what the indicated call could do to x.value, nor any way of telling whether it might cause y.value to change at some arbitrary future time.
Although structures with exposed fields implement mutable value semantics, it is possible for structures to implement mutable reference semantics if they hold an immutable field of class type, and if the only mutators they expose act upon that field. This can at times be a useful pattern, though somewhat restricted because the C# and vb compilers will forbid operations that look like they might attempt to mutate the structure (but would really mutate the class object to which it holds a reference). Note that immutable structs with mutable reference semantics must be initialized with a non-default constructor in order to be useful. Such structs that are created via default initialization will generally be broken and useless; at best they will be indistinguishable from each other.