通常我在 struct 和 class 之间选择不是因为内存问题,而是因为类型的语义。我的一些值类型有相当大的内存占用,有时太大而无法一直复制这些数据。所以我想知道总是通过引用传递不可变值对象是否是个好主意?由于对象是不可变的,它们不能被通过引用接受它们的方法修改。通过引用传递时是否还有其他问题?
4 回答
我的一些值类型有相当大的内存占用
从实现的角度来看,这表明它们不应该是值类型。来自“开发类库的设计指南”,“在类和结构之间选择”部分:
除非类型具有以下所有特征,否则不要定义结构:
- 它在逻辑上表示单个值,类似于原始类型(整数、双精度等)。
- 它的实例大小小于 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
完全不同的价值来替换整个价值。
当涉及到引用类型时,不可变性更容易考虑 - 尽管即使那样你也可以拥有一个本身不会改变的对象,但可以引用可变对象......
乔恩的回答当然是正确的;我会添加它:当您调用值类型的方法时,值类型已经通过引用传递。例如:
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 是对的:通过 ref 传递结构意味着传递对变量的引用,并且变量可以更改。这就是为什么它们被称为“变量”。C# 中没有 C++ 中的“const ref”;即使值类型本身似乎是“不可变的”,但这并不意味着持有它的变量是不可变的。你可以在这个人为但有教育意义的例子中看到一个极端的例子:
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和this都是变量,变量可以改变:
S q = new S(1);
q.M(ref q);
打印 1, 2 因为this
和s
都是对 的引用q
,并且没有什么可以阻止q
改变;它不是只读的。
简而言之:如果我有很多我想传递的数据并且有强有力的保证它是不可变的,我会使用一个类,而不是一个结构。仅当您有一个已证明的性能问题实际上可以通过将其设置为 struct 来解决时,才在该场景中使用 struct,请记住,复制大型结构可能非常昂贵。
所以我想知道总是通过引用传递不可变值对象是否是个好主意?由于对象是不可变的,它们不能被通过引用接受它们的方法修改。通过引用传递时是否还有其他问题?
目前还不清楚你的意思。假设您的意思是将其作为ref
orout
参数传递,则该方法只能将新实例分配给存储位置。这将修改调用者看到的内容,因为被调用者中的存储位置是调用者传递的存储位置的别名。
如果你因为复制 around 的实例而处理内存问题struct
,你应该考虑创建一个不可变的引用类型,就像string
.
我认为实际上一个坏主意是当你所做的一切都指向使用类时使用结构
相关答案:https ://stackoverflow.com/questions/2440029/is-it-a-bad-practice-to-pass-structs-by-reference