Почему System.Windows.Point & System.Windows.Vector изменен?

Учитывая, что изменчивые структуры обычно считаются злыми (например, Почему изменчивые структуры "злые" ?), существуют потенциальные выгоды, которые могли бы побудить разработчиков .NET framework, чтобы сделать System.Windows.Point и System.Windows.Vector изменчивым?

Я хотел бы это понять, поэтому я могу решить, имеет ли смысл сделать мои собственные подобные структуры изменчивыми (если вообще когда-либо). Возможно, решение сделать Point и Vector изменчивым было просто ошибкой в ​​оценке, но если есть веская причина (например, преимущество в производительности), я хотел бы понять, что это было.

Я знаю, что несколько раз наткнулся на реализацию метода Vector.Normalize(), потому что он, неожиданно (!), не возвращает свежий Vector. Он просто изменяет текущий вектор.

Я всегда думаю, что он должен работать следующим образом:

var vector = new Vector(7, 11);
var normalizedVector = vector.Normalize(); // Bzzz! Won't compile

Но он действительно работает следующим образом:

var vector = new Vector(7, 11);
vector.Normalize(); // This compiles, but now I've overwritten my original vector

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

Ответы

Ответ 1

Эти типы находятся в пространстве имен System.Windows и обычно используются в приложениях WPF. Разметка XAML приложения является большой частью структуры, поэтому для многих вещей им нужен способ, который можно выразить с помощью XAML. К сожалению, не существует способа вызвать конструкторы без параметров без использования WPF XAML (но это возможно в свободном XAML), поэтому попытка вызвать конструктор с соответствующими аргументами для инициализации будет невозможна. Вы можете только установить значения свойств объекта так естественно, эти свойства должны быть изменены.

Это плохо? Для этих типов, я бы сказал, нет. Они предназначены только для хранения данных, не более того. Если вы хотите получить размер Window, вы должны получить доступ к DesiredSize, чтобы получить объект Size, представляющий желаемый размер. Вы не должны "изменять желаемый размер", изменяя свойства Width или Height объекта Size, который вы получаете, вы изменяете размер, предоставляя новый объект Size. Глядя на это, этот путь намного естественнее, я считаю.

Если эти объекты были более сложными и выполняли более сложные операции или имели состояние, то да, вы не хотели бы, чтобы эти типы не изменялись и не структурировались. Однако, поскольку они примерно такие же простые и простые, как могут (по существу, POD), структуры здесь будут уместны.

Ответ 2

Такие типы изменяемы, потому что, вопреки тому, что могут требовать некоторые люди, изменчивая семантика типа значения полезна. Есть несколько мест, где .net пытается притвориться, что типы значений должны иметь ту же семантику, что и ссылочные типы. Поскольку изменчивая семантика значения-типа принципиально отличается от изменчивой семантики ссылочного типа, притворяясь, что они одинаковы, это вызовет проблемы. Однако это не делает их "злыми" - это просто показывает недостаток объектной модели, предполагающий, что действие на копию чего-то будет семантически эквивалентно действию на оригинал. Истинно, если предмет, о котором идет речь, является ссылкой на объект; как правило, истина, но с исключениями - если это неизменная структура; false, если это изменчивая структура.

Одна из красивейших вещей о структурах с открытыми полями заключается в том, что их семантика легко определяется даже простой проверкой. Если у вас есть Point[100] PointArray, у него есть 100 различных экземпляров Point. Если говорить PointArray[4].X = 9;, это изменит один элемент PointArray и не будет другого.

Предположим, что вместо использования struct Point у одного был измененный класс PointClass:

class PointClass {public int X; public int Y;};

Сколько экземпляров PointClass хранится в PointClass[100] PointClassArray? Есть ли способ рассказать? Будет ли выражение PointClass[4].X = 9 влиять на значение PointClass [2].X? Что насчет someOtherObject.somePoint.X?

В то время как коллекции .net не очень хорошо подходят для хранения изменчивых структур, я тем не менее считаю:

Dictionary<string, Point>;
...
  Point temp = myDict["George"];
  temp.X = 9;
  myDict["George"] = temp;

иметь относительно четкую семантику, по крайней мере, при отсутствии проблем с потоками. Хотя я считаю, что это печально, что коллекции .net не предоставляют средства, с помощью которых можно было бы просто сказать myDict[lookupKey].X = 9;. Я бы по-прежнему рассматривал приведенный выше код как очень понятный и понятный, не зная ничего о Точке, отличной от того факта, что у нее есть общедоступное целочисленное поле, называемое X. Напротив, если бы у одного был Dictionary<PointClass>, было бы непонятно, что следует ожидать, чтобы изменить значение X, связанное с "Джорджем". Возможно, экземпляр PointClass, связанный с Джорджем, нигде не используется, и в этом случае можно просто написать соответствующее поле. С другой стороны, также возможно, что кто-то еще схватил копию MyDict["George"] с целью захвата значений в ней и не ожидает, что объект PointClass, который он захватил, может измениться.

Некоторые люди могут подумать, что "Точка" должна быть неизменяемой структурой, но эффект выражения типа somePoint.X = 5; может быть полностью определен, зная только, что somePoint является переменной типа Point, которая, в свою очередь, является struct с открытым пространством int, называемым X. Если Point была неизменяемой структурой, нужно было бы сказать что-то вроде somePoint = new Point(5, somePoint.Y);, которое, помимо медленности, требует изучения структуры, чтобы определить, что все его поля инициализируются в конструкторе, причем X является первый и Y второй. В каком смысле это будет улучшение по сравнению с somePoint.X = 5;?

BTW, самая большая "gotcha" с изменяемыми структурами связана с тем, что система не может различать методы структуры, которые изменяют 'this' от тех, которые этого не делают. Большой позор. Предпочтительными обходными способами являются либо функции, которые возвращают новые структуры, полученные из старых, либо используют статические функции, которые принимают "ref" структурные параметры.

Ответ 3

Возможности:

  • В то время это казалось хорошей идеей для тех, кто не рассматривал прецеденты, в которых он кусает людей. List<T>.Enumerator является изменчивой структурой, которая использовалась, потому что в то время казалось хорошей идеей воспользоваться преимуществами микрооптов, которые часто бывали. Это почти плакат-ребенок для изменчивых структур, являющихся "злыми", поскольку он укусил больше, чем несколько человек. Тем не менее, в то время это казалось хорошей идеей...
  • Они действительно думали о недостатках, но им был известен случай использования, где различия в производительности были в пользу структуры (они не всегда) и считались важными.
  • Они не рассматривали структуры зла. "Зло" - это мнение о том, что нисходящие стороны бьют вверх, а не демонстративный факт, и не каждый должен согласиться с чем-то, даже если это говорят Эрик Липперт и Джон Скит. Лично я думаю, что они не злые, их просто неправильно поняли; но опять же, зло часто бывает легче иметь дело с чем-то неправильно понятым для программиста, так что на самом деле хуже...;) Может быть, те, кто участвует, не согласны.