[英]How do you make C# function parameters act as values?
我的问题与C#函数参数有关。 我习惯于C ++,默认情况下参数是值,除非您明确将其指定为引用。 因此,在C#中,如何使函数参数通过值而不是通过引用传递? 实际上,我只想知道如何传递变量,而不是在调用函数后修改该变量。
例如:
void foo(Widget w)
{
w.X = 3;//where w wouldn't really be modified here
}
没有人提到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
时,才可以修改实例……但是显然,这仅在您控制了所涉及的类型时才有效。
由于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++;
}
}
编辑:对于非原始数据类型, 此问题的答案表明C#不能像C ++那样实现复制构造函数,因此,我不确定有一种通过值传递实际对象的方法,除了使类实现ICloneable
接口或其他东西。 (为便于记录,C#按值将引用传递给对象。)
编辑2: 马克·拉沙科夫(Mark Rushakoff)的回答是一种很好的方法,即使不是最好的方法...假设您的Widget
类型是由您定义的(看起来是这样)。
为了使这样的代码在C ++中工作,通常必须为Widget类提供一个副本构造函数。 在C ++中复制对象非常普遍,如果将对象存储在集合中通常是必需的。 但是,它是可怕的堆损坏错误的来源,由编译器实现的默认副本构造函数并不总是合适的。 它别名化指针,复制指针值而不是指向的值。 浅表副本而不是深表副本。 当类的析构函数像其应有的那样删除指向对象时,将指针留在副本中指向垃圾的情况下,这会严重炸毁。
C#语言没有这种行为,它没有实现复制构造函数。 无论如何,很少需要它,垃圾收集器避免了制作副本的需要。 在收集类的任何基准测试中都很值得注意。
您当然可以创建自己的副本构造函数,只需明确实现它即可:
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 ++代码中这样做。 这既昂贵又容易出错,您最好知道自己在做什么。 也许这使问题变得过于琐碎了。 抱歉。
您可以使对象不可变,这样, 除非将对象返回给用户, 否则函数中的突变是不可见的。 因此,您最终会得到如下结果:
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");
}
传递给函数的任何内容都不会受到影响,因为局部突变只会创建一个新对象。
所有基本类型都将按值自动传递。 通过参考传递它们:
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:
Rectangle r = new Rectangle(oldR.Location, oldR.Size);
结构是按值自动传递的。
如果您创建自己的类,并希望传递其副本,则应实现IClonable或创建副本构造函数。
如果传递引用类型,则需要克隆对象。
在方法参数中使用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)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.