为什么要复制对象?

时间:2013-04-03 00:46:17

标签: c# reflection copy

请考虑以下代码:

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);
  }
  ...
}

致电SetValueAtPath时,我打算将value分配到obj内的path深处的特定字段或属性。我希望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);
  }
  ...
}

更新:来电网站的代码:

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

@Kirk:在执行container后将SetMember变量悬停在调试器中时,我看到修改后的字符串字段从null更改为"Sergiy",而在悬停{{1}时并导航到同一个字段,它仍为obj

顺便说一下,代码可以在这里找到:https://github.com/rryk/opensim-omp/blob/kiara/KIARA/

更新:在以下测试中运行时,我遇到了这种奇怪的行为:https://github.com/rryk/opensim-omp/blob/kiara/KIARA.Test/FunctionMapingConfigTest.cs

更新:感谢Chris,我已经意识到问题所在并重新实现了以下代码:

null

1 个答案:

答案 0 :(得分:1)

原因是您的对象成员路径中有struct个值类型。

当您在值类型上调用ObjectAccessor.GetFieldOrPropertyType时,它会返回原始值的副本。然后,当你最终改变一个值(或者更深入地挖掘更多值类型成员的兔子洞)时,你就会改变一个副本。

我建议你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"],它会在“name”处创建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:如果避免使用嵌套值类型是不可行的(考虑到库的性质,我怀疑这样做),你可以做的事情就是将每一个检索回来。因此,如果您在遍历ValuePath的循环/堆栈中确定检索到struct的步骤,那么请确保重新分配您所创建的每个副本,它可能会有效。