C#会从聚合结构/类中受益吗?

Laz*_*zlo 8 c# struct aggregate class

前言

tl; wr:这是一个讨论.

我知道这个"问题"更多的是讨论,因此我将其标记为社区维基.但是,根据" 如何询问"页面,它可能属于此处,因为它具体与编程相关,经过一个小时的研究后无法在网络上讨论,具体而言,与大多数C#程序员和主题相关.而且,这个问题本身就是为了获得答案,不管我的偏见,我都会保持开放的态度:C#真的会从总体结构中受益吗?尽管有这一前言,但我知道这是关闭的,但如果有权限和意图关闭的用户将我重定向到Web上的适当讨论点,我将不胜感激.


介绍


缺乏结构可变性

结构是C#中的灵活但有争议的类型.它们提供堆栈分配的值类型组织范例,但不提供其他值类型的不变性.

有人说结构应该代表值,而值不会改变(例如int i = 5;,5是不可变的),而有些人认为它们是带有子字段的OOP布局.

上结构不变性(辩论1,2,3),为此,当前的解决方案似乎具有程序员执行不变性,也没有解决.

例如,当结构作为引用(本页底部)访问并限制赋值时,C#编译器将检测可能的数据丢失.此外,由于结构构造函数,属性和函数能够执行任何操作,在返回控件之前使用限制(对于构造函数)分配所有字段,结构不能声明为常量,如果它们仅限于数据表示.


不可变的结构子集,聚合

聚合类(维基百科)是严格的数据结构,功能有限,注定会因缺乏灵活性而在对应方面提供语法糖.在C++中,它们"没有用户声明的构造函数,没有私有或受保护的非静态数据成员,没有基类,也没有虚函数".尽管核心概念保持不变,但C#中此类类别的理论细节仍可供讨论.

由于聚合结构是严格的带有标记访问器的数据持有者,因此它们的不变性(在可能的C#环境中)将被保险.除非?指定了null运算符(),否则聚合也无法为空,无其他纯值类型.出于这个原因,许多非法的结构操作将成为可能,以及一些语法糖.


用途


  1. 聚合可以声明为const,因为它们的构造函数将被强制除了分配字段之外什么都不做.
  2. 聚合可以用作方法参数的默认值.
  3. 聚合可以是隐式顺序,促进与本机的交互
  4. 聚合将是不可变的,不会导致参考访问的数据丢失.编译器检测到这些子字段修改可能会导致完整的隐式重新分配.libraries.

假设语法


从C++语法中我们可以想象出以下几点:(记住,这是一个社区维基,欢迎并鼓励改进)

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

隐含(重新)赋值

截至今天,在C#4.0中分配结构的子字段是有效的:

Vector v = new Vector(1, 2, 3);
v.Z = 5; // Legal in current C#.
Run Code Online (Sandbox Code Playgroud)

但是,有时,编译器可以检测何时错误地将结构作为引用进行访问,并且将禁止更改子字段.例如,(示例问题)

//(in a Windows.Forms context)
control.Size.Width = 20; // Illegal in current C#.
Run Code Online (Sandbox Code Playgroud)

作为Size属性和struct Size值类型,我们将编辑实际属性的副本/克隆,在这种情况下这将是无用的.作为C#用户,我们倾向于假设大部分内容都是通过引用访问的,特别是在OOP设计中,这会让我们认为这样的调用是合法的(如果struct Size是a ,那就是class).

此外,在访问集合时,编译器还禁止我们修改struct子字段:( 示例问题)

List<Vector> vectors = ... // Imagine populated data.
vectors[4].Y = 10; // Illegal in current C#.
Run Code Online (Sandbox Code Playgroud)

关于这些不幸限制的好消息是编译器为这种情况做了一半可能的聚合解决方案:检测它们何时发生.另一半是隐式重新分配具有更改值的新聚合.

  • 在本地范围内时,只需重新分配矢量即可.
  • 在外部作用域中,找到一个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.
}
Run Code Online (Sandbox Code Playgroud)

当然,最后一次隐式重新分配只是一种语法简化,可以或不可以采用.我只是提出它,因为编译器似乎能够检测到对结构的这种引用访问,并且如果它是聚合的话,可以很容易地为程序员转换代码.


摘要

  • 聚合可以有字段;
  • 聚合是值类型;
  • 聚合是不可变的;
  • 聚合在堆栈上分配;
  • 聚合不能继承;
  • 聚合具有顺序布局;
  • 聚合具有顺序默认构造函数;
  • 聚合不能有用户定义的构造函数;
  • 聚合可以有默认值和标记结构;
  • 聚合可以内联定义;
  • 聚合可以声明为常量;
  • 聚合可以用作默认参数;
  • 除非指定(?),否则聚合是不可为空的;

可能是:

  • 聚合(可能)被隐式重新分配; 请参阅Marcelo Cantos的回复和评论.
  • 聚合(可能)具有接口;
  • 聚合(可能)有方法;

缺点

由于聚合不会取代结构而是另一种组织方案,我找不到很多缺点,但希望S#的C#资深人员能够填充这个CW部分.最后一点,请直接回答这个问题,然后再讨论一下:如本文所述,C#会对聚合类有益吗?我不是任何C#专家,但只是C#语言的狂热爱好者,并且错过了这个对我来说至关重要的功能.我正在寻求有经验的程序员就此案例提出建议和意见.我知道有很多解决方法存在并且每天都在积极使用它们,我只是认为它们太常见而不容忽视.

Eri*_*ert 5

我希望结构首先被定义为类似于你提出的语义.

然而,我们仍然坚持我们现在所拥有的东西,我认为我们不太可能在CLR中获得全新的"类型".引入一种新类型意味着将它引入每种 .NET语言,而不仅仅是C#,这是一个很大的变化.

我认为更有可能 - 并且记住,当我谈论假设的,未公布的未来产品的假设语言特征,这些产品不存在且可能永远不存在时,我这样做只是为了娱乐目的 - 我们会这样做找到一些方法在类和结构上做出更好的不变性注释和强制执行.无论所讨论的类型是值类型还是引用类型,编译器都可以更好地执行不变性并使其更容易以不可变样式进行编程.如果编译器或CLR在编译时或jit时具有更多的不变性保证,那么编译器或CLR也可以更好地优化在多核机器上运行的代码.

当你在斟酌你的提议时,你可能想要考虑的一个有趣的问题是:如果聚合类型有方法,那么"this"是还是变量?例如:

aggregate Vector
{
    int x, y, z;
    public void M(Action action)
    {
         Console.WriteLine(this.x);
         action();
         Console.WriteLine(this.x);
    }
}
...
Vector v = new Vector(1, 2, 3);
Action action = ()=>{ v = new Vector(4, 5, 6); };
v.M(action);
Run Code Online (Sandbox Code Playgroud)

怎么了?"this"是否通过传递给M ,在这种情况下它会两次写出"1",或者它是否作为对变量的引用传递,在这种情况下,您所谓的"不可变"类型会被观察到变异?(因为变异是变量 ;根据定义变量是允许变异的,这就是为什么它们被称为"变化能力".)