Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GetSerializableMembers (FormatterServices) returns the same field twice! Why?

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?

like image 749
Mårten Wikström Avatar asked Mar 01 '13 20:03

Mårten Wikström


1 Answers

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.

like image 70
Alex Avatar answered Nov 15 '22 12:11

Alex