Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# - Failed to serialize a class with nullable generic field inherited from base class #1024

Open
hhuberman opened this issue Feb 19, 2020 · 5 comments
Labels

Comments

@hhuberman
Copy link

hhuberman commented Feb 19, 2020

Having the following schema:

struct TestBase<T>
{
    0: nullable<T> Value;
}
struct Test : TestBase<double> { }

I'm getting the following exception when trying to serialize an instance of a Test object:

 System.InvalidOperationException: The binary operator NotEqual is not defined for the types 'System.Double' and 'System.Object'.
   at System.Linq.Expressions.Expression.GetEqualityComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull)
   at System.Linq.Expressions.Expression.NotEqual(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
   at System.Linq.Expressions.Expression.NotEqual(Expression left, Expression right)
   at Bond.Expressions.ObjectParser.Nullable(ContainerHandler handler)
   at Bond.Expressions.ObjectParser.Container(Nullable`1 expectedType, ContainerHandler handler)
   at Bond.Expressions.SerializerTransform`2.Container(IParser parser, RuntimeSchema schema)
   at Bond.Expressions.SerializerTransform`2.<>c__DisplayClass12_0.<GenerateSerialize>b__0(IParser p)
   at Bond.Expressions.SerializerGenerator`2.GenerateSerialize(Serialize serialize, IParser parser, ParameterExpression writer, Boolean inline)
   at Bond.Expressions.SerializerTransform`2.GenerateSerialize(SerializeWithSchema serializeWithSchema, IParser parser, RuntimeSchema schema)
   at Bond.Expressions.SerializerTransform`2.Value(IParser parser, Expression valueType, RuntimeSchema schema)
   at Bond.Expressions.SerializerTransform`2.<>c__DisplayClass15_1.<Struct>b__6(IParser fieldParser, Expression fieldType)
   at Bond.Expressions.Field.Bond.Expressions.IField.Value(IParser parser, Expression valueType)
   at Bond.Expressions.ObjectParser.Field(ITransform transform, Expression structVar, UInt16 id, ISchemaField schemaField, IField field)
   at Bond.Expressions.ObjectParser.<>c__DisplayClass21_0.<Apply>b__4(<>f__AnonymousType0`2 <>h__TransparentIdentifier0, IField knownField)
   at System.Linq.Enumerable.SelectManyIterator[TSource,TCollection,TResult](IEnumerable`1 source, Func`2 collectionSelector, Func`3 resultSelector)+MoveNext()
   at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection)
   at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection)
   at Bond.Expressions.ObjectParser.Apply(ITransform transform)
   at Bond.Expressions.SerializerTransform`2.Struct(IParser parser, RuntimeSchema schema, Boolean isBase)
   at Bond.Expressions.SerializerTransform`2.<>c__DisplayClass15_0.<Struct>b__4(IParser baseParser)
   at Bond.Expressions.Transform.Bond.Expressions.ITransform.Base(IParser parser)
   at Bond.Expressions.ObjectParser.Apply(ITransform transform)
   at Bond.Expressions.SerializerTransform`2.Struct(IParser parser, RuntimeSchema schema, Boolean isBase)
   at Bond.Expressions.SerializerTransform`2.Struct(IParser parser, RuntimeSchema schema)
   at Bond.Expressions.SerializerTransform`2.<>c__DisplayClass12_0.<GenerateSerialize>b__0(IParser p)
   at Bond.Expressions.SerializerGenerator`2.GenerateSerialize(Serialize serialize, IParser parser, ParameterExpression writer, Boolean inline)
   at Bond.Expressions.SerializerTransform`2.GenerateSerialize(SerializeWithSchema serializeWithSchema, IParser parser, RuntimeSchema schema)
   at Bond.Expressions.SerializerTransform`2.Generate(IParser parser)
   at Bond.Serializer`1.SerializerHelper..ctor(ObjectParser parser, Type type, Boolean inlineNested)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
   at System.Activator.CreateInstance(Type type, Object[] args)
   at Bond.Serializer`1..ctor(Type type, IParser parser, Boolean inlineNested)
   at Bond.Serializer`1..ctor(Type type)

However, it works if I change the schema to:

struct TestBase<T>
{
    0: T Value;
}

struct Test : TestBase<nullable<double>> { }
@chwarr
Copy link
Member

chwarr commented Feb 19, 2020

Interesting. It's getting confused somewhere by the generic, the nullable, and the nullable inner type.

(Somewhat related: Generation of C# serializer for a = nothing field with a default value fails with an obscure exception)

@chwarr chwarr added the bug label Feb 19, 2020
@katruno
Copy link

katruno commented Jul 20, 2020

I faced a similar problem. In my case I'm not using a generic value, instead I've a nullable field of an string alias:

using StrAlias = string;
struct Test
{
    0: nullable<StrAlias> Value;
}

StrAlias is defined as a struct (value type):

public struct StrAlias 
{
    public string Value { get; }
}

Serialization for Test object is failing with similar error:

Error: System.InvalidOperationException: The binary operator NotEqual is not defined for the types 'MyNamespace.StrAlias' and 'System.Object'.
at System.Linq.Expressions.Expression.GetEqualityComparisonOperator(ExpressionType binaryType, String opName, Expression left, Expression right, Boolean liftToNull)
at System.Linq.Expressions.Expression.NotEqual(Expression left, Expression right, Boolean liftToNull, MethodInfo method)
at Bond.Expressions.ObjectParser.Nullable(ContainerHandler handler) in D:\a\1\s\cs\src\core\expressions\ObjectParser.cs:line 376
at Bond.Expressions.DeserializerTransform`1.FieldValue(IParser parser, Expression var, Expression valueType, Type schemaType, Boolean initialize) in D:\a\1\s\cs\src\core\expressions\DeserializerTransform.cs:line 502
at Bond.Expressions.DeserializerTransform`1.<>c__DisplayClass18_2.b__3(IParser fieldParser, Expression fieldType) in D:\a\1\s\cs\src\core\expressions\DeserializerTransform.cs:line 222
at Bond.Expressions.ObjectParser.Field(ITransform transform, Expression structVar, UInt16 id, ISchemaField schemaField, IField field) in D:\a\1\s\cs\src\core\expressions\ObjectParser.cs:line 125
at Bond.Expressions.ObjectParser.<>c__DisplayClass21_0.b__4(<>f__AnonymousType0`2 <>h__TransparentIdentifier0, IField knownField) in D:\a\1\s\cs\src\core\expressions\ObjectParser.cs:line 88

@chwarr
Copy link
Member

chwarr commented Jul 20, 2020

These very well could be related. I wonder if it's the struct that's causing the expression generation to get confused. Nullable<Double> is itself a struct...

@katruno
Copy link

katruno commented Jul 21, 2020

By looking a little bit more at the code: https://github.com/microsoft/bond/blob/master/cs/src/core/expressions/ObjectParser.cs
and the expression that is throwing the exception:
ln 376: var notNull = Expression.NotEqual(nullableValue, Expression.Constant(null));

Could be possible that it is missing the specific type while creating the constant?
By default it used object but you can pass the specific type in an overload:
https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression.constant?view=netcore-3.1

@chwarr
Copy link
Member

chwarr commented Jul 22, 2020

Could very well be the case. I don't have time for the foreseeable future to add a regression test and attempt a fix. If you're interested, feel free to play around here and submit a PR if you come up with a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants