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

.net-core .net-core-rc2 c# expression-trees

Question

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?

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

Popular Answer

This is indeed a bug in the implementation of .NET Core. The reason is that certain APIs were not avaiable in .NET Core when System.Linq.Expressions was ported to core, so a custom implementation was developed and this was never caught.

I've sent a PR to dotnet/corefx to fix this. For the curious, the problem was that the method finding the operator loops through the methods, but breaks out of the loop when it finds a match, before checking that the method is the one we want. The fix is to move parameter checking inside the loop, e.g.

        internal static MethodInfo GetAnyStaticMethodValidated(
        this Type type,
        string name,
        Type[] types)
    {
        foreach (var method in type.GetRuntimeMethods())
        {
            if (method.IsStatic && method.Name == name && method.MatchesArgumentTypes(types))
            {
                return method;
            }
        }
        return null;
    }


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why