Steven Jeuris Steven Jeuris - 2 months ago 12
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
InvalidOperationException
in
.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

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);
Comments