Programing

구조체 목록에서 요소 값 변경

lottogame 2020. 12. 5. 09:08
반응형

구조체 목록에서 요소 값 변경


구조체 목록이 있고 하나의 요소를 변경하고 싶습니다. 예 :

MyList.Add(new MyStruct("john");
MyList.Add(new MyStruct("peter");

이제 하나의 요소를 변경하고 싶습니다.

MyList[1].Name = "bob"

그러나 이것을 시도하고 할 때마다 다음과 같은 오류가 발생합니다.

System.Collections.Generic.List.this [int] '의 반환 값은 변수가 아니므로 수정할 수 없습니다.

클래스 목록을 사용하면 문제가 발생하지 않습니다.

대답은 구조체가 값 유형과 관련이 있다고 생각합니다.

따라서 구조체 목록이 있으면 읽기 전용 으로 처리해야 합니까? 목록의 요소를 변경해야하는 경우 구조체가 아닌 클래스를 사용해야합니까?


MyList[1] = new MyStruct("bob");

C #의 구조체는 거의 항상 변경 불가능하도록 설계되어야합니다 (즉, 생성 된 후에는 내부 상태를 변경할 방법이 없음).

귀하의 경우 원하는 것은 단일 속성 또는 필드를 변경하지 않고 지정된 배열 인덱스의 전체 구조체를 바꾸는 것입니다.


좀 빠지는. 유형을 클래스 또는 구조체로 디자인하는 것은 컬렉션에 저장해야하는 필요성에 의해 구동되어서는 안됩니다. :) 필요한 '의미론'을 살펴 봐야합니다.

당신이보고있는 문제는 값 유형 의미론 때문입니다. 각 값 유형 변수 / 참조는 새 인스턴스입니다. 네가 얘기 할 때

Struct obItem = MyList[1];

struct의 새 인스턴스가 생성되고 모든 멤버가 하나씩 복사됩니다. 따라서 MyList [1]의 복제본, 즉 2 개의 인스턴스가 있습니다. 이제 obItem을 수정해도 원본에는 영향을주지 않습니다.

obItem.Name = "Gishu";  // MyList[1].Name still remains "peter"

이제 여기에서 2 분 동안 저와 함께 참아주세요. (이것은 꿀꺽 꿀꺽 꿀꺽하는 데 시간이 걸립니다 .. 저를 위해했습니다. :) 구조체를 컬렉션에 저장하고 질문에 표시된대로 수정해야하는 경우 정말 다음을 만들어야합니다. 귀하의 구조체는 인터페이스를 노출합니다 ( 그러나 이것은 권투를 초래할 것입니다 ). 그런 다음 boxed 객체를 참조하는 인터페이스 참조를 통해 실제 구조체를 수정할 수 있습니다.

다음 코드 스 니펫은 위에서 말한 내용을 보여줍니다.

public interface IMyStructModifier
{
    String Name { set; }
}
public struct MyStruct : IMyStructModifier ...

List<Object> obList = new List<object>();
obList.Add(new MyStruct("ABC"));
obList.Add(new MyStruct("DEF"));

MyStruct temp = (MyStruct)obList[1];
temp.Name = "Gishu";
foreach (MyStruct s in obList) // => "ABC", "DEF"
{
    Console.WriteLine(s.Name);
}

IMyStructModifier temp2 = obList[1] as IMyStructModifier;
temp2.Name = "Now Gishu";
foreach (MyStruct s in obList) // => "ABC", "Now Gishu"
{
    Console.WriteLine(s.Name);
}

HTH. 좋은 질문.
업데이트 : @Hath-당신은 내가 그렇게 간단한 것을 간과했는지 확인하기 위해 나를 달렸다. (setter 속성이 dont하고 메소드가
작동하면
일관성 이 없습니다 . .Net 유니버스는 여전히 균형을 이룹니다. :) Setter 메소드가 작동하지 않습니다. obList2 [1]은 상태가 수정 될 복사본을 반환합니다. 목록의 원래 구조체는 수정되지 않은 상태로 유지됩니다. 그래서 Set-via-Interface는 그것을 할 수있는 유일한 방법 인 것 같습니다.

List<MyStruct> obList2 = new List<MyStruct>();
obList2.Add(new MyStruct("ABC"));
obList2.Add(new MyStruct("DEF"));
obList2[1].SetName("WTH");
foreach (MyStruct s in obList2) // => "ABC", "DEF"
{
    Console.WriteLine(s.Name);
}

구조체가 "불변"하는 것은 그리 많지 않습니다.

실제 근본적인 문제는 구조체가 참조 유형이 아니라 값 유형이라는 것입니다. 따라서 목록에서 구조체에 대한 "참조"를 꺼내면 전체 구조체의 새 복사본이 생성됩니다. 따라서 변경 사항은 목록의 원본 버전이 아니라 복사본을 변경하는 것입니다.

Like Andrew states, you have to replace the entire struct. As that point though I think you have to ask yourself why you are using a struct in the first place (instead of a class). Make sure you aren't doing it around premature optimization concerns.


There is nothing wrong with structs that have exposed fields, or that allow mutation via property setters. Structs which mutate themselves in response to methods or property getters, however, are dangerous because the system will allow methods or property getters to be called on temporary struct instances; if the methods or getters make changes to the struct, those changes will end up getting discarded.

Unfortunately, as you note, the collections built into .net are really feeble at exposing value-type objects contained therein. Your best bet is usually to do something like:

  MyStruct temp = myList[1];
  temp.Name = "Albert";
  myList[1] = temp;

Somewhat annoying, and not at all threadsafe. Still an improvement over a List of a class type, where doing the same thing might require:

  myList[1].Name = "Albert";

but it might also require:

  myList[1] = myList[1].Withname("Albert");

or maybe

  myClass temp = (myClass)myList[1].Clone();
  temp.Name = "Albert";
  myList[1] = temp;

or maybe some other variation. One really wouldn't be able to know unless one examined myClass as well as the other code that put things in the list. It's entirely possible that one might not be able to know whether the first form is safe without examining code in assemblies to which one does not have access. By contrast, if Name is an exposed field of MyStruct, the method I gave for updating it will work, regardless of what else MyStruct contains, or regardless of what other things may have done with myList before the code executes or what they may expect to do with it after.


In addition to the other answers, I thought it could be helpful to explain why the compiler complains.

When you call MyList[1].Name, unlike an array, the MyList[1] actually calls the indexer method behind the scenes.

Any time a method returns an instance of a struct, you're getting a copy of that struct (unless you use ref/out).

So you're getting a copy and setting the Name property on a copy, which is about to be discarded since the copy wasn't stored in a variable anywhere.

This tutorial describes what's going on in more detail (including the generated CIL code).

참고URL : https://stackoverflow.com/questions/51526/changing-the-value-of-an-element-in-a-list-of-structs

반응형