2013-04-03 2 views
3

다음 코드를 고려하십시오개체가 복사되는 이유는 무엇입니까?

class ObjectAccessor { 
    ... 
    static public void SetValueAtPath(ref object obj, List<PathEntry> path, 
            object value) 
    { 
    if (path.Count == 0) 
     obj = value; 

    object container = obj; 
    for (int i = 0; i < path.Count - 1; i++) 
     container = GetMember(container, path[i]); 
    SetMember(container, path[path.Count - 1], value); 
    } 
    ... 
} 

나는 path에 따라 발견되는 특정 필드 또는 obj 내부 깊은 속성에 value을 할당하려는 SetValueAtPath를 호출. container 변수는 필드가 들어있는 실제 객체를 가리키고 해당 필드를 수정하려면 SetMember이 필요합니다. 컨테이너가 참조이므로 원본 obj도 수정해야합니다. 그러나 디버거에 따르면 container 만 수정되고 obj은 변경되지 않습니다. 사본이 어디서 왜 만들어 졌습니까? 여기

는 위의 코드에서 사용되는 유형과 기능의 정의입니다 :

class PathEntry 
{ 
    public enum PathEntryKind 
    { 
    Index, 
    Name 
    } 
    public PathEntryKind Kind; 
    public int Index; // Kind == Index 
    public string Name; // Kind == Name 
} 

class ObjectAccessor { 
    ... 
    static public object GetMember(object obj, PathEntry member) 
    { 
    if (member.Kind == PathEntry.PathEntryKind.Index) 
     return ((IList)obj)[member.Index]; 
    else 
     return GetFieldOrPropertyValue(obj, member.Name); 
    } 

    static public object GetFieldOrPropertyValue(object obj, string name) 
    { 
    FieldInfo fieldInfo = obj.GetType().GetField(name, BindingFlags.Public | 
     BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); 
    PropertyInfo propertyInfo = obj.GetType().GetProperty(name, BindingFlags.Public | 
     BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); 
    if (fieldInfo != null) 
     return fieldInfo.GetValue(obj); 
    else if (propertyInfo != null) 
     return propertyInfo.GetValue(obj, null); 
    throw new IncompatibleNativeTypeException(); 
    } 

    static public void SetMember(object obj, PathEntry member, object value) 
    { 
    if (member.Kind == PathEntry.PathEntryKind.Index) 
     ((IList)obj)[member.Index] = value; 
    else 
     SetFieldOrPropertyValue(obj, member.Name, value); 
    } 

    static public void SetFieldOrPropertyValue(object obj, string name, object value) 
    { 
    FieldInfo fieldInfo = obj.GetType().GetField(name, BindingFlags.Public | 
     BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); 
    PropertyInfo propertyInfo = obj.GetType().GetProperty(name, BindingFlags.Public | 
     BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance); 
    if (fieldInfo != null) 
     fieldInfo.SetValue(obj, value); 
    else if (propertyInfo != null) 
     propertyInfo.SetValue(obj, value, null); 
    } 
    ... 
} 

UPDATE :

object obj = ObjectConstructor.ConstructObject(encoding, objectType); 
... 
ObjectAccessor.SetValueAtPath(ref obj, encodingEntry.ValuePath, value); 

@Kirk : 전화 사이트에서 코드 container 변수를 유혹 SetMember을 실행 한 후 디버거에서 수정 된 문자열 필드가 null에서 "Sergiy"으로 변경되는 것을 볼 수 있으며, obj과 동일한 필드로 이동하면 null으로 남습니다.

, BTW 코드는 여기에 있습니다 : https://github.com/rryk/opensim-omp/blob/kiara/KIARA/ UPDATE

: https://github.com/rryk/opensim-omp/blob/kiara/KIARA.Test/FunctionMapingConfigTest.cs

가 UPDATE : 크리스에 덕분에, 내가 깨달은 나는 다음 테스트에서 실행이 이상한 행동을 경험 문제점은 무엇이며 다음과 같이 코드를 다시 구현했습니다.

// Supports empty path in which case modifies passed obj as it's passed by reference. 
static public void SetValueAtPath(ref object obj, List<PathEntry> path, object value) 
{ 
    if (path.Count == 0) 
    { 
    obj = value; 
    return; 
    } 

    // List of value types (structs) to be reassigned. 
    List<KeyValuePair<object, PathEntry>> valueTypeContainers = new List<KeyValuePair<object,PathEntry>>(); 

    object container = obj; 
    for (int i = 0; i < path.Count - 1; i++) 
    { 
    object newContainer = GetMember(container, path[i]); 

    // Keep the trail of the value types (struct) or clear it if next container is non-value type. 
    if (newContainer.GetType().IsValueType) 
     valueTypeContainers.Add(new KeyValuePair<object, PathEntry>(container, path[i])); 
    else 
     valueTypeContainers.Clear(); 

    container = newContainer; 
    } 

    SetMember(container, path[path.Count - 1], value); 

    // Reassign the value types (structs). 
    for (int i = valueTypeContainers.Count - 1; i >= 0; i--) 
    { 
    object valueContainer = valueTypeContainers[i].Key; 
    PathEntry pathEntry = valueTypeContainers[i].Value; 
    SetMember(valueContainer, pathEntry, container); 
    container = valueContainer; 
    } 
} 
+0

'SetValueAtPath' (및 주변 코드)를 어떻게 호출하는지 표시 할 수 있습니까? –

+0

Simon의 요청 외에도 "디버거에 따르면 컨테이너 만 수정되고 obj는 변경되지 않습니다."라는 의미를 자세히 설명하십시오. 어떤 의미에서 컨테이너가 수정 되었습니까? –

+0

사이드 노트 : 나는 정상적인 생각 과정으로 이것을 재현 할 수 없다. 그래서 이상한 일이 벌어지고 있습니다 (나는 당신의 전화 사이트를 의심합니다.). –

답변

1

이유는 struct 개체 유형 경로의 값 유형

ObjectAccessor.GetFieldOrPropertyType을 value-type으로 호출하면 복사본이 원래 값으로 반환됩니다. 그런 다음 결국 값을 변경하거나 더 가치있는 멤버를 복사하는 토끼 구멍을 내려 가면 복사본을 변경합니다.

나는 당신에게 avoid mutable structs altogether를 제안합니다. 당신이 당신의 타입을 참조 타입으로 변경한다면 그것은 잘 동작 할 것입니다.

편집 : 유형 FullNameLoginRequest 사용하여 시험을 감안할 때 :

struct FullName 
{ 
    public string first; 
    public string last; 
} 

struct LoginRequest 
{ 
    FullName name; 
    string pwdHash; 
    string start; 
    string channel; 
    string version; 
    string platform; 
    string mac; 
    string[] options; 
    string id0; 
    string agree_to_tos; 
    string read_critical; 
    string viewer_digest; 
} 

그리고 경로 ["name", "first"], 그것은 "이름"에서 FullName의 사본을 만들고, 그 "최초의"필드 값을 설정합니다. 그러나이 사본은 결국 폐기됩니다.

LoginRequest login = new LoginRequest(); 
FullName name = login.name; 
name.first = "My name!"; 
Console.WriteLine(name.first); //My name! 
Console.WriteLine(login.name.first); //null 

EDITx2 :

그것은 쓰기와 같은 것 그것 (I 라이브러리의 성격을 고려 등 의심) 중첩 된 가치 유형을 피하기 위해 가능한 아니라면, 당신이 을 할 수 do는 각각 다시 검색된 것으로 설정됩니다. 따라서 루프/스택 스택 ValuePath에서 어느 단계에서 struct을 검색했는지 확인한 다음 작성한 각 복사본을 다시 할당해야합니다.

+0

EDITx2하려면 : 최종 코드에 대한 질문을 참조하십시오. 나는 똑같은 생각을 가지고 있었지만, 나는 그것을 위해 추가적인 배열을 유지하기로 결정했다. –

+0

@SergiyByelozyorov 경로에 유형 (예 :'class' ->'struct' ->'struct' ->'class'->'struct'->'struct')이 혼합되어있을 때 테스트해볼 수 있습니다. 나는 그러한 상황에서 당신의 알고리즘이 작동하지 않을 것이라는 _ 빠르다. 나는 _ 가치 _ 유형의 _ 마지막 실행을 설명 할 것입니다. 반면에 참조 유형이 적절히 재 할당되지 않고 사본 체인이 깨지기 전에 나타난 체인보다 높은 가치 유형이 있습니다. 편집 : 아마도 당신이 할 필요가 아마도'valueTypeContainers' 참조 유형이 발견되면 지우지 않는 것이라고 생각합니다. 그래도 추측. –

+0

나는 이것을 고려했다. 사실 두 번째 구조체를 제시 한 경우 경로의 두 번째 클래스에 대한 참조가 포함됩니다. 가장 깊은 객체의 값만 수정하기 때문에 3 번째 struct와 3 번째 struct의 필드에 4 번째 struct (last)를 2 번째 클래스의 필드에 재 할당하면됩니다. –

관련 문제