Closed ghost closed 5 years ago
Models:
public struct EventStruct
{
public EventStruct(Guid id, PayloadStruct payload, int version, DateTimeOffset created) { Id = id; Payload = payload; Version = version; Created = created; }
public Guid Id { get; }
public PayloadStruct Payload { get; }
public int Version { get; }
public DateTimeOffset Created { get; }
}
public struct PayloadStruct
{
public PayloadStruct(string value) { Value = value; }
public string Value { get; }
}
public struct EventStructWithPrivateSetters
{
public EventStructWithPrivateSetters(Guid id, PayloadStructWithPrivateSetter payload, int version, DateTimeOffset created) { Id = id; Payload = payload; Version = version; Created = created; }
public Guid Id { get; private set; }
public PayloadStructWithPrivateSetter Payload { get; private set; }
public int Version { get; private set; }
public DateTimeOffset Created { get; private set; }
}
public struct PayloadStructWithPrivateSetter
{
public PayloadStructWithPrivateSetter(string value) { Value = value; }
public string Value { get; private set; }
}
public struct EventStructWithReadOnlyBackingFields
{
private readonly Guid _id;
private readonly PayloadStructWithReadOnlyBackingField _payload;
private readonly int _version;
private readonly DateTimeOffset _created;
public EventStructWithReadOnlyBackingFields(Guid id, PayloadStructWithReadOnlyBackingField payload, int version, DateTimeOffset created) { _id = id; _payload = payload; _version = version; _created = created; }
public Guid Id => _id;
public PayloadStructWithReadOnlyBackingField Payload => _payload;
public int Version => _version;
public DateTimeOffset Created => _created;
}
public struct PayloadStructWithReadOnlyBackingField
{
private readonly string _value;
public PayloadStructWithReadOnlyBackingField(string value) { _value = value; }
public string Value => _value;
}
public class EventClass
{
public EventClass(Guid id, PayloadClass payload, int version, DateTimeOffset created) { Id = id; Payload = payload; Version = version; Created = created; }
public Guid Id { get; }
public PayloadClass Payload { get; }
public int Version { get; }
public DateTimeOffset Created { get; }
}
public class PayloadClass
{
public PayloadClass(string value) { Value = value; }
public string Value { get; }
}
public class EventClassWithPrivateSetters
{
public EventClassWithPrivateSetters(Guid id, PayloadClassWithPrivateSetter payload, int version, DateTimeOffset created) { Id = id; Payload = payload; Version = version; Created = created; }
public Guid Id { get; private set; }
public PayloadClassWithPrivateSetter Payload { get; private set; }
public int Version { get; private set; }
public DateTimeOffset Created { get; private set; }
}
public class PayloadClassWithPrivateSetter
{
public PayloadClassWithPrivateSetter(string value) { Value = value; }
public string Value { get; private set; }
}
public class EventClassWithReadOnlyBackingFields
{
private readonly Guid _id;
private readonly PayloadClassWithWithReadOnlyBackingField _payload;
private readonly int _version;
private readonly DateTimeOffset _created;
public EventClassWithReadOnlyBackingFields(Guid id, PayloadClassWithWithReadOnlyBackingField payload, int version, DateTimeOffset created) { _id = id; _payload = payload; _version = version; _created = created; }
public Guid Id => _id;
public PayloadClassWithWithReadOnlyBackingField Payload => _payload;
public int Version => _version;
public DateTimeOffset Created => _created;
}
public class PayloadClassWithWithReadOnlyBackingField
{
private readonly string _value;
public PayloadClassWithWithReadOnlyBackingField(string value) { _value = value; }
public string Value => _value;
}
And tests:
[TestClass]
public class NetJSONTests
{
private static readonly NetJSONSettings Settings = new NetJSONSettings{ DateFormat = NetJSONDateFormat.ISO, TimeZoneFormat = NetJSONTimeZoneFormat.Utc };
private static readonly Random Random = new Random();
[TestMethod] // Fails: not deserialised
public void EventStructTest()
{
var e = new EventStruct(Guid.NewGuid(), new PayloadStruct(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventStruct>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
[TestMethod] // Fails: System.AccessViolationException at NetJSON.SetterPropertyValue<EventStructWithPrivateSetters>(EventStructWithPrivateSetters instance, object value, System.Reflection.MethodInfo methodInfo)
public void EventStructWithPrivateSettersTest()
{
var e = new EventStructWithPrivateSetters(Guid.NewGuid(), new PayloadStructWithPrivateSetter(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventStructWithPrivateSetters>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
[TestMethod] // Fails: not deserialised
public void EventStructWithReadOnlyBackingFieldsTest()
{
var e = new EventStructWithReadOnlyBackingFields(Guid.NewGuid(), new PayloadStructWithReadOnlyBackingField(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventStructWithReadOnlyBackingFields>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
[TestMethod] // Fails: not deserialised
public void EventClassTest()
{
var e = new EventClass(Guid.NewGuid(), new PayloadClass(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventClass>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
[TestMethod] // Passes
public void EventClassWithPrivateSettersTest()
{
var e = new EventClassWithPrivateSetters(Guid.NewGuid(), new PayloadClassWithPrivateSetter(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventClassWithPrivateSetters>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
[TestMethod] // Fails: not deserialised
public void EventClassWithReadOnlyBackingFieldsTest()
{
var e = new EventClassWithReadOnlyBackingFields(Guid.NewGuid(), new PayloadClassWithWithReadOnlyBackingField(Guid.NewGuid().ToString("n")), Random.Next(), DateTimeOffset.UtcNow);
var s = NetJSON.NetJSON.Serialize(e, Settings);
var d = NetJSON.NetJSON.Deserialize<EventClassWithReadOnlyBackingFields>(s, Settings);
Assert.AreEqual(e.Id, d.Id);
Assert.AreEqual(e.Payload.Value, d.Payload.Value);
Assert.AreEqual(e.Version, d.Version);
Assert.AreEqual(e.Created, d.Created);
}
}
Thanks for the details. I will look into it.
Sorry, I have not had time to look much into it. I will try to find some time this coming week to analyze it. Thanks,
Thanks for the update, happy that you're looking at it!
Sorry been busy with other things that came up. I will look at it soon and get back to you.
Hi, besides the auto generated backing fields for properties. Could I assume the naming of fields used when there is no setter? E.x. fields will always end with name similar to property while ignoring the casing of name.
Thanks,
@rpgmaker Instead of relying on naming conventions, it may be possible to parse the IL byte array of the property get method body. Usually it is as simple as just a few bytes, if it is compiler generated, then you can find the field token and manipulate its values.
When it is compiler generated, it will use backing fields. But the scenario above uses a field returned by the get. I guess I could consider parsing the first few bytes and use it for field token assuming it is faster than manually calling get field and fields. At the moment, I need to do ldtoken and pass the field itself to the opcodes call and then resolve the runtime handle of the field in order to use dynamic method for it. So not sure yet if it would help till I test it.
Thanks for the idea either way.
Please test using the code from the repo. I will create nuget package once i get another item completed. @wmjordan, i will look into the IL code that can detect the field later because it requires parsing which i think might be too heavy for this logic itself. I am open if you will like to do it too :)
Pal, use this code. I wrote this quite some time before. Below code finds the underlying static or instance field from a read-only property like:
? Property { get; }
or
? Property => _UnderlyingField;
public static FieldInfo ReflectUnderlyingField(this PropertyInfo property) {
var m = property.GetGetMethod(true);
if (m == null) {
return null;
}
var ilBytes = m.GetMethodBody().GetILAsByteArray();
var l = ilBytes.Length;
if (l != 6 && l != 7) {
return null;
}
var gta = m.ReflectedType.IsGenericType ? m.ReflectedType.GetGenericArguments() : null;
var gma = m.IsGenericMethod ? m.GetGenericArguments() : null;
if (m.IsStatic) {
if (l == 6 && ilBytes[0] == OpCodes.Ldsfld.Value && ilBytes[5] == OpCodes.Ret.Value) {
return property.Module.ResolveField(BitConverter.ToInt32(ilBytes, 1), gta, gma);
}
}
else {
if (l == 7 && ilBytes[0] == OpCodes.Ldarg_0.Value && ilBytes[1] == OpCodes.Ldfld.Value && ilBytes[6] == OpCodes.Ret.Value) {
return property.Module.ResolveField(BitConverter.ToInt32(ilBytes, 2), gta, gma);
}
}
return null;
}
The problem about property parsing is that it requires an individual DynamicMethod
to access the underlying private field.
Thanks, I will take a look at it. Note, this does not work for netstandard 1.6. since method body function is not available.
Thanks,
netstandard 1.*
is a headache.
It seemed to me that things from MS was not so good, until their 2.0 versions were released, such as,
.NET Framework 2.0, .NET Core 2.0, .NET Standard 2.0...
Following on from #116 , please could you kindly add support for immutable structs with constructor initialisation?
After testing a few scenarios (v1.2.7 on .Net Core 2.1), it appears that this currently only works for classes with private setters (and doesn't seem to support some of the scenarios mentioned in #116):
The tests and models to follow - happy to post the IL if you wish to take this on...