Как заставить параметры функции C # действовать как значения? - PullRequest
1 голос
/ 18 июля 2010

Мой вопрос связан с параметрами функции C #.Я привык к C ++, где параметры являются значениями по умолчанию, если вы явно не указали их как ссылки.Итак, как в C # сделать параметры функции передаваемыми по значению, а не по ссылке?На самом деле, я просто хочу знать, как передать переменную, и не изменять ее после вызова функции.

Например:

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

Ответы [ 8 ]

10 голосов
/ 18 июля 2010

Никто не упомянул, что struct s передаются по значению:

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 s для своих типов, то вы можете изменять экземпляр только тогда, когда вы явно используете ref или out в своих методах ... но, очевидно, это работает только тогда, когда у вас есть контроль над типами.

3 голосов
/ 18 июля 2010

Вы можете сделать объект неизменным, чтобы мутации в функциях были невидимы , если только вы не вернете объект обратно пользователю.Таким образом, вы получите что-то вроде этого:

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");
}

Все, что вы передадите в функцию, не пострадает, потому что локальные мутации просто создаютновый объект.

3 голосов
/ 18 июля 2010

Чтобы код, подобный этому, работал на 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 ++. Это дорого и подвержено ошибкам, и вы лучше знаете, что делаете. Может быть, это слишком упрощает проблему. К сожалению.

3 голосов
/ 18 июля 2010

Поскольку 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: Ответ Марка Рушакова - отличный, если не лучший , способ пойти ... при условии, что ваш тип Widget определен вами (это выглядит так).

1 голос
/ 18 июля 2010

Все основные типы передаются по значению автоматически.Чтобы передать их по ссылке:

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 или сделать копиюконструктор.

0 голосов
/ 18 июля 2010

Если вы передаете ссылочный тип, вам необходимо клонировать ваш объект.

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

0 голосов
/ 18 июля 2010

Используйте 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 не может быть нулевым).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...