Steven Jeuris Steven Jeuris - 1 year ago 54
C# Question

Difference in Expression.Subtract for DateTime between .NET Core and .NET Framework

While converting my

.NET 4.5
library to
.NETStandard v1.6
I ran into a failing unit test which used to pass before.

I pinpointed the problem to the following three lines of code:

ParameterExpression arg1 = Expression.Parameter( typeof( DateTime ), "arg1" );
ParameterExpression arg2 = Expression.Parameter( typeof( DateTime ), "arg2" );
var test = Expression.Subtract( arg1, arg2 );

This expression tree compiles for
.NET 4.5
, but throws an
.NETStandard v1.6

The binary operator Subtract is not defined for the types
'System.DateTime' and 'System.DateTime'.

However, for both targets the following code works:

DateTime one = new DateTime();
DateTime two = new DateTime();
TimeSpan difference = one - two;

I thus would expect the expression trees to compile for .NET Core as well? Am I doing something wrong, or is this a bug in .NET Core?

Answer Source

It's a bug in System.Linq.Expressions assembly.

These methods are used to find the Subtract operator method:

public static MethodInfo GetAnyStaticMethodValidated(this Type type, string name, Type[] types)
    // Method name is "op_Subtraction" in your case
    MethodInfo anyStaticMethod = type.GetAnyStaticMethod(name);
    // DateTime and DateTime in your case
    if (!anyStaticMethod.MatchesArgumentTypes(types))
        return null;
    return anyStaticMethod;

public static MethodInfo GetAnyStaticMethod(this Type type, string name)
    foreach (MethodInfo current in type.GetRuntimeMethods())
        if (current.IsStatic && current.Name == name)
            return current;
    return null;

As you see, the GetAnyStaticMethod picks randomly the first "op_Subtraction" method from DateTime, instead of looping through all available, where DateTime has two of such operator methods:

public static DateTime operator -(DateTime d, TimeSpan t);
public static TimeSpan operator -(DateTime d1, DateTime d2);

So the code picks the wrong one that takes in DateTime and TimeSpan, then just fails because input types don't match.

In .NET 4.5 they do search in a proper way by passing argument types:

Type[] types = new Type[]
    leftType, // DateTime in your case
    rightType // DateTime in your case
BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
// Method name is "op_Subtraction" in your case
MethodInfo methodInfo = nonNullableType.GetMethodValidated(name, bindingAttr, null, types, null);