C# 结构是“按值传递”吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9251608/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Are structs 'pass-by-value'?
提问by Acidic
I've recently tried to create a property for a Vector2field, just to realize that it doesn't work as intended.
我最近尝试为一个Vector2字段创建一个属性,只是为了意识到它不能按预期工作。
public Vector2 Position { get; set; }
this prevents me from changing the values of its members (X& Y)
这会阻止我更改其成员的值 ( X& Y)
Looking up information on this, I read that creating a property to a Vector2struct returns only a copy of the original object and not a reference.
查找关于此的信息,我读到为Vector2结构创建属性只返回原始对象的副本,而不是引用。
As a Java developer this confuses me.
作为 Java 开发人员,这让我感到困惑。
When are objects in C# passed by value and when are they passed by reference?
Are all struct objects passed by value?
C#中的对象什么时候按值传递,什么时候按引用传递?
所有 struct 对象都按值传递吗?
采纳答案by Konrad Rudolph
It is important to realise that everything in C# is passed by value, unless you specify refor outin the signature.
重要的是要意识到C#中的所有内容都是按值传递的,除非您在签名中指定ref或out。
What makes value types (and hence structs) different from reference types is that a value type is accessed directly, while a reference type is accessed via its reference. If you pass a reference type into a method, its reference, not the value itself, is passed by value.
值类型(以及structs)与引用类型的不同之处在于值类型是直接访问的,而引用类型是通过其引用访问的。如果您将引用类型传递给方法,则它的引用,而不是值本身,是按值传递的。
To illustrate, imagine we have a classPointClassand a structPointStruct, defined analogously (omitting irrelevant details):
为了说明,假设我们有一个类PointClass和一个structPointStruct,定义类似(省略不相关的细节):
struct PointStruct { public int x, y; }
class PointClass { public int x, y; }
And we have a method SomeMethodthat takes these two types by value:
我们有一个方法SomeMethod可以按值获取这两种类型:
static void ExampleMethod(PointClass apc, PointStruct aps) { … }
If we now create two objects and call the method:
如果我们现在创建两个对象并调用该方法:
var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);
ExampleMethod(pc, ps);
…?we can visualise this with the following diagram:
...?我们可以用下图形象化:
Since pcis a reference, it doesn't contain the value in itself; rather, it references an (unnamed) value somewhere else in memory. This is visualised by the dashed border and the arrow.
由于pc是引用,它本身不包含值;相反,它引用了内存中其他地方的(未命名)值。这由虚线边框和箭头形象化。
But: for both pcand ps, the actual variable is copiedwhen calling the method.
但是:对于pc和ps,实际变量在调用方法时被复制。
What happens if ExampleMethodreassigns the argument variables internally? Let's check:
如果ExampleMethod在内部重新分配参数变量会发生什么?让我们检查:
static void ExampleMethod(PointClass apc, PointStruct aps); {
apc = new PointClass(2, 2);
aps = new PointStruct(2, 2);
}
Output of pcand psafter calling the method:
输出pc和ps调用该方法后:
pc: {x: 1, y: 1}
ps: {x: 1, y: 1}
→?ExampleMethodchanged a copy of the values, and the original values are unaffected.
→?ExampleMethod更改了值的副本,而原始值不受影响。
This, fundamentally, is what “pass by value” means.
从根本上说,这就是“传递价值”的含义。
There's still a difference between reference and value types, and that comes into play when modifying membersof the value, not the variable itself. This is the part that trips people up when they are confronted with the fact that reference types are passed by value. Consider a different ExampleMethod.
引用类型和值类型之间仍然存在差异,这在修改值的成员而不是变量本身时起作用。当人们面对引用类型是按值传递的事实时,这是让他们绊倒的部分。考虑一个不同的ExampleMethod.
static void ExampleMethod(PointClass apc, PointStruct aps) {
apc.x = 2;
aps.x = 2;
}
Now we observe the following result after calling the method:
现在我们在调用该方法后观察到以下结果:
pc: {x: 2, y: 1}
ps: {x: 1, y: 1}
→ The reference object was changed, whereas the value object wasn't. The diagram above shows why that is: for the reference object, even though pcwas copied, the actual value that both pcand apcreference remains identical, and we can modify that via apc. As for ps, we copied the actual value itself into aps; the original value cannot be touched by ExampleMethod.
→ 引用对象已更改,而值对象未更改。上图说明了原因:对于引用对象,即使pc被复制,两者pc和apc引用的实际值仍然相同,我们可以通过apc. 至于ps,我们将实际值本身复制到aps; 无法触及原始值ExampleMethod。
回答by Guffa
A structis a value type, so it's always passed as a value.
Astruct是值类型,因此它始终作为值传递。
A value can either be a reference type (object) or a value type (struct). What's passed around is always a value; for a reference type you pass the value of the reference to it, for a value type you pass the value itself.
值可以是引用类型(对象)或值类型(结构)。传递的总是一个值;对于引用类型,您将引用的值传递给它,对于值类型,您传递值本身。
The term by referenceis used when you use the refor outkeywords to pass a parameter. Then you are passing a reference to the variable that contains the value instead of passing the value. Normally a parameter is always passed by value.
当您使用or关键字传递参数时,将使用术语by reference。然后,您将传递对包含该值的变量的引用,而不是传递该值。通常,参数总是按值传递。refout
回答by Dan Bystr?m
Objects are passed by reference and structs by value. But note that you have the "out" and "ref" modifiers on arguments.
对象按引用传递,结构按值传递。但请注意,您在参数上有“out”和“ref”修饰符。
So you can pass a struct by reference like so:
所以你可以像这样通过引用传递结构:
public void fooBar( ref Vector2 position )
{
}
回答by Olivier Jacot-Descombes
The problem is, that the getter returns a copy of Vector2. If you change the coordinates like this
问题是,getter 返回Vector2. 如果你像这样改变坐标
obj.Position.X = x;
obj.Position.Y = y;
You only change the coordinates of this ephemeral copy.
您只需更改此临时副本的坐标。
Do this instead
改为这样做
obj.Position = new Vector2(x, y);
This has nothing to do with by value or by reference. Value2is a value type and getreturns this value. If the vector had a reference type (class), getwould return this reference. returnreturns values by value. If we have a reference type, then these references are the values and are returned.
这与按值或按引用无关。Value2是值类型并get返回此值。如果向量具有引用类型(类),get则将返回此引用。return按值返回值。如果我们有一个引用类型,那么这些引用就是值并返回。
回答by Avner Shahar-Kashtan
Yes, structs inherit from ValueType, and are passed by value. This is true for primitive types as well - int, double, bool, etc (but not string). Strings, arrays and all classes are reference types, and are passed by reference.
是的,结构继承自 ValueType,并按值传递。对于原始类型也是如此 - int、double、bool 等(但不是字符串)。字符串、数组和所有类都是引用类型,并通过引用传递。
If you want to pass a struct by ref, using the refkeyword:
如果要通过 ref 传递结构,请使用ref关键字:
public void MyMethod (ref Vector2 position)
which will pass the struct by-ref, and allow you to modify its members.
它将通过 struct by-ref,并允许您修改其成员。
回答by Jonathan Wood
.NET data types are divided into valueand referencetypes. Value types include int, byte, and structs. Reference types include stringand classes.
.NET 数据类型分为值类型和引用类型。值类型包括int,byte和struct秒。引用类型包括string和类。
structs are appropriate instead of classes when they just contain one or two value types (although even there you can have unintended side effects).
当结构体只包含一两个值类型时,结构体代替类是合适的(尽管即使在那里你也可能有意想不到的副作用)。
So structs are indeed passed by value and what you are seeing is expected.
所以结构确实是按值传递的,你所看到的都是预期的。
回答by supercat
Every storage location of a structure type holds all the fields, private and public, of that struct. Passing a parameter of a structure type entails allocating space for that structure on the stack and copying all of the fields from the structure to the stack.
结构类型的每个存储位置都包含该结构的所有字段,私有和公共字段。传递结构类型的参数需要在堆栈上为该结构分配空间并将所有字段从该结构复制到堆栈。
With regard to working with structures stored within a collection, using mutable structs with the existing collections generally requires reading out a struct to a local copy, mutating that copy, and the storing it back. Assuming MyList is a List<Point>, and one wants to add some local variable zto MyList[3].X:
关于处理存储在集合中的结构,将可变结构与现有集合一起使用通常需要将结构读出到本地副本,对该副本进行变异,然后将其存储回去。假设 MyList 是一个List<Point>,并且想要添加一些局部变量z到MyList[3].X:
Point temp = MyList[3]; temp.X += Z; MyList[3] = temp;
This is mildly annoying, but is often cleaner, safer, and more efficient than using immutable structs, way more efficient than immutable class objects, and way safer than using mutable class objects. I'd really like to see compiler support for a better way for collections to expose value-type elements. There are ways of writing efficient code to handle such exposure with good semantics (e.g. a collection object could react when elements were updated, without requiring those elements to have any link to the collection) but the code reads horribly. Adding compiler support in a manner conceptually similar to closures would allow efficient code to also be readable.
这有点烦人,但通常比使用不可变结构更干净、更安全、更有效,比不可变类对象更有效,比使用可变类对象更安全。我真的很想看到编译器支持更好的集合公开值类型元素的方法。有一些方法可以编写有效的代码来处理这种具有良好语义的暴露(例如,集合对象可以在元素更新时做出反应,而无需这些元素与集合有任何链接),但代码读起来很糟糕。以概念上类似于闭包的方式添加编译器支持将允许高效代码也可读。
Note that contrary to what some people claim, a structure is fundamentally differentfrom a class-type object, but for every structure type there is a corresponding type, sometimes referred to as a "boxed structure", which derives from Object (see the CLI (Common Language Infrastructure) specification, sections 8.2.4 and 8.9.7). Although the compiler will implicitly convert any struct into its corresponding boxed type when necessary to pass it to code that expects a reference to a class-type object, will allow references to boxed structs to have their contents copied into real structs, and will sometimes allow code to work with boxed structs directly, boxed structs behave like class objects, because that's what they are.
请注意,与某些人声称的相反,结构从根本上不同于类类型对象,但对于每个结构类型都有一个对应的类型,有时称为“盒装结构”,它派生自 Object(请参阅CLI (公共语言基础设施)规范,第 8.2.4 和 8.9.7 节)。尽管编译器会在必要时将任何结构隐式转换为其相应的装箱类型,以将其传递给需要对类类型对象的引用的代码,但将允许对装箱结构的引用将其内容复制到实际结构中,并且有时会允许直接处理装箱结构的代码,装箱结构的行为就像类对象,因为它们就是这样。
回答by drzaus
Just to illustrate the different effects of passing struct vs class through methods:
只是为了说明通过方法传递结构与类的不同效果:
(note: tested in LINQPad 4)
(注意:在LINQPad 4 中测试)
Example
例子
/// via http://stackoverflow.com/questions/9251608/are-structs-pass-by-value
void Main() {
// just confirming with delegates
Action<StructTransport> delegateTryUpdateValueType = (t) => {
t.i += 10;
t.s += ", appended delegate";
};
Action<ClassTransport> delegateTryUpdateRefType = (t) => {
t.i += 10;
t.s += ", appended delegate";
};
// initial state
var structObject = new StructTransport { i = 1, s = "one" };
var classObject = new ClassTransport { i = 2, s = "two" };
structObject.Dump("Value Type - initial");
classObject.Dump("Reference Type - initial");
// make some changes!
delegateTryUpdateValueType(structObject);
delegateTryUpdateRefType(classObject);
structObject.Dump("Value Type - after delegate");
classObject.Dump("Reference Type - after delegate");
methodTryUpdateValueType(structObject);
methodTryUpdateRefType(classObject);
structObject.Dump("Value Type - after method");
classObject.Dump("Reference Type - after method");
methodTryUpdateValueTypePassByRef(ref structObject);
methodTryUpdateRefTypePassByRef(ref classObject);
structObject.Dump("Value Type - after method passed-by-ref");
classObject.Dump("Reference Type - after method passed-by-ref");
}
// the constructs
public struct StructTransport {
public int i { get; set; }
public string s { get; set; }
}
public class ClassTransport {
public int i { get; set; }
public string s { get; set; }
}
// the methods
public void methodTryUpdateValueType(StructTransport t) {
t.i += 100;
t.s += ", appended method";
}
public void methodTryUpdateRefType(ClassTransport t) {
t.i += 100;
t.s += ", appended method";
}
public void methodTryUpdateValueTypePassByRef(ref StructTransport t) {
t.i += 1000;
t.s += ", appended method by ref";
}
public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) {
t.i += 1000;
t.s += ", appended method by ref";
}
Results
结果
(from LINQPad Dump)
(来自 LINQPad 转储)
Value Type - initial
StructTransport
UserQuery+StructTransport
i 1
s one
Reference Type - initial
ClassTransport
UserQuery+ClassTransport
i 2
s two
//------------------------
Value Type - after delegate
StructTransport
UserQuery+StructTransport
i 1
s one
Reference Type - after delegate
ClassTransport
UserQuery+ClassTransport
i 12
s two, appended delegate
//------------------------
Value Type - after method
StructTransport
UserQuery+StructTransport
i 1
s one
Reference Type - after method
ClassTransport
UserQuery+ClassTransport
i 112
s two, appended delegate, appended method
//------------------------
Value Type - after method passed-by-ref
StructTransport
UserQuery+StructTransport
i 1001
s one, appended method by ref
Reference Type - after method passed-by-ref
ClassTransport
UserQuery+ClassTransport
i 1112
s two, appended delegate, appended method, appended method by ref
回答by Chris Marisic
Foreward: C# while managed still has the core memory idioms created by C. Memory can be reasonably viewed as a giant array where the index in the array is labeled the "memory address". A pointeris a numeric index of this array aka a memory address. The values in this array can either by data or a pointer to another memory address. A const pointeris a value stored in this array at some index which cannot change. A memory address inherently exists and can never change however the value that is located at that address can always change if it is notconst.
前言:C# 在托管时仍然具有由 C 创建的核心内存习惯用法。内存可以合理地视为一个巨大的数组,其中数组中的索引被标记为“内存地址”。甲指针是这一阵列又名的存储器的数字索引地址。该数组中的值可以是数据,也可以是指向另一个内存地址的指针。甲常量指针是在一些索引,其存储在该阵列中的值不能改变。内存地址固有地存在并且永远不会改变,但是如果它不是常量,则位于该地址的值总是可以改变。
Pass by class
按班级通过
A class / reference type (which includes string) is passed by a const pointer reference. Mutation will affect all usages of this instance. You cannot change the address of the object. If you attempt to change the address with either assignment or newyou will in effect create a local variable that shares the same name as the parameter in the current scope.
类/引用类型(包括字符串)由 const 指针引用传递。变异将影响此实例的所有用法。您不能更改对象的地址。如果您尝试使用赋值来更改地址,或者new您实际上将创建一个与当前作用域中的参数共享相同名称的局部变量。
Pass by copy
通过副本
Primitives / ValueTypes / structs (strings are neither even though they disingenuously pose as them) are completely copied when returned from a method, property, or received as a parameter. Mutation of a struct will never be shared. If a struct contains a class member, what is copied is the pointer reference. This member object would be mutable.
当从方法、属性返回或作为参数接收时,原语 / ValueTypes / 结构体(字符串都不是,即使它们不诚实地伪装成它们)被完全复制。结构的变异永远不会被共享。如果结构体包含类成员,则复制的是指针引用。这个成员对象是可变的。
Primitives are never mutable. You cannot mutate 1 to 2, you can mutate the memory address that currently refers to 1 to the memory address of 2 in the current scope.
原语从不可变。不能将 1 变异为 2,可以将当前引用 1 的内存地址变异为当前作用域中 2 的内存地址。
Pass by truereference
通过真实引用
Requires the usage of outor refkeywords.
需要使用out或ref关键字。
refwill allow you to alter the pointer a newobject or assign an existing object. refwill also allow you to pass a primitive / ValueType / struct by it's memory pointer to avoid copying the object. It would also allow you to replace the pointer to a different primitive if you assign to it.
ref将允许您更改指针new对象或分配现有对象。ref还将允许您通过它的内存指针传递原始/ValueType/结构以避免复制对象。如果您分配给它,它还允许您将指针替换为不同的原语。
outis semantically identical to refwith one minor difference. refparameters are required to be initialized where outparameters are allowed to uninitialized as they are required to be initialized in the method that accepts the parameter. This is commonly shown in the TryParsemethods and eliminates the need for you to have int x = 0; int.TryParse("5", out x)when the initial value of x would serve no purpose.
out在语义上ref与一个细微差别相同。ref需要初始化out参数,其中允许未初始化参数,因为它们需要在接受参数的方法中进行初始化。这通常显示在TryParse方法中,并且int x = 0; int.TryParse("5", out x)当 x 的初始值没有用处时,您就不需要拥有了。


