FormatterServices.GetSerializableMembers returns protected and internal fields twice for derived types. Once as an instance of SerializationFieldInfo
and once as RtFieldInfo
.
I find this very confusing! Can anyone help me understand why Microsoft decided to implement it this way?
I have written a sample program that re-produce my problem:
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();
}
}
I would expect that privateField
and protectedField
are reported once each. However this is the actual output when running the program:
DerivedA BaseA+privateField (67108865) DerivedB protectedField (67108866) BaseB+protectedField (67108866)
As you can see protectedField
appear twice, with different names but with the same metadata token so it is indeed the very same field.
Can anyone explain why?
This seems not to be related so much to the FormatterServices, but to how reflection works and how it is used by FormatterServices. For the Type.GetFields
method when used with BindingFlags.NonPublic
(see: http://msdn.microsoft.com/en-us/library/6ztex2dc.aspx): "Only protected and internal fields on base classes are returned; private fields on base classes are not returned."
Completely stripped of any checks, and tailored to your example, what FormatterServices does to get the fields is basically:
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();
}
}
Which generates the following output for your example classes:
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)
And FormatterServices simply does not filter its results by checking if it includes the same field from the same declaring type more than once. Given the way FormatterServices is implemented (performing checks on a type's serializable base types), they should probably be doing something like filter by ReflectedType == DeclaringType:
Hope this helps.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With