C# GetSerializableMembers(FormatterServices)返回同一字段两次!为什么?
为派生类型返回两次受保护字段和内部字段。一次作为C# GetSerializableMembers(FormatterServices)返回同一字段两次!为什么?,c#,serialization,reflection,C#,Serialization,Reflection,为派生类型返回两次受保护字段和内部字段。一次作为SerializationFieldInfo的实例,一次作为RtFieldInfo 我觉得这很令人困惑!有人能帮我理解微软为什么决定用这种方式实施它吗 我已经编写了一个示例程序来重新生成我的问题: class Program { [Serializable] public class BaseA { private int privateField; } [Serializable]
SerializationFieldInfo
的实例,一次作为RtFieldInfo
我觉得这很令人困惑!有人能帮我理解微软为什么决定用这种方式实施它吗
我已经编写了一个示例程序来重新生成我的问题:
class Program
{
[Serializable]
public class BaseA
{
private int privateField;
}
[Serializable]
public class DerivedA : BaseA { }
[Serializable]
public class BaseB
{
protected int protectedField;
}
[Serializable]
public class DerivedB : BaseB { }
static void Main(string[] args)
{
Program.PrintMemberInfo(typeof(DerivedA));
Program.PrintMemberInfo(typeof(DerivedB));
Console.ReadKey();
}
static void PrintMemberInfo(Type t)
{
Console.WriteLine(t.Name);
foreach (var mbr in FormatterServices.GetSerializableMembers(t))
{
Console.WriteLine(" {0} ({1})", mbr.Name, mbr.MetadataToken);
}
Console.WriteLine();
}
}
我希望privateField
和protectedField
每个报告一次。但是,这是运行程序时的实际输出:
DerivedA
BaseA+privateField (67108865)
DerivedB
protectedField (67108866)
BaseB+protectedField (67108866)
德里维达
BaseA+privateField(67108865)
德里维达
protectedField(67108866)
BaseB+protectedField(67108866)
正如您所看到的,protectedField
出现两次,名称不同,但元数据标记相同,因此它实际上是同一个字段
有人能解释为什么吗?在从几个角度进行测试后,我决定改变我的答案 GetSerializableMembers()方法存在缺陷, 这些副本不是底层内存的正确投影。 (这真是令人惊讶) 我建议使用: t、 GetType().GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 并检查成员名单的入会结果
祝你好运。这似乎与FormatterServices没有太大关系,而是与反射如何工作以及FormatterServices如何使用反射有关。对于与
BindingFlags.NonPublic
(请参阅:)一起使用的Type.GetFields
方法:“只返回基类上的受保护字段和内部字段;不返回基类上的私有字段。”
FormatterServices完全去除了任何检查,并根据您的示例进行了定制,其获取字段的功能基本上是:
static IEnumerable<FieldInfo> GetSerializableFields(Type type, Func<Type, IEnumerable<FieldInfo>> andNext)
{
return
(type.IsInterface || type == typeof(object))
? new FieldInfo[0]
: type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(f => (f.Attributes & FieldAttributes.NotSerialized) != FieldAttributes.NotSerialized)
.Concat(andNext(type));
}
static void PrintMemberInfo(Type t)
{
Console.WriteLine(t.Name);
Func<Type, IEnumerable<FieldInfo>> andNext = null;
andNext = tp => GetSerializableFields(tp.BaseType, andNext);
var fields = GetSerializableFields(t, tp => new FieldInfo[0]).ToArray();
var base_fields = GetSerializableFields(t.BaseType, andNext).ToArray();
var counter = 0;
foreach (var f in fields.Concat(base_fields))
{
Console.WriteLine(
"{0} Reflected: {1} - Declaring: {2} - Field: {3} ({4})",
(counter++) + 1, f.ReflectedType.Name, f.DeclaringType.Name, f.Name, f.MetadataToken);
}
Console.WriteLine();
}
}
FormatterServices只是不通过多次检查是否包含来自同一声明类型的同一字段来过滤其结果。考虑到FormatterServices的实现方式(对类型的可序列化基类型执行检查),它们可能应该执行类似filter by ReflectedType==DeclaringType的操作:
希望这能有所帮助。显然,这一点已经知道一段时间了:(评论部分)。但仍然看不到任何解释…:-(感谢您的回答。但是,您写了“…获取所有标记为[Serializable]的派生对象成员,无论其访问修饰符如何。”-我的解释方式实际上与方法返回的内容相反,在同一字段多次返回的情况下。我想不出有哪一个用例的重复项是有趣的或不令人困惑的。如果您能提供这样一个示例,我会将您的答案标记为已接受。公平地说,我添加了这样一个示例。但是示例或案例ge并不是真正的问题所在-问题是:您是否更喜欢返回适合我们作为开发人员或人类如何可视化对象或对象如何物理存储在内存中的结果的方法?我认为您没有抓住我问题的重点。我认为重复的字段实际上指的是同一个成员ce他们有相同的元数据标记。在您发布的示例中,您有两个单独的字段(不同的元数据标记),因此没有重复项!是的-我明白您的观点。我相信(尚未测试)声明受保护的字段并随后派生类时,编译器将创建一个新成员并在基类中设置对该字段的引用。如果不是这样,则该方法存在错误,但我认为这是实际情况。这是不正确的。编译器将不会为继承的成员创建新成员,除非它们被重写恩。是的,就像他们忘了过滤一样。谢谢你的研究工作。
DerivedA
1 Reflected: BaseA - Declaring: BaseA - Field: privateField (67108865)
DerivedB
1 Reflected: DerivedB - Declaring: BaseB - Field: protectedField (67108866)
2 Reflected: BaseB - Declaring: BaseB - Field: protectedField (67108866)