我有下面的代码(注意下面的代码不会更新属性)
private void queryResultsFilePath_Click(object sender, EventArgs e)
{
Library.SProc.Browse browser = new Browse();
browser.selectFile(QueryResultFilePath);
}
和
public class Browse
{
public void selectFile(string propertyName)
{
...
propertyName = browserWindow.FileName;
}
}
现在我意识到我需要更改第二个方法,以便它返回一个字符串,并将其手动分配给第一个示例中的属性。
我不确定的是,当我分配一个ref类型作为方法的实际参数时,它在堆栈上的值的副本(即它在堆中的内存地址)被复制到方法形参在堆栈上的新位置,所以它们都指向堆上的相同内存地址。因此,当我更改形参的值时,它实际上会更改存储在堆中的值,从而更改实际参数值。
显然,我遗漏了一些东西,因为我必须返回一个字符串并手动将其分配给属性。如果有人能指出我误解了什么,我会很感激的。
谢谢。
发布于 2012-05-22 17:18:21
我认为这里缺少的部分是:字符串是不可变的。
尽管您通过引用传递它,但只要有任何东西试图改变字符串,就会创建一个新的字符串,而保留旧的字符串不变。
我相信它是唯一一个强制不可变的引用类型。
来自MSDN
字符串是不可变的-- string对象的内容在对象创建后不能更改,尽管语法使其看起来好像可以这样做。例如,当您编写此代码时,编译器实际上创建了一个新的string对象来保存新的字符序列,并将该新对象分配给b。然后,字符串"h“有资格进行垃圾回收。
进一步阅读:
http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/e755cbcd-4b09-4a61-b31f-e46e48d1b2eb
如果您希望该方法“更改”调用者的字符串,则可以使用ref
关键字进行模拟:
public void SelectFile(ref string propertyName)
{
propertyName = browserWindow.FileName;
}
在此示例中,由于使用了ref
,因此将在方法中为参数propertyName
赋值,这也会更改调用者所指向的字符串。请注意,这里的不变性仍然是强制的。propertyName
过去指向字符串A,但赋值之后现在指向字符串B-旧的字符串A现在是未引用的,并且将被垃圾回收(但重要的是,它仍然存在并且没有改变-不可变)。如果未使用ref
关键字,则调用方仍将指向A,而方法将指向B。但是,由于使用了ref
关键字,因此callers变量现在指向字符串B。
这与以下示例具有相同的效果:
static void Main(string[] args)
{
MyClass classRef = new MyClass("A");
PointToANewClass(ref classRef);
// classRef now points to a brand new instance containing "B".
}
public static void PointToANewClass(ref MyClass classRef)
{
classRef = new MyClass("B");
}
如果在不使用关键字的情况下尝试上面的,即使类是通过引用传递的,classRef
仍然会指向一个包含"A“的对象。
不要混淆字符串语义和ref
语义。另外,不要混淆通过引用传递和赋值传递。Stuff在技术上是的,从不通过引用传递,堆上对象的指针是通过值传递的-因此引用类型上的ref
具有上面指定的行为。同样,不使用ref
将不允许在调用者和方法之间“共享”新的赋值,方法已经接收到它自己的指向堆上的对象的指针副本,取消对指针的引用具有通常的效果(查看相同的底层对象),但是为指针赋值不会影响调用者的指针副本。
发布于 2012-05-22 18:00:02
我真的很感谢Adam Houldsworth,因为我终于理解了.NET框架如何使用引用参数以及字符串发生了什么。
在.NET中有两种数据类型:
在引用类型为的情况下,对象存储在堆中,并且变量仅保存指向此对象的引用。您可以通过引用访问对象的属性并对其进行修改。当您将此变量之一作为参数传递时,指向同一对象的引用的副本将传递给方法体。因此,当你访问和修改属性时,你是在修改存储在堆中的相同对象。也就是说,这个类是一个引用对象:
public class ClassOne
{
public string Desc { get; set; }
}
当你这样做的时候
ClassOne one = new { Desc = "I'm a class one!" };
堆上有一个引用one
指向的对象。如果您这样做:
one.Desc = "Changed value!";
堆上的对象已被修改。如果将此引用作为参数传递:
public void ChangeOne(ClassOne one)
{
one.Desc = "Changed value!"
}
堆上的原始对象也被更改,因为one
持有指向堆上同一对象的原始引用的副本。
但是如果你这样做:
public void ChangeOne(ClassOne one)
{
one = new ClassOne { Desc ="Changed value!" };
}
原始对象保持不变。这是因为one
是引用的副本,现在它指向不同的对象。
如果通过引用显式传递它:
public void ChangeOne(ref ClassOne one)
{
one = new ClassOne { Desc ="Changed value!" };
}
此方法中的one
不是外部引用的副本,而是引用本身,因此,原始引用现在指向这个新对象。
字符串是不可变的。这意味着您不能更改字符串。如果您尝试这样做,则会创建一个新的字符串。所以,如果你这样做:
string s = "HELL";
s = s + "O";
第二行创建了一个新的string实例,它的值为"HELLO“,而”地狱“被丢弃在堆上(留下等待垃圾收集)。
因此,如果您像这样将其作为参数传递,则不可能对其进行更改:
public void AppendO(string one)
{
one = one + "O";
}
string original = "HELL";
AppendO(original);
original
字符串保持原样。函数内部的代码创建一个新对象,并将其分配给一个对象,该对象是原始引用的副本。但original一直指向“地狱”。
在值类型为的情况下,当它们作为参数传递给函数时,它们是通过值传递的,即函数接收原始值的副本。因此,对函数体内部的对象所做的任何修改都不会影响函数外部的原始值。
问题是,虽然string是一个引用类型,但它看起来就像是一个值类型(这适用于比较、传递参数等)。
但是,如上所述,编译器可以使用ref
关键字通过引用传递引用类型。这也适用于字符串。
您可以检查此代码,您将看到字符串已被修改(这也适用于int
、float
或任何其他值类型):
public static class StringTest
{
public static void AppednO(ref string toModify)
{
toModify = toModify + "O";
}
}
// test:
string hell = "HELL";
StringTest.AppendO(ref hell);
if (hell == "HELLO")
{
// here, hell is "HELLO"
}
请注意,为了避免错误,当您将参数定义为ref时,还必须传递带有此修饰符的参数。
无论如何,对于这种情况(以及类似的情况),我建议您使用更自然的函数语法:
var hell = StringTest.AppendO(hell);
(当然,在这种情况下,函数将具有此签名和相应的实现:
public static string AppendO(string value)
{
return value + "O";
}
如果你要对一个字符串做很多修改,你应该使用StringBuilder类,它可以处理“可变字符串”。
发布于 2012-05-22 17:18:24
字符串是不可变的,因此您要将它们的副本传递给方法。这意味着副本会发生变化,但原始参数保持不变。
https://stackoverflow.com/questions/10699103
复制相似问题