繁体   English   中英

如何使C#函数参数充当值?

[英]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 ,则仅当在方法中显式使用refout时,才可以修改实例……但是显然,这仅在您控制了所涉及的类型时才有效。

由于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或创建副本构造函数。

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

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

在方法参数中使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM