Почему структура может изменить свои собственные поля? - PullRequest
5 голосов
/ 18 января 2011

Рассмотрим структуру Foo следующим образом:

struct Foo
{
  public float X;
  public float Y;

  public Foo(float x, float y)
  {
    this.X = x;
    this.Y = y;
  }

  public void Change(float x)
  {
    this.X = x;
  }
}

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

Однако, поскольку можно 't сделать:

Foo bar = new Foo(1, 2);
bar.X = 5;

Почему можно использовать:

Foo bar = new Foo(1, 2);
bar.Change(5);

РЕДАКТИРОВАТЬ : Если структуры изменчивы, то почему они не могут быть изменены в списке или возвращены из свойства?

Невозможно изменить выражение, поскольку оно не является переменной

Ответы [ 5 ]

11 голосов
/ 18 января 2011

Так как никто не может сделать

Foo bar = new Foo(1, 2); 
bar.X = 5; 

Почему можно использовать:

Foo bar = new Foo(1, 2); 
bar.Change(5); 

Ваш первоначальный вопрос на самом деле не может быть дан ответ, потому что он основан на полностью ложном предположении . Оба примера кода абсолютно законны, и поэтому вопрос о том, почему один из них является незаконным, не имеет смысла. Давайте перейдем к следующему вопросу:

Если структуры изменчивы, то почему они не могут быть изменены в списке или возвращены из свойства?

Поскольку переменные изменчивы, а значения неизменны.

Вот почему их называют «переменными», в конце концов, , потому что они могут измениться .

Когда вы говорите "bar.X = 5", "bar" - это локальная переменная . Переменные могут изменяться.

Когда вы говорите «bar.Change (5)», «bar» является локальной переменной . Переменные могут изменяться.

Когда вы говорите «myArray [123] .X = 5», «myArray [123]» представляет собой элемент массива , а элемент массива представляет собой переменную . Переменные могут изменяться.

Когда вы говорите «myDictionary [123] .X = 5», «myDictionary [123]» равен , а не является переменной . Значение возвращается из словаря, а не ссылка на место хранения . Поскольку это значение, а не переменная, там нет ничего, что могло бы измениться, поэтому компилятор не позволяет его изменять.

Тонкий момент заключается в том, что при попытке изменить поле получатель должен быть переменной. Если это не переменная, это не имеет смысла; Вы явно пытаетесь изменить переменную, и там нечего менять. Когда вы вызываете метод, получатель должен иметь значение variable , но что если у вас есть значение? Метод может не пытаться что-либо изменить, поэтому его следует разрешить. Что на самом деле делает компилятор, если получатель вызова метода для структуры не является переменной, то он создает новую временную локальную переменную и вызывает метод с этой переменной. Так что если вы говорите: "myDictionary [123] .Change (5);" это то же самое, что сказать

var temp = myDictionary[123];
temp.Change(5);

Здесь «temp» - это переменная, и метод мутации может изменять временную копию.

Теперь понятно? Ключ к выводу здесь: переменные могут меняться .

8 голосов
/ 18 января 2011

Вы сделали ключевое ошибочное предположение.

.NET структуры являются изменяемыми. Вы можете абсолютно выполнить bar.X = 5;.

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

Посмотрите на этот вопрос, чтобы узнать, где изменяющиеся структуры могут привести к неприятностям. Неизменяемость структур

2 голосов
/ 18 января 2011

В общем, все структуры C # не являются неизменяемыми, даже только для чтения. Таким образом, вы не можете создать свои структуры как неизменные вообще.

Все структуры изменяемы, как в C ++ :)

Неизменяемость означает, что структуры данных неизменны на уровне языка, что неверно для C #. Я покажу вам, как нарушить правило неизменяемости с использованием легального синтаксиса C #. Обратите внимание, что NotReallyImmutableFoo.X объявлено как поле readonly .

Приветствия;)

namespace test
{
    public unsafe struct MutableFoo
    {
        public int Id;
        public float X;
        public MutableFoo(int id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (MutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    public struct NotReallyImmutableFoo
    {
        public long Id;
        public readonly float X;
        public NotReallyImmutableFoo(long id, float x) { Id = id; X = x; }
        public void Change(float x)
        {
            unsafe
            {
                fixed (NotReallyImmutableFoo* self = &(this))
                {
                    MutabilityHelper.Rewrite(self, x);
                }
            }
        }
    }

    // this calls breaks up the immutability rule, because we are modifying structures itself
    public static class MutabilityHelper
    {
        struct MutableFooPrototype
        {
            int Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        struct NotReallyImmutableFooPrototype
        {
            long Id;
            float X;
            public void Rewrite(float value)
            {
                X = value;
            }
        }
        public static unsafe void Rewrite(NotReallyImmutableFoo* obj, float value)
        {
            NotReallyImmutableFooPrototype* p_obj = (NotReallyImmutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
        public static unsafe void Rewrite(MutableFoo* obj, float value)
        {
            MutableFooPrototype* p_obj = (MutableFooPrototype*)(*(&obj));
            p_obj->Rewrite(value);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MutableFoo foo = new MutableFoo(0, 2);
            foo.X = 3; // X is writeable
            foo.Change(5); // write X using pointer prototyping

            NotReallyImmutableFoo nrifoo = new NotReallyImmutableFoo(0, 2);
            // error CS0191
            //nrifoo.X = 3; // X is not writeable
            nrifoo.Change(3); // anyway, write X using pointer prototyping
        }
    }
}
0 голосов
/ 01 августа 2012

Структуры в .net сочетают кусочную изменчивость с семантикой мелкого копирования при назначении, а также возможность передачи по назначению значения или по ссылке.Однако в .net не существует соглашения, согласно которому классы должны предоставлять свойства по ссылке, и при этом никакие компиляторы языка .net не предоставляют удобных средств для этого.Для языка было бы возможно обеспечить такую ​​функцию с определенными ограничениями, признав, что что-то вроде:

  somePoint.X = 5;

может быть записано как:

void SetXToFive(ref Point it) {it.X = 5;}
...
  SetXToFive(ref somePoint);

, позволяя код, которыйманипулирует Point (устанавливая его поле X на 5) из кода, который имеет к нему доступ.Если объект, обладающий свойством типа Point, затем предоставляет подпрограмму, которая принимает делегат для метода, подобного описанному выше, код, который хочет установить для поля X этого свойства значение 5, может передать эту подпрограмму делегату в SetXToFive., который подпрограмма могла бы затем вызывать с любым местом хранения, содержащим Point, о котором идет речь.

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

0 голосов
/ 18 января 2011

Нашел мой ответ в самом низу: http://www.albahari.com/valuevsreftypes.aspx

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

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