我有以下代码(请注意下面的代码不会更新属性)
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类型指定为方法的实际参数时,其在堆栈上的值的副本(即其在堆中的内存地址)被复制到新位置上方法形式参数的堆栈,因此它们都指向堆上的相同内存地址。因此,当我更改形式参数的值时,它实际上会更改存储在堆上的值,从而更改实际参数值。
显然我遗漏了一些东西,因为我必须返回一个字符串并手动将其分配给属性。如果有人能够指出我误解了什么,那就是欣赏它。
感谢。
答案 0 :(得分:3)
我相信这里缺少的部分是:字符串是不可变的。
虽然你通过引用传递它,但是只要有人试图改变字符串,就会创建一个新的字符串,保留旧的字符串。
我相信它是唯一强制不变性的参考类型。
来自MSDN:
字符串是不可变的 - 字符串对象的内容不能 在创建对象后更改,尽管语法可以实现 好像你可以这样做。例如,当您编写此代码时, 编译器实际上创建了一个新的字符串对象来保存新的 字符序列,并将新对象分配给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;
}
在此示例中,参数propertyName
将在方法中分配,因为使用了ref
,这也会更改调用者指向的字符串。请注意,仍然强制执行不变性。 propertyName
用于指向字符串A,但现在赋值后指向字符串B - 旧字符串A现在未被引用并将被垃圾收集(但重要的是仍然存在并且未被更改 - 不可变)。如果未使用ref
关键字,则调用者仍将指向A,方法将指向B.但是,由于使用了ref
关键字,调用者变量现在指向字符串B. / p>
这与以下示例的效果相同:
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");
}
如果你尝试使用上面的而不是 ref
关键字,classRef
仍会指向包含“A”的对象,即使该类是通过引用传递的。
不要混淆字符串语义和ref
语义。并且也不要在通过引用和赋值传递内容之间混淆。从技术上说,Stuff 从不 通过引用传递, 指向堆上对象的指针通过值传递 - 因此引用类型上的ref
具有上面指定的行为。因此,不使用ref
将不允许在调用者和方法之间“共享”新的赋值,该方法已经收到了自己的 copy 指向堆上对象的指针,解除引用指针具有通常的效果(查看相同的底层对象),但是指定指针不会影响指针的调用者副本。
答案 1 :(得分:2)
我非常感谢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";
第二行创建一个新的字符串实例,其值为" HELLO" " HELL"被遗弃在堆上(留下来进行垃圾收集)。
因此,如果您将其作为参数传递给它,则无法更改它:
public void AppendO(string one)
{
one = one + "O";
}
string original = "HELL";
AppendO(original);
保留original
字符串。函数内部的代码创建一个新对象,并将其分配给一个原始引用的副本。但是原创一直指向" HELL"。
对于值类型,当它们作为参数传递给函数时,它们按值传递,即函数接收原始值的副本。因此,对函数体内对象所做的任何修改都不会影响函数外的原始值。
问题是,虽然字符串是引用类型,它看起来好像的行为类似于值类型(这适用于比较,传递参数等等)上)。
但是,如上所述,可以使编译器使用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类,该类使用"可变字符串"。
答案 2 :(得分:1)
字符串是不可变的,因此您将它们的副本传递给方法。这意味着副本会更改但原始参数保持不变。