C # нечетное поведение объекта - PullRequest
5 голосов
/ 27 сентября 2011

Я заметил что-то в C # при работе с пользовательскими объектами, что показалось мне немного странным. Я уверен, что это просто недостаток понимания с моей стороны, поэтому, возможно, кто-то сможет просветить меня.

Если я создаю пользовательский объект и затем назначаю этот объект свойству другого объекта, а второй объект изменяет назначенный ему объект, эти изменения отражаются в том же классе, который выполнял назначение, даже если ничего не возвращается.

Вы хотите это на английском? Вот пример:

class MyProgram
{
    static void Main()
    {
        var myList = new List<string>();
        myList.Add("I was added from MyProgram.Main().");
        var myObject = new SomeObject();
        myObject.MyList = myList;
        myObject.DoSomething();

        foreach (string s in myList)
            Console.WriteLine(s); // This displays both strings.
    }
}

public class SomeObject
{
    public List<string> MyList { get; set; }

    public void DoSomething()
    {
        this.MyList.Add("I was added from SomeObject.DoSomething()");
    }
}

В приведенном выше примере я бы подумал, что, поскольку SomeObject.DoSomething() возвращает void, эта программа будет отображать только "I was added from MyProgram.Main().". Однако List<string> фактически содержит и эту строку, и "I was added from SomeObject.DoSomething()".

Вот еще один пример. В этом примере строка остается неизменной. В чем разница и что мне не хватает?

class MyProgram
{
    static void Main()
    {
        var myString = "I was set in MyProgram.Main()";
        var myObject = new SomeObject();
        myObject.MyString = myString;
        myObject.DoSomething();

        Console.WriteLine(myString); // Displays original string.
    }
}

public class SomeObject
{
    public string MyString { get; set; }

    public void DoSomething()
    {
        this.MyString = "I was set in SomeObject.DoSomething().";
    }
}

В этом примере программы отображается "I was set in MyProgram.Main()". После просмотра результатов первого примера я бы предположил, что вторая программа перезаписала бы строку с "I was set in SomeObject.DoSomething().". Я думаю, что я что-то неправильно понимаю.

Ответы [ 8 ]

15 голосов
/ 27 сентября 2011

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

var myList = new List<string>();
myList.Add("I was added from MyProgram.Main().");
var myObject = new SomeObject();
myObject.MyList = myList;
myObject.DoSomething();

Так что в этом блоке кода вы создаете новый экземпляр List<string> и присвойте ссылку на этот экземпляр переменной myList.Затем вы добавляете "I was added from MyProgram.Main()." к списку, указанному myList.Затем вы назначаете ссылку на этот же список на myObject.MyList (чтобы быть явным, оба myList и myObject.MyList ссылаются на один и тот же List<string>! Затем вы вызываете myObject.DoSomething(), который добавляет "I was added from SomeObject.DoSomething()" к myObject.MyListТак как myList и myObject.MyList относятся к одному и тому же List<string>, они оба увидят эту модификацию.

Давайте по аналогии. У меня есть лист бумаги с телефонным номером наЯ копирую этот листок бумаги и отдаю его. У нас обоих есть листок бумаги с одним и тем же номером телефона. Теперь я набираю этот номер и говорю человеку на другом конце линии поставить баннерв их доме с надписью "I was added from MyProgram.Main()." Вы звоните человеку на другом конце линии, чтобы поставить на его дом плакат с надписью "I was added from SomeObject.DoSomething()". Ну, человек, который живет в доме с таким номером телефона,Теперь у их дома будет два баннера: один с надписью

I was added from MyProgram.Main().

и другой с надписью

I was added from SomeObject.DoSomething()

Имеет смысл?

Теперь, во втором примереМаленькая хитростьr.

var myString = "I was set in MyProgram.Main()";
var myObject = new SomeObject();
myObject.MyString = myString;
myObject.DoSomething();

Вы начинаете с создания нового string со значением "I was set in MyProgram.Main()" и присваиваете ссылку на эту строку на myString.Затем вы присваиваете ссылку на эту же строку на myObject.MyString.Опять же, и myString, и myObject.MyString относятся к тому же string, значение которого "I was set in MyProgram.Main()".Но затем вы вызываете myObject.DoSomething с этой интересной строкой

this.MyString = "I was set in SomeObject.DoSomething().";

Итак, теперь вы создали новый string со значением "I was set in SomeObject.DoSomething()." и назначаете ссылку для этого string на myObject.MyString.Обратите внимание, что вы никогда не меняли ссылку на myString.Итак, теперь myString и myObject.MyString относятся к разным строкам!

Давайте снова по аналогии.У меня есть лист бумаги с веб-адресом.Я копирую этот лист бумаги и отдаю его вам.У нас обоих есть лист бумаги с одним и тем же веб-адресом.Вы вычеркиваете этот веб-адрес и записываете другой адрес.Это не влияет на то, что я вижу на своем листе бумаги!

Наконец, многие люди в этой теме кричат ​​о неизменности string.То, что здесь происходит, не имеет ничего общего с неизменностью string.

2 голосов
/ 27 сентября 2011

Это абсолютно правильно:

myObject.MyList = myList; 

В этой строке присваивается ссылка myList свойству myObject.
Чтобы доказать это, вызовите GetHashCode() для myList и myObject.MyList.

мы говорим о разных указателях на одну и ту же ячейку памяти, если хотите.

1 голос
/ 27 сентября 2011

Независимо от того, возвращает ли метод что-либо, не имеет ничего общего с тем, что происходит внутри него.
Вы, похоже, не понимаете, что на самом деле означает присвоение.

Давайте начнем с самого начала.

var myList = new List<string>();

выделяет новый объект List<string> в памяти и помещает ссылку на него в переменную myList.
В настоящее время только один экземпляр List<string> создан вашим кодомно вы можете хранить ссылки на него в разных местах.

var theSameList = myList; 
var sameOldList = myList;
someObject.MyList = myList;

Прямо сейчас myList, theSameList, sameOldList и someObject.MyList (которые в свою очередь хранятся вприватное поле SomeObject, автоматически сгенерированное компилятором) все ссылаются на один и тот же объект .

Посмотрите на них:

var bob = new Person();
var guyIMetInTheBar = bob;
alice.Daddy = bob;
harry.Uncle = bob;
itDepartment.Head = bob;

Есть только одинэкземпляр Person и множество ссылок на него.
Вполне естественно, что если бы наш Боб вырос на год старше, каждый экземпляр Age увеличился бы.
Это тот же объект.

Если город был переименован, можно ожидать, что все карты будут перепечатаны сts new name.

Вам кажется странным, что

эти изменения отражаются в том же классе, который назначил

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

Так что не имеет значения , где вы добавляетеэлемент в списке: пока вы ссылаетесь на тот же список, вы увидите добавляемый элемент.

Что касается вашего второго примера, я вижу, что Джейсон уже предоставилВы с гораздо лучшим объяснением , чем я мог бы дать, поэтому я не буду вдаваться в подробности.

Будет достаточно, если я скажу:

  1. Строкинеизменяемый в .NET, вы не можете изменить экземпляр string по разным причинам.
  2. , даже если они были изменяемыми (например, List<T>, чье внутреннее состояние можно изменить с помощью методов)во втором примере вы не меняете объект, вы меняете ссылку .

    var goodGuy = jack;
    alice.Lover = jack;
    alice.Lover = mike;
    

Может ли alice изменить настроение jack плохим парнем?Конечно, нет.
Аналогично, изменение myObject.MyString не влияет на локальную переменную myString.Вы ничего не делаете с самой строкой (а на самом деле вы не можете).

0 голосов
/ 27 сентября 2011

В первом примере ... вы работаете с изменяемыми объектами, и к нему всегда обращаются по ссылке. Все ссылки на MyList в разных объектах ссылаются на одно и то же .

В другом случае строки ведут себя немного по-разному.Объявление строкового литерала (т. Е. Текста между кавычками) создает новый экземпляр String, полностью отделенный от исходной версии. Вы НЕ МОЖЕТЕ изменить строку, просто создайте новую .

ОБНОВЛЕНИЕ

Джейсон прав , оно имеетне имеет ничего общего с неизменяемостью строк ... но ... я не могу не думать, что неизменность строк имеет здесь свое слово.Не в этом конкретном примере, но если бы код SomeObject.DoSomething был таким: this.MyString += "I was updated in SomeObject.DoSomething().";, то вам пришлось бы объяснить, что новая строка создается «конкатенацией», а первая строка не обновляется

0 голосов
/ 27 сентября 2011

Алекс - ты написал -

В приведенном выше примере я бы подумал, что поскольку SomeObject.DoSomething () возвращает void, эта программа будет отображать только «Я был добавлен из MyProgram.Main ().» Однако в действительности список содержит и эту строку, и «я был добавлен из SomeObject.DoSomething ()».

Это не тот случай. VOID функции просто означает, что функция не возвращает значение. Это не имеет ничего общего с методом this.MyList.Add, который вы вызываете в методе DoSomething (). Вы должны ссылаться на один и тот же объект - myList и MyList в SomeObject.

0 голосов
/ 27 сентября 2011

Так ведут себя ожидаемые типы ссылок.myList и myObject.MyList являются ссылками на один и тот же объект List в динамической памяти.

Во втором примере строки являются неизменяемыми и передаются по значению, поэтому в строке

myObject.MyString = myString;

Содержимое myString копируется в myObject.MyString (то есть передается значением , а не ссылкой )

Строка являетсянемного особенный, потому что это ссылочный тип и тип значения со специальным свойством неизменяемости (после того, как вы создали строку, вы не можете ее изменить, сделайте только новую, но это несколько скрыто от вас реализацией)

0 голосов
/ 27 сентября 2011

Не забывайте, что ваши переменные тоже объекты.В первом примере вы создаете объект List <> и назначаете его новому объекту.У вас есть только ссылка на список, в этом случае вы теперь держите две ссылки на один и тот же список.

Во втором примере вы назначаете конкретный строковый объект вашему экземпляру.

0 голосов
/ 27 сентября 2011

Вы путаете оба типа объектов.Список - это список типа string .. это означает, что он может принимать строки:)

Когда вы вызываете метод Add, он добавляет строковый литерал в свою коллекцию строк.

В то времяВы вызываете свой метод DoSomething (), для него доступна та же ссылка на список, что и в Main.Следовательно, вы могли видеть обе строки при печати в консоли.

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