2

我有以下代码(注意下面的代码不会更新属性)

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 类型分配为方法的实际参数时,它在堆栈上的值的副本(即它在堆中的内存地址)被复制到堆栈上的新位置方法形式参数,因此它们都指向堆上的相同内存地址。因此,当我更改形式参数的值时,它实际上会更改存储在堆上的值,从而更改实际参数值。

显然我错过了一些东西,因为我必须返回一个字符串并将其手动分配给属性。如果有人能指出我误解了什么,我将不胜感激。

谢谢。

4

3 回答 3

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。

这与以下示例的效果相同:

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语义。并且不要在通过引用传递某些东西和赋值之间感到困惑。从技术上讲,东西永远不会通过引用传递,指向堆上对象的指针是通过值传递的——因此ref在引用类型上具有上面指定的行为。同样因此不使用ref将不允许调用者和方法之间“共享”新的分配,该方法已经收到了它自己的指向堆上对象的指针副本,取消引用指针具有通常的效果(查看相同的底层object),但分配给指针不会影响指针的调用者副本。

于 2012-05-22T09:18:20.923 回答
2

我真的很感谢Adam Houldsworth,因为我终于明白了 .NET 框架如何使用引用参数以及字符串会发生什么。

在 .NET 中有两种数据类型:

  • 值类型:原始类型,如 int、float、bool 等
  • 引用类型:所有其他对象,包括字符串

引用类型的情况下,对象存储在堆中,变量只保存指向该对象的引用。您可以通过引用访问对象的属性并对其进行修改。当您将其中一个变量作为参数传递时,指向同一对象的引用副本将传递给方法体。因此,当您访问和修改属性时,您正在修改存储在堆上的同一个对象。即,这个类是一个引用对象:

    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原样。函数内部的代码创建一个新对象,并将其分配给一个对象,该对象是原始引用的副本。但原来一直指向“地狱”。

值类型的情况下,当它们作为参数传递给函数时,它们是按值传递的,即函数接收原始值的副本。因此,对函数体内部的对象所做的任何修改都不会影响函数外部的原始值。

问题是,虽然 string 是一个引用类型,但它看起来就像一个值类型(这适用于比较、传递参数等)。

但是,如上所述,可以使编译器使用ref关键字通过引用传递引用类型。这也适用于字符串。

您可以检查此代码,您会看到字符串已被修改(这也适用于intfloat任何其他值类型):

    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-22T10:00:01.530 回答
1

字符串是不可变的,因此您将它们的副本传递给方法。这意味着副本更改但原始参数保持不变。

于 2012-05-22T09:18:24.220 回答