考虑以下代码:
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
to更改"Sergiy"
,而当悬停obj
并导航到同一字段时,它仍然存在null
。
顺便说一句,代码可在此处获得:https ://github.com/rryk/opensim-omp/blob/kiara/KIARA/
更新:我在以下测试中运行时遇到了这种奇怪的行为:https ://github.com/rryk/opensim-omp/blob/kiara/KIARA.Test/FunctionMapingConfigTest.cs
更新:感谢克里斯,我已经意识到问题出在哪里,并重新实现了代码,如下所示:
// 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;
}
}