【发布时间】:2013-04-03 00:46:16
【问题描述】:
考虑以下代码:
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:在执行SetMember 后,在调试器中悬停container 变量时,我看到修改后的字符串字段从null 更改为"Sergiy",而在悬停obj 并导航到同一字段时,它仍然是@ 987654341@.
顺便说一句,代码在这里可用: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;
}
}
【问题讨论】:
-
你能告诉我们你是怎么打电话给
SetValueAtPath(和周围的代码)吗? -
除了Simon的要求,还请详细说明“但是,根据调试器,只有container被修改,obj保持不变”是什么意思。容器在什么意义上被修改?
-
旁注:我无法用任何正常的思维过程重现这一点。所以发生了一些奇怪的事情(我怀疑它是你的呼叫站点..)。
-
也许这只是调试器中的陈旧数据,或者您正在查看错误的断点,或者如果为发布模式编译时进行了一些优化。您可以尝试注销或将值写入控制台而不是使用调试器吗?编辑:此外,沿着
ValuePath,是否有any 成员访问值类型(struct)作为属性 或 索引器访问? EDITx2:既然您已经发布了源代码的链接,那么您的测试用例中使用的objectType和确切的encodingEntry.ValuePath是什么? -
我已经尝试打印到控制台 - 与调试器中的结果相同。始终在调试模式下编译。是的,有结构在路上 - 请参阅github.com/rryk/opensim-omp/blob/kiara/KIARA.Test/… 中的实际数据类型。
objectType将是LoginRequest,ValuePath将是["name", "first"]。
标签: c# reflection copy