【发布时间】:2011-01-18 06:29:59
【问题描述】:
前言
tl;wr:这是一个讨论。
我知道这个“问题”更多的是讨论,因此我将其标记为社区 wiki。但是,根据How to Ask 页面,它可能属于这里,因为它专门与编程相关,经过一个小时的研究,在网络上的任何地方都没有讨论过,具体,与大多数 C# 程序员相关,并且是主题。此外,这个问题本身就是为了获得答案,不管我有什么偏见,我都会保持开放的态度:C# 真的会从聚合结构中受益吗?尽管有这个前言,我还是明白这一点被关闭,但如果有权限和意图关闭的用户将我重定向到网络上的适当讨论点,我将不胜感激。
简介
缺乏结构可变性
结构是 C# 中灵活但有争议的类型。它们提供堆栈分配的值类型组织范式,但不提供其他值类型的不变性。
有些人说结构应该代表值,并且值不会改变(例如int i = 5;,5 是不可变的),而有些人认为它们是带有子字段的 OOP 布局。
关于结构不变性(1,2,3)的争论,目前的解决方案似乎是让程序员强制执行不变性,但也没有解决。
例如,当结构作为引用(this page 的底部)访问时,C# 编译器将检测可能的数据丢失并限制分配。此外,由于结构构造函数、属性和函数能够执行任何操作,限制(对于构造函数)在返回控件之前分配所有字段,结构cannot be declared as constant,如果它们仅限于数据表示,这将是一个正确的声明.
结构、聚合的不可变子集
聚合 类 (Wikipedia) 是功能有限的严格数据结构,由于缺乏灵活性,注定要提供语法糖。在 C++ 中,它们“没有用户声明的构造函数,没有私有或受保护的非静态数据成员,没有基类,也没有虚函数”。尽管核心概念保持不变,但 C# 中此类类的理论细节在此有待商榷。
由于聚合结构严格来说是带有标记访问器的数据持有者,因此它们的不变性(在可能的 C# 上下文中)将得到保证。聚合也不能为空,除非指定了空运算符 (?),对于其他纯值类型。因此,许多非法的结构操作以及一些语法糖都成为可能。
用途
- 可以将聚合声明为 const,因为它们的构造函数将被强制执行,除了分配字段之外什么都不做。
- 聚合可用作方法参数的默认值。
- 聚合可以是隐式顺序的,便于与本地交互
- 聚合将是不可变的,不会对引用访问执行任何数据丢失。此类子字段修改的编译器检测可能会导致完整的、隐式的 reassignment.libraries。
假设语法
从 C++ 语法中,我们可以想象以下内容: (请记住,这是一个社区 wiki,欢迎和鼓励改进)
aggregate Size
{
int Width;
int Height;
}
aggregate Vector
{
// Default values for constructor.
double X = 0, Y = 0, Z = 0;
}
aggregate Color
{
byte R, G, B, A = 255;
}
aggregate Bar
{
int X;
Qux Qux;
}
aggregate Qux
{
int X, Y;
}
static class Foo
{
// Constant is possible.
const Size Big = new Size(200, 100);
// Inline constructor.
const Vector Gravity = { 0, -9.8, 0 };
// Default value / labeled parameter.
const Color Fuschia = { 255, 0, 255 };
const Vector Up = { y: 1 };
// Sub-aggregate initialization
const Bar Test = { 20, { 4, 3 } };
static void SetVelocity(Vector velocity = { 0, 1, 0 }) { ... }
static void SetGravity(Vector gravity = Foo.Gravity) { ... }
static void Main()
{
Vector v = { 1, 2, 3 };
double y = v.Y; // Valid.
v.Y = 5; // Invalid, immutable.
}
}
隐式(重新)赋值
截至今天,在 C# 4.0 中分配结构的子字段是有效的:
Vector v = new Vector(1, 2, 3);
v.Z = 5; // Legal in current C#.
但是,有时,编译器可以检测结构何时被错误地作为引用访问,并禁止更改子字段。例如,(example question)
//(in a Windows.Forms context)
control.Size.Width = 20; // Illegal in current C#.
由于Size 是一个属性而struct Size 是一个值类型,我们将编辑实际属性的副本/克隆,在这种情况下这将毫无用处。作为 C# 用户,我们倾向于假设大多数东西都是通过引用来访问的,尤其是在 OOP 设计中,这会让我们认为这样的调用是合法的(如果 struct Size 是 class,它就是合法的)。
此外,在访问集合时,编译器还禁止我们修改struct子字段:(example question)
List<Vector> vectors = ... // Imagine populated data.
vectors[4].Y = 10; // Illegal in current C#.
关于这些不幸限制的好消息是,对于这种情况,编译器做了一半可能的聚合解决方案:检测它们何时发生。另一半是隐式地重新分配具有更改值的新聚合。
- 在本地范围内,只需重新分配向量即可。
- 在外部范围内时,找到一个 get,如果匹配的 set 访问器可访问,则重新分配给这个。
为此,为了避免混淆,委托必须标记为隐式:
implicit aggregate Vector { ... }
implicit aggregate Size { ... }
// Example 1
{
Vector v = new Vector(1, 2, 3);
v.Z = 5; // Legal with implicit aggregates.
// What is implicitly done:
v = new Vector(v.X, v.Y, 5); // Local variable, simply reassign.
}
// Example 2
{
//(in a Windows.Forms context)
control.Size.Width = 20; // Legal with implicit aggregates.
// What is implicitly done:
Size old = control.Size.__get(); // External, MSIL detects a get.
// If MSIL can find a matching, accessible __set:
control.Size.__set({ 20, old.Height });
}
// Example 3
{
List<Vector> vectors = ... // Imagine populated data.
vectors[4].Y = 10; // Legal with implicit aggregates.
// What is implicitly done:
Vector old = vectors[4].__get(); // External, MSIL detects a get.
// If MSIL can find a matching, accessible __set:
vectors[4].__set({ old.X, 10, old.Z });
}
// Example 4
{
Vector The5thVector(List<Vector> vectors) { return vectors[4]; }
...
List<Vector> vectors = ...;
The5thVector(vectors).Y = 10; // Illegal with implicit aggregates.
// This is illegal because the compiler cannot find an implicit
// "set" to match. as it is a function return, not a property or
// indexer.
}
当然,这最后的隐式重新赋值只是一种句法简化,可以或不能采用。我只是提出它,因为编译器似乎能够检测到对结构的这种引用访问,并且如果它是一个聚合,它可以很容易地为程序员转换代码。
总结
- 聚合可以有字段;
- 聚合是值类型;
- 聚合是不可变的;
- 聚合在堆栈上分配;
- 聚合不能继承;
- 聚合具有顺序布局;
- 聚合具有顺序的默认构造函数;
- 聚合不能有用户定义的构造函数;
- 聚合可以有默认值和标签结构;
- 可以内联定义聚合;
- 聚合可以声明为常量;
- 聚合可用作默认参数;
- 除非指定,否则聚合不可为空 (
?);
可能:
- 聚合(可以)被隐式重新分配;请参阅 Marcelo Cantos 的回复和评论。
- 聚合(可能)有接口;
- 聚合(可能)有方法;
缺点
由于聚合不会取代结构,而是另一种组织方案,我找不到很多缺点,但希望 S/O 的 C# 资深人士能够填充这个 CW 部分。最后一点,请直接回答这个问题,并讨论它:C# 是否有利于聚合类,如本文所述?我无论如何都不是 C# 专家,而只是 C# 语言的爱好者,并且怀念这个对我来说似乎至关重要的功能。我正在向有经验的程序员寻求有关此案例的建议和 cmet。 我知道有许多变通方法存在并且每天都在积极使用它们,我只是认为它们太常见了,不容忽视。
【问题讨论】:
-
如果我理解正确,您希望结构具有写时复制语义吗?
-
这是一个假设聚合类的特征中的一个建议,但正如 Marcelo Cantos 指出的那样,它应该被忽略。
-
Wiki,然而,迁移到程序员可能是更好的选择。
-
如果能够简单地通过列出其字段并让编译器为它自动生成一个构造函数来定义一个可变结构,那就太好了。不过,我可以看到让这样的声明产生除普通结构之外的任何东西的唯一优势是引入协方差的可能性(因此,即使
KeyValuePair是可变 POD,也可以将KeyValuePair<String, Button>提供给期望KeyValuePair<String, Control>)。请注意,对于 POD,这种协方差始终是安全的... -
...因为传递一个拆箱的 POD 总是会复制数据,拆箱 POD 也是如此。传递盒装 POD 不会复制数据,但由于盒装 POD 不能在不先拆箱的情况下进行变异,因此任何人都无法获得与他们期望的确切类型不同的可变 POD(请注意,结构并非严格如此,因为它们可以实现非协变的接口)。另外,顺便说一句,通过在该字段中创建具有不同值的临时结构实例来更改公开结构中的字段,然后将临时结构复制到原始结构上是愚蠢的。