我最近尝试为Vector2
字段创建一个属性,只是为了意识到它无法按预期工作。
public Vector2 Position { get; set; }
这可以防止我更改其成员的值(X
& Y
)
查看有关此内容的信息,我读到为Vector2
结构创建属性只返回原始对象的副本而不是引用。
作为一名Java开发人员,这让我很困惑。
C#中的对象何时按值传递?何时通过引用传递?
是否所有struct对象都按值传递?
答案 0 :(得分:70)
除非您在签名中指定everything in C# is passed by value或ref
,否则必须认识到out
。
使值类型(以及struct
s)与引用类型不同的原因是直接访问值类型,而通过引用访问引用类型。如果将引用类型传递给方法,其引用,而不是值本身,则按值传递。
为了说明,假设我们有一个类 PointClass
和一个 struct PointStruct
,类似地定义(省略不相关的细节):
struct PointStruct { public int x, y; }
class PointClass { public int x, y; }
我们有一个方法SomeMethod
,它按照这两种类型:
static void ExampleMethod(PointClass apc, PointStruct aps) { … }
如果我们现在创建两个对象并调用方法:
var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);
ExampleMethod(pc, ps);
......我们可以通过下图来形象化:
由于pc
是引用,因此它本身不包含值;相反,它在内存中的其他地方引用(未命名)值。这可以通过虚线边框和箭头显示。
但是:对于pc
和ps
,在调用方法时会复制实际变量。
如果ExampleMethod
在内部重新分配参数变量,会发生什么?我们来看看:
static void ExampleMethod(PointClass apc, PointStruct aps); {
apc = new PointClass(2, 2);
aps = new PointStruct(2, 2);
}
调用方法后输出pc
和ps
:
pc: {x: 1, y: 1}
ps: {x: 1, y: 1}
→ ExampleMethod
更改了值的副本,原始值不受影响。
从根本上说,这就是“按价值传递”的含义。
引用值和值类型之间仍然存在差异,并且在修改值的成员时会发挥作用,而不是变量本身。当面对参考类型按值传递这一事实时,这是让人们吵架的部分。考虑一个不同的ExampleMethod
。
static void ExampleMethod(PointClass apc, PointStruct aps) {
apc.x = 2;
aps.x = 2;
}
现在我们在调用方法后会看到以下结果:
pc: {x: 2, y: 1}
ps: {x: 1, y: 1}
→参考对象已更改,而值对象未更改。上图显示了原因:对于参考对象,即使复制了pc
,pc
和apc
引用的实际值仍然相同,我们可以通过{修改{ {1}}。至于apc
,我们将实际值本身复制到ps
; aps
无法触及原始值。
答案 1 :(得分:22)
struct
是值类型,因此它始终作为值传递。
值可以是引用类型(对象)或值类型(结构)。什么传递总是一个价值;对于引用类型,您将引用的值传递给它,对于您传递值本身的值类型。
当您使用ref
或out
关键字传递参数时,会使用引用这一术语。然后,您将传递对包含该值的变量的引用,而不是传递该值。通常,参数始终按值传递。
答案 2 :(得分:5)
.NET数据类型分为值和引用类型。值类型包括int
,byte
和struct
。参考类型包括string
和类。
结构只包含一个或两个值类型时,它们是合适的而不是类(尽管在那里你可能会产生意想不到的副作用)。
所以结构确实是通过价值传递的,你所看到的是预期的。
答案 3 :(得分:5)
前瞻:C#管理时仍然具有由C创建的核心内存习语。可以合理地将内存视为一个巨大的数组,其中数组中的索引标记为"内存地址"。 指针是此数组的数字索引,即内存地址。此数组中的值可以是数据,也可以是指向另一个内存地址的指针。 const指针是存储在此数组中某个无法更改的索引处的值。内存地址本身存在并且永远不会改变,但是如果不是 const,那么位于该地址的值总是会改变。
类/引用类型(包括字符串)由const指针引用传递。变异将影响此实例的所有用法。您无法更改对象的地址。如果您尝试使用赋值或new
更改地址,您实际上将创建一个与当前范围中的参数具有相同名称的本地变量。
Primitives / ValueTypes / structs(字符串既不是它们不正当地伪装成它们)也被完全复制。结构的变异永远不会被共享。如果struct包含类成员,则复制的是指针引用。这个成员对象是可变的。
基元永远不可变。你不能改变1到2,你可以将当前引用1的内存地址改为当前范围内的2的内存地址。
需要使用out
或ref
个关键字。
ref
将允许您更改指针new
对象或分配现有对象。 ref
还允许您通过它的内存指针传递一个原语/ ValueType / struct,以避免复制该对象。如果你指定它,它还允许你将指针替换为另一个原语。
out
在语义上与ref
完全相同,只有一个小的区别。 ref
参数需要初始化,其中out
参数被允许未初始化,因为它们需要在接受参数的方法中初始化。这通常在TryParse
方法中显示,并且当x的初始值没有用处时,您无需拥有int x = 0; int.TryParse("5", out x)
。
答案 4 :(得分:3)
只是为了说明通过方法传递struct vs class的不同效果:
(注意:在LINQPad 4中测试)
/// 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";
}
(来自LINQPad Dump)
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
答案 5 :(得分:2)
问题是,getter返回Vector2
的副本。如果你改变这样的坐标
obj.Position.X = x;
obj.Position.Y = y;
您只能更改此临时副本的坐标。
改为
obj.Position = new Vector2(x, y);
这与价值或参考无关。 Value2
是值类型,get
返回此值。如果向量具有引用类型(类),get
将返回此引用。 return
按值返回值。如果我们有一个引用类型,那么这些引用就是值并返回。
答案 6 :(得分:1)
对象通过引用传递,并按值进行结构化。但请注意,你有&#34; out&#34;和&#34; ref&#34;论证的修饰语。
所以你可以通过引用传递一个struct,如下所示:
public void fooBar( ref Vector2 position )
{
}
答案 7 :(得分:1)
是的,结构继承自ValueType,并按值传递。这对于原始类型也是如此 - int,double,bool等(但不是字符串)。 字符串,数组和所有类都是引用类型,并通过引用传递。
如果您想通过ref传递结构,请使用ref
关键字:
public void MyMethod (ref Vector2 position)
将传递结构by-ref,并允许您修改其成员。
答案 8 :(得分:0)
结构类型的每个存储位置都包含该结构的所有字段(私有和公共)。传递结构类型的参数需要在堆栈上为该结构分配空间,并将所有字段从结构复制到堆栈。
关于使用存储在集合中的结构,使用现有集合的可变结构通常需要将结构读出到本地副本,改变该副本并将其存储回来。假设MyList是List<Point>
,并且想要将一些局部变量z
添加到MyList[3].X
:
Point temp = MyList[3]; temp.X += Z; MyList[3] = temp;
这有点令人讨厌,但通常比使用不可变结构更清晰,更安全,更高效,比不可变类对象更有效,并且比使用可变类对象更安全。我真的很想看到编译器支持更好的集合方式来公开值类型元素。有一些方法可以编写有效的代码来处理这种具有良好语义的曝光(例如,集合对象可以在元素更新时作出反应,而不需要这些元素与集合有任何链接)但代码读取可怕。以类似于闭包的方式添加编译器支持将允许高效代码也可读。
请注意,与某些人声称的相反,结构与类型对象根本不同,但对于每种结构类型,都有相应的类型,有时也称为“盒装结构” “,派生自Object(参见the CLI (Common Language Infrastructure) specification,8.2.4和8.9.7节)。虽然编译器会在必要时将任何结构隐式转换为相应的盒装类型,然后将其传递给需要引用类类型对象的代码,但是允许引用盒装结构将其内容复制到实际结构中,并且有时会允许用于直接处理盒装结构的代码,盒装结构的行为类似于类对象,因为它就是它们。