我的问题与C#函数参数有关。我习惯于C ++,默认情况下参数是值,除非你明确地将它们指定为引用。那么,在C#中,如何使函数参数按值而不是按引用传递?实际上,我只想知道如何传递变量,并且在调用函数后没有修改该变量。
E.g:
void foo(Widget w)
{
w.X = 3;//where w wouldn't really be modified here
}
答案 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
,则只能在明确在方法中使用ref
或out
时修改实例。但显然这只有在你控制了所涉及的类型时才有效。
答案 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)
Follow the link to find out the comprehensive explanation of Methods Parameters
<强> UPD 强> Link to download C# 4.0 specs.
答案 6 :(得分:0)
如果传递引用类型,则需要克隆对象。
答案 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)。