Open Muchaszewski opened 3 years ago
Can you please confirm whether Foo
is a struct? Structs in c# are value types they cannot be null. Most likely Foo
was a class instead?
You are right. This container was initially a struct but I had changed it to class and forgot about it. I modified the issue to reflect the real problem with class and not struct.
And just to confirm. The above will result in a null ref exception but if we add the following constructor (default constructor) to it, it should work?
public Foo()
{
}
Thats correct
@MFatihMAR 👀
yeah, deserialization code can't construct/instantiate types with non-default constructors. that's a limitation and I think we should mention this on the docs side too and ILPP should spit out an error indicating this limitation.
In case its useful to anyone else, in this thread (and the current documentation) the term default constructor is being used to refer to a parameterless constructor (this messed me up when googling a bit). To elaborate unnecessarily, this constructor only exists if you have explicitly defined it or you have defined no constructors (and so the default constructor given to you is a parameterless one)
tldr; In short, your receiver will receive null
instead of the value you expect if no parameterless constructor is provided in the class you are trying to serialize.
Gratuitous examples for those who only read code and are allergic to text:
✔️ The following will serialize properly (C# gives you a default constructor since you didn't declare any constructors at all and this default constructor is parameterless)
using MLAPI.Serialization;
public class Example : INetworkSerializable
{
public int SomeVariable;
public void NetworkSerialize(NetworkSerializer serializer)
{
serializer.Serialize(ref SomeVariable);
}
}
❌ The following will NOT serialize properly, the receiver will just get null
because you have defined a constructor with parameters, so C# will not give you a default one (which would have been parameterless)
using MLAPI.Serialization;
public class Example : INetworkSerializable
{
public int SomeVariable;
public void NetworkSerialize(NetworkSerializer serializer)
{
serializer.Serialize(ref SomeVariable);
}
public Example(int someVariable)
{
SomeVariable = someVariable;
}
}
✔️ The following will serialize properly because a parameterless constructor was explicitly declared on the class
using MLAPI.Serialization;
public class Example : INetworkSerializable
{
public int SomeVariable;
public void NetworkSerialize(NetworkSerializer serializer)
{
serializer.Serialize(ref SomeVariable);
}
// You need this parameterless constructor for serialization to not return null
public Example()
{
}
public Example(int someVariable)
{
SomeVariable = someVariable;
}
}
what would you think about this @ShadauxCat?
@MFatihMAR this is working as expected and as designed. We can't deserialize without knowing how to construct the type, and the only way we can reasonably construct it is with a parameterless constructor since we have no parameters to pass in. What @ArgentumVir describes is how the C# language works, and we can't change it, and even if we could... If a class or struct has defined a constructor, then any default parameterless constructor we could provide would almost certainly be incorrect.
The answer here is that users simply need to make sure a parameterless constructor exists, in this case by defining it manually since they have provided another constructor. The new()
constraint on those methods is there intentionally... I'd say this is not ”will not fix”, but ”cannot fix” and probably also ”should not fix” even if we could since there's no solution I can imagine that we could reasonably be certain is correct.
I mean what do you think about minor docs update + erroring when no default ctor found? :)
Oh, sorry. Yes, taking another look at it, I realize this is actually a little different than what I thought it was.
This seems to be specific to RPC parameters, and the issue is that the ILPP code that's generating RPC instructions isn't properly checking the new()
constraint on the method it's emitting code for. As a result, the method's getting called, but it's unable to operate (invalid IL instructions), so the result ends up being null.
That's something we can (and should and will) fix.
@ShadauxCat, not urgent and also not triaged yet but I think it'd worth checking if we implemented RPC params new()
(default parameterless constructor) constraint check on the ILPP side and closing this one off. There might be a minor update on the docs side (as we mentioned before above) but I actually want to just leave all this on your judgement.
Describe the bug Custom class serialization without default constructor does not work
To Reproduce Steps to reproduce the behavior:
Actual outcome Foo is null
Expected outcome
Environment (please complete the following information):