通常我在struct和class之间选择不是因为内存问题,而是因为类型的语义。我的一些值类型具有相当大的内存占用,有时太大而无法一直复制此数据。所以我想知道总是通过引用传递不可变值对象是一个好主意吗?由于对象是不可变的,因此不能通过引用接受它们的方法进行修改。通过引用传递时是否还有其他问题?
答案 0 :(得分:14)
我的一些值类型具有相当大的内存占用
从实现的角度来看,这表明它们不应该是值类型。从“开发类库的设计指南”,第"Choosing Between Classes And Structures"部分:
除非类型具有以下所有特征,否则不要定义结构:
- 它逻辑上表示单个值,类似于基本类型(整数,双精度等)。
- 实例大小小于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;
}
我们不会更改值的一部分 - 我们将x
的整个值替换为完全不同的值。
当涉及引用类型时,不变性更容易思考 - 尽管如此,你可以拥有一个本身不会改变的对象,但可以引用可变对象... < / p>
答案 1 :(得分:11)
Jon的回答当然是对的;我会添加它:当您在值类型上调用方法时,值类型已经通过引用传递。例如:
struct S
{
int x;
public S(int x) { this.x = x; }
public void M() { Console.WriteLine(this.x); }
}
方法M()在逻辑上与:
相同 public static void M(ref S _this) { Console.WriteLine(_this.x); }
每当你在结构上调用实例方法时,我们都会将 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);
}
}
M可以写出两个不同的数字吗?你会天真地认为结构是不可变的,因此x不能改变。但 s 和 这两个变量,而变量可以改变:
S q = new S(1);
q.M(ref q);
打印1,2,因为this
和s
都是对q
的引用,并且没有任何内容阻止q
更改; 它不是只读。
简而言之:如果我有很多数据需要传递并且有很强的保证它是不可变的,那么我将使用类,而不是结构。如果你有一个已经证明的性能问题,那么只在这个场景中使用一个结构,它实际上是通过使它成为一个结构来解决的,记住大型结构的复制可能非常昂贵。
答案 2 :(得分:3)
所以我想知道总是通过引用传递不可变值对象是一个好主意吗?由于对象是不可变的,因此不能通过引用接受它们的方法进行修改。通过引用传递时是否还有其他问题?
目前尚不清楚你的意思。假设您的意思是将其作为ref
或out
参数传递,则该方法只能将新实例分配给存储位置。这将修改调用者看到的内容,因为被调用者中的存储位置是调用者传递的存储位置的别名。
如果由于复制struct
的实例而处理内存问题,则应考虑使用不可变的引用类型,就像string
一样。
答案 3 :(得分:0)
我认为实际上糟糕的想法是当你所做的一切都指向使用类
时使用结构相关答案:https://stackoverflow.com/questions/2440029/is-it-a-bad-practice-to-pass-structs-by-reference