请考虑以下代码:
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
答案 0 :(得分:1)
原因是您的对象成员路径中有struct
个值类型。
当您在值类型上调用ObjectAccessor.GetFieldOrPropertyType
时,它会返回原始值的副本。然后,当你最终改变一个值(或者更深入地挖掘更多值类型成员的兔子洞)时,你就会改变一个副本。
我建议你avoid mutable structs altogether。可能如果你将类型更改为引用类型,它将正常工作。
编辑:考虑使用FullName
和LoginRequest
类型进行测试:
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
的步骤,那么请确保重新分配您所创建的每个副本,它可能会有效。