У меня есть эта структура:
struct Map
{
public int Size;
public Map ( int size )
{
this.Size = size;
}
public override string ToString ( )
{
return String.Format ( "Size: {0}", this.Size );
}
}
При использовании массива он работает:
Map [ ] arr = new Map [ 4 ] {
new Map(10),
new Map(20),
new Map(30),
new Map(40)};
arr [ 2 ].Size = 0;
Но при использовании List он не компилируется:
List<Map> list = new List<Map> ( ) {
new Map(10),
new Map(20),
new Map(30),
new Map(40)};
list [ 2 ].Size = 0;
Почему?
Извините, я никогда раньше не сталкивался с этой проблемой ..
@Peter Mortensen: нет необходимости добавлять «C#» к заголовку. SO справляется с этим просто отлично, так как это тег





Компилятор C# выдаст следующую ошибку:
Cannot modify the return value of 'System.Collections.Generic.List.this[int]' because it is not a variable
Причина в том, что структуры являются типами значений, поэтому при доступе к элементу списка вы фактически получаете доступ к промежуточной копии элемента, возвращенной индексатором списка.
От MSDN:
Error Message
Cannot modify the return value of 'expression' because it is not a variable
An attempt was made to modify a value type that was the result of an intermediate expression. Because the value is not persisted, the value will be unchanged.
To resolve this error, store the result of the expression in an intermediate value, or use a reference type for the intermediate expression.
Решения:
List<Map> list = new List<Map>() {
new Map(10),
new Map(20),
new Map(30),
new Map(40)
};
Map map = list[2];
map.Size = 42;
list[2] = map;
Спасибо, но я использую платформу XNA, и большинство вещей реализовано в виде структуры, например Vector3 и т. д.
Это просто ограничение C#, CLR имеет возможность возвращать ссылки на структуры (управляемые указатели), но язык C# этого не реализует.
Спасибо, а для массивов он возвращает ссылки?
Да, msdn.microsoft.com/en-us/library/…, но управляемые указатели также могут быть возвращены из методов, C# этого не делает.
Поскольку это struct, при использовании List <T> вы создаете копии.
При использовании структуры лучше сделать их неизменяемыми. Это позволит избежать подобных эффектов.
При использовании массива у вас есть прямой доступ к структурам памяти. Используя List <T> .get_Item, вы работаете с возвращаемым значением, то есть копией структуры.
Если бы это был класс, вы бы получили копию указателя на класс, но вы бы этого не заметили, поскольку в C# указатели скрыты.
Также использование List <T> .ToArray не решает эту проблему, потому что он создаст копию внутреннего массива и вернет ее.
И это не единственное место, где вы можете получить подобные эффекты при использовании структуры.
Решение, предоставленное Divo, является очень хорошим обходным путем. Но вы должны помнить, что нужно работать таким образом не только при использовании List <T>, но и везде, где вы хотите изменить поле внутри структуры.
Как сделать его неизменным?
Не предоставляйте никаких способов изменения значений внутри структуры. Как .NET делает со строками и объектами DateTime, все операции должны возвращать новый экземпляр, а не изменять текущий экземпляр.
Но как насчет методов экземпляра?
Измените код для поля Размер на (что-то вроде): public int Size {get; частный набор; }
Если вам нужно сильно изменить размер (или другие поля), лучше выбрать класс, а не использовать sctruct.
Поскольку OP требует, чтобы это была структура, нельзя ли использовать «небезопасный» код для прямого доступа к структуре?
Это интересно. Как вы думаете, это будет единственный способ получить дескриптор структуры? Но даже в этом случае почему те, что в массиве, работают так, как ожидалось, а не в List <T>? Похоже, команда .NET упустила из виду?
Что касается вашего последнего абзаца: на самом деле вам не нужно помнить, что нужно работать таким образом. Компилятор выдаст вам ошибку, когда вы попытаетесь присвоить значение промежуточного результата.
list [ 2 ].Size = 0;
на самом деле:
//Copy of object
Map map = list[2];
map.Size = 2;
Используйте class вместо struct.
Спасибо, но я использую платформу XNA, и большинство вещей реализовано в виде структуры, например Vector3 и т. д.
Я не разработчик XNA, поэтому не могу комментировать, насколько строгое требование использовать структуры и классы для XNA. Но это действительно кажется более естественным местом для использования класса, чтобы получить семантику прохода по ссылке, которую вы ищете.
У меня была одна мысль, что ты можешь использовать бокс. Создав интерфейс, скажем, IMap в вашем случае, который будет имплантирован вашей структурой Map, а затем используя List<IMap>, список будет содержать объекты System.Object, которые передаются по ссылке. Например:
interface IMap
{
int Size { get; set; }
}
struct Map: IMap
{
public Map(int size)
{
_size = size;
}
private int _size;
public int Size
{
get { return _size; }
set { _size = value; }
}
public override string ToString()
{
return String.Format("Size: {0}", this.Size);
}
}
Что затем можно было бы вызвать следующим образом:
List<IMap> list = new List<IMap>() {
new Map(10),
new Map(20),
new Map(30),
new Map(40)};
list[2].Size = 4;
Console.WriteLine("list[2].Size = " + list[2].Size.ToString());
Обратите внимание, что эти структуры будут упакованы только один раз при передаче в список в первую очередь, а НЕ при вызове с использованием кода, такого как 'list [2] .Size = 4', поэтому он должен быть достаточно эффективным, если вы не принимаете эти объекты IMap и приведение обратно к Map (копирование из List<IMap>) в других частях вашего кода.
Хотя это позволит достичь вашей цели - иметь прямой доступ для чтения и записи к структурам в List <>, упаковка структуры действительно помещает структуру в класс (System.Object), и поэтому я бы подумал, что это может сделать больше смысла в том, чтобы сделать вашу «карту» классом?
Майк
Я решил напрямую заменить результат на копию и переназначить результат следующим образом:
Map map = arr [ 2 ];
map.Size = 0;
arr [ 2 ] = map;
Я постоянно возвращаюсь к этому вопросу, пытаясь вычислить нормали в буфере вершин в XNA.
Лучшее решение XNA, которое я придумал, состояло в том, чтобы скопировать данные (или сохранить их) в массиве.
private void SomeFunction()
{
List<VertexBasicTerrain> vertexList = GenerateVertices();
short[] indexArray = GenerateIndices();
CalculateNormals(vertexList, ref indexArray); // Will not work
var vertexArray = vertexList.ToArray();
CalculateNormals(ref vertexArray, ref indexArray);
}
// This works (algorithm from Reimers.net)
private void CalculateNormals(ref VertexBasicTerrain[] vertices, ref short[] indices)
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
Vector3 firstvec = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
Vector3 secondvec = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
Vector3 normal = Vector3.Cross(firstvec, secondvec);
normal.Normalize();
vertices[indices[i * 3]].Normal += normal;
vertices[indices[i * 3 + 1]].Normal += normal;
vertices[indices[i * 3 + 2]].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
}
// This does NOT work and throws a compiler error because of the List<T>
private void CalculateNormals(List<VertexBasicTerrain> vertices, ref short[] indices)
{
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal = new Vector3(0, 0, 0);
for (int i = 0; i < indices.Length / 3; i++)
{
Vector3 firstvec = vertices[indices[i * 3 + 1]].Position - vertices[indices[i * 3]].Position;
Vector3 secondvec = vertices[indices[i * 3]].Position - vertices[indices[i * 3 + 2]].Position;
Vector3 normal = Vector3.Cross(firstvec, secondvec);
normal.Normalize();
vertices[indices[i * 3]].Normal += normal;
vertices[indices[i * 3 + 1]].Normal += normal;
vertices[indices[i * 3 + 2]].Normal += normal;
}
for (int i = 0; i < vertices.Length; i++)
vertices[i].Normal.Normalize();
}
Что значит не работает? Он компилируется? Выдает ли исключение во время выполнения? Работает, но свойство соответственно не меняется?