【问题标题】:Difference between passing a reference type and a value type as argument to a method将引用类型和值类型作为参数传递给方法之间的区别
【发布时间】:2016-07-13 06:41:50
【问题描述】:

我一整天都在脑海中浮现这种困惑。 我对传递给方法的引用和值类型感到非常困惑。

假设我有 2 个类 Class AClass B 分别对应于引用和值类型。

public Class A
{
    public static void main(string[] args)
    {
        String s = “hello”;
        String w = Changestring(s);
        String x = s;
    }

    private string Changestring(string s)
    {
        s += “Hi”;
        return s;
    }
}   

.

public Class B
{
    public static void main(string[] args)
    {
        int s = 99;
        int w = Changeint(s);
        int x = s;
    }       

    private int Changeint(int s)
    {
            s += 30;
            return s;
    }
}

我对将引用类型(字符串)和值类型(int)作为参数传递给函数感到困惑。 当我运行这样一个程序时,字符串(引用类型)的 x 本身是“hello”,值类型的 x 是 99。x 应该是“HelloHi”而不是“hello”,因为它是引用类型?

请帮我解决这个简单的疑问。

【问题讨论】:

  • s += "Hi";s = s + "Hi"; 的简写。您正在创建一个新字符串,而不是改变现有字符串。因此,您看到的结果
  • 您在ChangeString() 方法中引入了一个新的引用字符串s,因此它与main 方法中的字符串s 不同。如果你想改变现有的,你应该使用ref

标签: c#


【解决方案1】:

除了Reference TypeValue Type之外,还有Mutable TypeImmutable Type

Immutable 表示初始化后对象不能也不会改变。因此,您的语句只生成新字符串,但不会修改原始字符串。

s += "嗨";

hello 字符串对象仍为 hello。变化是s 被分配了一个新对象helloHi


string为例,你很不幸。

尝试在您的示例中使用可变类型,例如 StringBuilder

public class C
{
    public static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder("hello");
        StringBuilder w = Changestring(s);
        StringBuilder x = s;
    }

    private static StringBuilder Changestring(StringBuilder s)
    {
        s.Append("Hi");
        return s;
    }
}

【讨论】:

  • 其实字符串的不变性与结果无关。真正的原因是 Koozik 在他的评论中写道:s += "Hi";s = s + "Hi"; 的简写 - 所以问题是为传递给方法的变量分配新值。 My answer to a similar post will prove this.
  • 所以这意味着 Reference 类型和 Value 类型在作为参数传递给函数时具有相同的行为,并且行为仅取决于可变和不可变?
  • @ViVi Reference-Immutable 类型的行为与 Value 类型相同。
  • @ZoharPeled :所以如果我将一个字符串作为参数传递给一个函数,并对字符串进行一些操作,比如 trim 之类的,s 将存储新值还是默认值本身?
  • @ViVi 这与不变性有关 - 您无法更改现有字符串,因此您可以对字符串应用的每个方法实际上都会创建一个新字符串。所以,s.Trim() 不会改变s 的值,而是返回一个作为修剪操作结果的新字符串。
【解决方案2】:

首先要知道,值传递不会改变初始变量值,而引用传递也会改变方法引用的初始变量值。

默认int被视为值类型,因此它是按值传递的。然而,String 甚至被声明为一个类(引用类型),默认情况下它是不可变的 sealed 修饰符,因此被视为按值传递并添加新字符只是创建 String 的新实例,而不是更改当前实例.

通过添加 ref 关键字,您可以将 int 行为更改为按引用传递:

public Class B
{
    public static void main(string[] args)
    {
        int s = 99;
        int w = Changeint(s); // w has changed
        int x = s; // s has also changed
    }       

    private int Changeint(ref int s)
    {
            s += 30; // result = 129
            return s;
    }
}

对于A类,你需要一个StringBuilder实例来完成“helloHi”:

public Class A
{
    public static void main(string[] args)
    {
        String s = “hello”;
        String w = Changestring(s);
        String x = w; // if you assign s to x, you will just get "hello"
    }

    private string Changestring(string s)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(s);
        sb.Append(“Hi”);
        return sb.ToString(); // "helloHi"
    }
}   

CMIIW。

【讨论】:

  • 明白了。感谢你的回答。因此,如果我的 main 方法中有一个字符串生成器,并且我将相同的 sb 传递给函数 ChangeString 并对 sb 本身进行操作(附加文本)而不创建 stringbuilder 的新实例,并且不返回任何值, main 方法中创建的 sb 是否会包含方法中的附加文本?
  • sealed 防止类被子类化,它不会使其不可变。不变性取决于类的实现方式。
【解决方案3】:

在 c# 中,发送到方法的所有参数都按值传递,除非它们使用refout 关键字传递。

这包括值类型、引用类型、可变和不可变类型的参数。

当将引用类型参数传递给方法时,实际发生的是参数的引用按值传递.

这意味着该方法实际上持有一个对传递给它的参数的新引用
因此,当使用方法外部的引用时,对该参数状态的任何更改也会反映出来。
但是,当为方法内部的引用分配新值时,它不会反映在方法外部的引用上。

要获得更好、更广泛的解释,请阅读 Jon Skeet 的 Parameter passing in C# 文章。

您无法使用任何 不可变 类型对此进行测试,因为根据定义,不可变类型无法更改。

但是,您可以使用任何可变类型对此进行测试:

public static void Main()
{
    var x = new List<int>();
    x.Add(1);
    Add(x, 2);
    Console.WriteLine(x.Count.ToString()); // will print "2";
    AddRef(ref x, 3);
    Console.WriteLine(x.Count.ToString()); // will print "1";
    Add3(x, 1, 2, 3 );
    Console.WriteLine(x.Count.ToString()); // will also print "1";
    Add3Ref(ref x, 1, 2, 3 );
    Console.WriteLine(x.Count.ToString()); // will print "3";
}

static void Add(List<int> list, int value)
{
    // adding an item to the list, thus chaning the state of the list.
    // this will reflect on the list sent to the method, 
    // since a List is a reference type.
    list.Add(value);
}

static void AddRef(ref List<int> list, int value)
{
    list = new List<int>(); // same as the s += “Hi”; in the question
    // Adding the value to the list.
    // Note that this change will reflect on the list passed to the method,
    // since it is passed using the ref keyword.
    list.Add(value);
}


static void Add3(List<int> list, int value1, int value2, int value3)
{
    list = new List<int>(); // same as the s += “Hi”; in the question

    // these values are added to the new list.
    // since the reference to the list was passed by value,
    // it WILL NOT effect the list sent to the method.
    list.Add(value1); 
    list.Add(value2);
    list.Add(value3);
}

static void Add3Ref(ref List<int> list, int value1, int value2, int value3)
{
    list = new List<int>(); // same as the s += “Hi”; in the question
    // these values are added to the new list.
    // since the list was passed by reference,
    // it WILL effect the list sent to the method.
    list.Add(value1);
    list.Add(value2);
    list.Add(value3);
}

You can see for yourself on this fiddle.

【讨论】:

    猜你喜欢
    • 2015-12-26
    • 1970-01-01
    • 2014-08-27
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 2013-05-19
    相关资源
    最近更新 更多