你如何使C#函数参数作为值?

时间:2010-07-18 12:32:59

标签: c# function parameters

我的问题与C#函数参数有关。我习惯于C ++,默认情况下参数是值,除非你明确地将它们指定为引用。那么,在C#中,如何使函数参数按值而不是按引用传递?实际上,我只想知道如何传递变量,并且在调用函数后没有修改该变量。

E.g:

void foo(Widget w)
{
     w.X = 3;//where w wouldn't really be modified here
}

8 个答案:

答案 0 :(得分:10)

没人提到struct是按值传递的:

struct Foo
{
    public int X;
    public override string ToString()
    {
        return "Foo.X == " + X.ToString();
    }
}

class Program
{
    static void ModifyFoo(Foo foo)
    {
        foo.X = 5;
        System.Console.WriteLine(foo);
    }

    static void Main()
    {
        Foo foo = new Foo();
        foo.X = 123;
        ModifyFoo(foo);
        System.Console.WriteLine(foo);
    }
}

输出:

$ mono ./a.exe
Foo.X == 5
Foo.X == 123

如果您对类型使用struct,则只能在明确在方法中使用refout时修改实例。但显然这只有在你控制了所涉及的类型时才有效。

答案 1 :(得分:3)

由于int是原始数据类型,x已在旧示例中​​按值传递。

概念证明:

class Program
{
    static void Main(string[] args)
    {
        int x = 1;
        System.Console.WriteLine(x); // 1

        foo(x);
        System.Console.WriteLine(x); // 1
    }

    static void foo(int x)
    {
        x++;
    }
}

编辑:对于非原始数据类型,this question的答案表明C#没有像C ++那样实现拷贝构造函数,所以我不知道如何通过按值传递实际对象,而不是让您的类实现ICloneable接口或其他东西。 (对于记录,C#按值传递对象的引用。)

编辑2: Mark Rushakoff's answer是一个优秀的,如果不是最好的,那么...假设您的Widget类型已定义由你(看起来如此)。

答案 2 :(得分:3)

要使这样的代码在C ++中工作,通常必须为Widget类提供复制构造函数。在C ++中复制对象非常常见,如果将对象存储在集合中,通常需要使用它。然而,它是可怕的堆损坏错误的来源,编译器实现的默认复制构造函数并不总是合适的。它对指针进行别名,复制指针值而不是指向的值。浅拷贝而不是深拷贝。当类的析构函数删除指向的对象时,这会非常糟糕,就像它应该一样,将指针留在副本中指向垃圾。

C#语言没有这种行为,它没有实现复制构造函数。无论如何,它很少被要求,垃圾收集器避免了复制的需要。在any benchmark集合类中非常值得注意。

您当然可以创建自己的复制构造函数,只需要明确地实现它:

void Widget(Widget other) {
   this.X = other.X;
   // etc...
}

并明确创建副本:

Widget w = new Widget();
w.X = 42;
// etc...
foo(new Widget(w));

这也许更清楚地表明制作这些副本的成本非零。如果您希望窗口小部件自动具有值行为,则将其设为结构,而不是类。当没有“ref”键盘传递时,运行时现在自动进行复制。它做成员副本。它很浅,就像默认的C ++拷贝构造函数一样。但是没有指针别名问题,垃圾收集器就解决了这个问题。

但是有浅拷贝问题。其中大多数C#程序员都在解决“我之前尝试过的方法,但是效果不好。让我们不再那个”。好的建议,你也不应该在C ++代码中这样做。它价格昂贵且容易出错,您最好知道自己在做什么。也许这有点过分夸大问题。遗憾。

答案 3 :(得分:3)

您可以使对象不可变,以便函数中的突变不可见,除非将对象返回给用户。所以你最终得到这样的东西:

public class Widget
{
    public readonly string X;
    public readonly string Y;
    public readonly string Z;

    public Widget() { }
    public Widget(string x, string y, string z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public Widget SetX(string value) { return new Widget(value, y, z); }
    public Widget SetY(string value) { return new Widget(x, value, z); }
    public Widget SetZ(string value) { return new Widget(x, y, value); }
}

所以现在你可以这样使用它:

public void DoStuff(Widget w)
{
    w = w.SetX("hello");
    w = w.SetY("world");
}

无论你传入函数的是什么都不受影响,因为局部突变只会创建一个新对象。

答案 4 :(得分:1)

所有基本类型都会自动按值传递。通过引用传递它们:

void foo(ref int x) {
  x = 3; // x does get modified here
}

void bar(int x) {
  x = 3; // does nothing
}

字符串也一样。

但是,如果您具有非原始数据类型,则会自动传递对它的引用。如果在这种情况下使用ref关键字,则可以使引用引用另一个对象。

如果要修改对象而不更改它,则必须克隆它。有些对象让你这样做:

Foo f = new Foo(oldF);

如果它支持IClonable,你可以这样做:

Foo f = oldF.Clone();

否则你必须手动完成,例如使用矩形:

Rectangle r = new Rectangle(oldR.Location, oldR.Size);

结构按值自动传递。

如果你创建自己的类,并希望传递它的副本,你应该实现IClonable,或者制作一个复制构造函数。

答案 5 :(得分:0)

答案 6 :(得分:0)

如果传递引用类型,则需要克隆对象。

http://www.csharp411.com/c-object-clone-wars/

答案 7 :(得分:-2)

在方法参数中使用out或ref。

void foo(out Widget w)
{
     w.X = 3;//where w wouldn't really be modified here
}

void foo(ref Widget w)
{
     w.X = 3;//where w wouldn't really be modified here
}

使用ref要求在调用方法之前将对象初始化(即,Widget不能为null)。