c# - 为什么要复制对象?

标签 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 深处的特定字段或属性,这是通过 路径。我希望 container 变量指向包含该字段的实际对象,并希望 SetMember 修改该字段。由于container是引用,原来的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:在执行 SetMember 后将鼠标悬停在调试器中的 container 变量时,我看到修改后的字符串字段从 null 更改为 "Sergiy",而当悬停 obj 并导航到同一字段时,它仍然是 null

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

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

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

// 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;
  }
}

最佳答案

原因是因为您的对象成员路径中有 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的副本,并设置它的"first"字段值.但是这个副本最终被丢弃了。

这和写一样:

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 的步骤,然后确保重新分配您制作的每个副本,它可能工作。

关于c# - 为什么要复制对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15777011/

相关文章:

c# - 在数据库中插入 1,000,000 行时超时(有时)

c# - 在 form1.cs 和 program.cs 之间传递数据

oop - 使用反射是否被认为是不面向对象的?

java - 如何使用对象的 getClass() 方法设置泛型类型

Scala 2.10 反射 : Creating an Array from a type

c# - 如何制作 XML 节点的副本及其所有子节点和值但名称不同 C# .NET

c# - 如何使用 C#、Entity Framework 和 SQL Server 2008 读取/写入地理数据?

c# - 使用Nest DSL语法用多个术语过滤的ElasticSearch嵌套查询无法按预期工作

html - 将 HTML 表格粘贴到 Excel 中,如何在单元格中保留换行符

c - 如何复制 char **a - C 的值