默认情况下通过引用传递不可变值类型

han*_*aad 5 c# value-type

通常我在struct和class之间选择不是因为内存问题而是因为类型的语义.我的一些值类型具有相当大的内存占用,有时太大而无法一直复制此数据.所以我想知道总是通过引用传递不可变值对象是不是一个好主意?由于对象是不可变的,因此不能通过引用接受它们的方法进行修改.通过参考传递时是否还有其他问题?

Jon*_*eet 14

我的一些值类型具有相当大的内存占用量

从实现的角度来看,这表明它们不应该是值类型.从"开发类库的设计指南","在类和结构之间选择 "部分:

除非类型具有以下所有特征,否则不要定义结构:

  • 它逻辑上表示单个值,类似于基本类型(整数,双精度等).
  • 它的实例大小小于16个字节.
  • 这是不可改变的.
  • 它不必经常装箱.

听起来你应该创建不可变的引用类型.在许多方面,他们最终会感觉像"价值对象"(想想字符串),但你不必担心传递它们的效率.

价值类型的"不变性"是一个略带流动的概念 - 它当然并不意味着使用ref是安全的:

// int is immutable, right?
int x = 5;
Foo(ref x);
Console.WriteLine(x); // Eek, prints 6...
...
void Foo(ref int y)
{
    y = 6;
}
Run Code Online (Sandbox Code Playgroud)

我们不会改变价值的一部分 - 我们用x完全不同的价值取代整个价值.

在涉及引用类型时,不变性更容易思考 - 尽管如此,你可以拥有一个本身不会改变的对象,但可以引用可变对象......

  • @hansmaad:如果类型很可能*通过值*传递很多,那么*是*,你应该.如果值类型不太可能通过值传递 - 如果您只创建一个并在本地使用它而不将每个传递给另一个方法 - 那么请继续,如果您愿意,请将其设置为大. (3认同)
  • @hansmaad这只是一个指导方针.每个案例都不能涵盖,但它们是做出决定时要考虑的事项/规则. (2认同)
  • @hansmaad:Rico Mariani的[博客文章](http://blogs.msdn.com/b/ricom/archive/2006/09/07/745085.aspx)就违反这些指导方针进行了很好的讨论,目的是创建一个`Point3d`结构. (2认同)

Eri*_*ert 11

乔恩的回答当然是正确的; 我会添加它:当您在值类型上调用方法时,值类型通过引用传递.例如:

struct S
{
    int x;
    public S(int x) { this.x = x; }
    public void M() { Console.WriteLine(this.x); }
}
Run Code Online (Sandbox Code Playgroud)

方法M()在逻辑上与以下内容相同:

    public static void M(ref S _this) { Console.WriteLine(_this.x); }
Run Code Online (Sandbox Code Playgroud)

每当在结构上调用实例方法时,我们都会将ref传递给作为调用接收者的变量.

那么如果接收器不是变量呢?然后将该值复制到用作接收器的临时变量中.如果价值很大,这可能是一个昂贵的副本!

值类型按值复制; 这就是为什么他们被称为价值类型.除非您计划非常小心地找到所有可能的昂贵副本并消除它们,否则我将遵循框架设计指南的建议:将结构保持在16字节以下,并按值传递它们.

我还要强调Jon是对的:通过引用传递结构意味着将引用传递给变量,并且变量可以更改.这就是为什么他们被称为"变量".C#中没有"const ref",就像C++中的那样; 即使值类型本身似乎是"不可变的",也不意味着持有它的变量是不可变的.你可以在这个人为但教育的例子中看到一个极端的例子:

struct S
{
    readonly int x;
    public S(int x) { this.x = x; }
    public void M(ref S s)
    {
        Console.WriteLine(this.x);
        s = new S(this.x + 1);
        Console.WriteLine(this.x);
    }
}
Run Code Online (Sandbox Code Playgroud)

M可以写出两个不同的数字吗?你会天真地认为结构是不可变的,因此x不能改变.但是sthis都是变量,变量可以改变:

S q = new S(1);
q.M(ref q);
Run Code Online (Sandbox Code Playgroud)

,打印1,2,因为thiss有两个参考点q,并没有什么阻止q更改; 它不是只读的.

简而言之:如果我有很多数据需要传递并且有很强的保证它是不可变的,那么我将使用类,而不是结构.如果您已经通过将其作为结构实际解决了已证实的性能问题,则仅在该场景中使用结构,请记住,大型结构可能非常昂贵.