Messagepack-csharp: Private fields on base class do not get serialized

Created on 12 Feb 2020  路  13Comments  路  Source: neuecc/MessagePack-CSharp

I have business classes derived from a base class. The base class has several private fields that should to be serialized.
Simplified demo example:
```C#
[DataContract]
public class BaseClass
{
[DataMember]
private int _intField;

public BaseClass()
{
_intField = SomeMethod();
}

private int SomeMethod() => 100;

public int IntField => _intField;
}

[DataContract]
public class DerivedClass : BaseClass
{
[DataMember]
public string Name {get; set;}
}


Is it possible to serialize/deserialize ``DerivedClass`` with ``_intField`` from ``BaseClass``? I tried with this code:
```C#
var obj = new DerivedClass {Name = "name"};
var bin = MessagePackSerializer.Serialize(obj);
var obj2 = MessagePackSerializer.Deserialize<DerivedClass>(bin);
//or
var obj = new DerivedClass {Name = "name"};
var bin = MessagePackSerializer.Serialize(obj, StandardResolverAllowPrivate.Options);
var obj2 = MessagePackSerializer.Deserialize<DerivedClass>(bin, StandardResolverAllowPrivate.Options);

But I had _intField = 0.
If it is not possible could you suggest some workaround for my situation.
Important addition: I can't change BaseClass.

bug

Most helpful comment

Ok.
Actually, it is a bug.
https://github.com/neuecc/MessagePack-CSharp/blob/56fa862/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs#L1426
GetRuntimeFields does not provides non public field on base classes.

Specify BindingFlags.NonPublic to include non-public fields (that is, private, internal, and protected fields) in the search. Only protected and internal fields on base classes are returned;

Replace iterate method like this.

static IEnumerable<FieldInfo> GetAllFields(Type type)
{
    if (type.BaseType != null)
    {
        foreach (var item in GetAllFields(type.BaseType))
        {
            yield return item;
        }
    }

    // with declared only
    foreach (var item in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    {
        yield return item;
    }
}

All 13 comments

I feel like StandardResolverAllowPrivate should have worked. @neuecc what do you think? That may be a bug.

In the meantime, you can get anything to work with a custom formatter.

I don't understand what data i confirm.

image

Maybe @OlegNadymov can clarify what version of MessagePack produced the error.

@AArnott @neuecc thank you for the feedback!
It's my bad. I simplified the demo code too much. Here is example where the error is reproduced:
```C#
[DataContract]
public class BaseClass
{
[DataMember]
private int _intField;

public int IntField => _intField;

internal void SetIntField(int intField)
{
  _intField = intField;
}

}

[DataContract]
public class DerivedClass : BaseClass
{
[DataMember]
public string Name { get; set; }
}

The test:
```C#
[Fact]
public void PrivateFieldFromBaseClassSerializeTest()
{
    // Arrange 
    var obj = new DerivedClass { Name = "name" };
    obj.SetIntField(100);

    // Act
    var bin = MessagePackSerializer.Serialize(obj, StandardResolverAllowPrivate.Options);
    var obj2 = MessagePackSerializer.Deserialize<DerivedClass>(bin, StandardResolverAllowPrivate.Options);

    // Assert
    Assert.Equal(obj.IntField, obj2.IntField);
}

The test result:
msgpack_issue
I updated MessagePack to the latest version 2.1.90.
The main idea here is support for serialization of private fields from base classes.

Ok.
Actually, it is a bug.
https://github.com/neuecc/MessagePack-CSharp/blob/56fa862/src/MessagePack.UnityClient/Assets/Scripts/MessagePack/Resolvers/DynamicObjectResolver.cs#L1426
GetRuntimeFields does not provides non public field on base classes.

Specify BindingFlags.NonPublic to include non-public fields (that is, private, internal, and protected fields) in the search. Only protected and internal fields on base classes are returned;

Replace iterate method like this.

static IEnumerable<FieldInfo> GetAllFields(Type type)
{
    if (type.BaseType != null)
    {
        foreach (var item in GetAllFields(type.BaseType))
        {
            yield return item;
        }
    }

    // with declared only
    foreach (var item in type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
    {
        yield return item;
    }
}

@AArnott @neuecc thank you for the quick fix 馃憤 Could you tell when will the fix be on NuGet?

One more addition. I don't know if anybody uses private properties. But if some developers use private properties and these properties are declared in base classes, then the properties do not get serialized. I think I don't use private properties so I can't realize whether it is necessary to add support for this case or not. What do you think?

I added a test for private properties on a base class and so far, the tests as already passing.

I added a test for private properties on a base class and so far, the tests as already passing.

@AArnott Private properties on base classes do not get serialized. I mean this case:
```C#
[DataContract]
public class BaseClass
{
[DataMember]
private string PrivateBaseNote { get; set; }

internal void SetPrivateBaseNote(string note)
{
    PrivateBaseNote = note;
}

}

[DataContract]
public class DerivedClass : BaseClass
{
[DataMember]
public int DerivedClassName { get; set; }
}
```

If you serialize DerivedClass the property PrivateBaseNote will not get serialized. But as I've mentioned before I don't have private properties with DataMember attribute. And so I don't know if it is should be serialized or not.

@neuecc, anyway, could tell me when you will be able to release the fix for the original issue on NuGet?

@OlegNadymov In my PR I added a test that shows private properties on the base class do get serialized.
Did I miss some particular trait about your repro that leads the properties in your code snippet above to not be serialized?

@AArnott in your PR there are public properties with private fields. But in my example there is private auto-implemented property (without backing fields)

Your test:
```C#
// private field (not property)
[DataMember]
private int baseClassField;

My example
```C#
// private property (not field)
[DataMember]
private string PrivateBaseNote { get; set; }

I hope it will be clear for understanding particular trait in my example.

Ah, crap. I meant for that property in my test to be private. That's what I overlooked. Thanks!

I was happy to help!

Was this page helpful?
0 / 5 - 0 ratings