这是 MiscUtils 中的错误还是我遗漏了什么?
decimal a = (1M/30);
int b = 59;
Assert.AreEqual(a*b, Operator.MultiplyAlternative(a, b));
Assert.AreEqual(b*a, Operator.MultiplyAlternative(b, a));
最后一行失败:
expected: <1.9666666666666666666666666647>
but was: <0>
更新
正如 Petr 指出的那样,CreateExpression 方法中存在一些强制操作,从而导致了问题。使用编译器时,我们不会看到此类问题,因为参数被提升为具有最高精度的类型,这也成为返回类型。
无论如何,第二个“Assert”应该失败,因为如果与正常的 C# 行为一致,我希望它将第一个参数 (b) 提升为十进制
并执行操作。通常,如果我们想将结果存储到精度较低的类型的变量中,我们需要进行显式转换。但是,由于我们调用的方法的返回类型可能具有较低的精度,并且调用者已显式调用返回较低精度结果的方法 - 作为操作的一部分自动执行潜在的截断转换似乎是合理的。换句话说,第二个表达式的预期结果将是 1
。
因此,我们可以更改 CreateExpression 以反射(reflect)该行为,如下所示:
if (castArgsToResultOnFailure && !( // if we show retry
typeof(TArg1) == typeof(TResult) && // and the args aren't
typeof(TArg2) == typeof(TResult)))
{ // already "TValue, TValue, TValue"...
var ltc = Type.GetTypeCode(lhs.Type);
var rtc = Type.GetTypeCode(rhs.Type);
// Use the higher precision element
if (ltc > rtc)
{
// TArg1/TResult is higher precision than TArg2. Simply lift rhs
var castRhs = Expression.Convert(rhs, lhs.Type);
return
Expression.Lambda<Func<TArg1, TArg2, TResult>>(body(lhs, castRhs), lhs, rhs).Compile();
}
// TArg2 is higher precision than TArg1/TResult. Lift lhs and Cast result
var castLhs = Expression.Convert(lhs, rhs.Type);
var castResult = Expression.Convert(body(castLhs, rhs), lhs.Type);
return Expression.Lambda<Func<TArg1, TArg2, TResult>>(castResult, lhs, rhs).Compile();
}
因此需要重写第二个断言:
Assert.AreEqual((int)(b*a), Operator.MultiplyAlternative(b, a));
现在两个断言都成功了。和以前一样,根据参数的顺序,将返回不同的结果,但现在第二次调用会产生逻辑上正确的结果。
最佳答案
根据source code ,方法签名如下:
public static TArg1 MultiplyAlternative<TArg1, TArg2>(TArg1 value1, TArg2 value2)
返回类型与第一个参数相同。因此在第二种情况下它使用 int
作为返回类型,并且很可能也将第二个参数转换为该类型,因此您有 59*0
这是零。
关于您的评论,this part源代码提供详细信息:
/// <summary>
/// Create a function delegate representing a binary operation
/// </summary>
/// <param name="castArgsToResultOnFailure">
/// If no matching operation is possible, attempt to convert
/// TArg1 and TArg2 to TResult for a match? For example, there is no
/// "decimal operator /(decimal, int)", but by converting TArg2 (int) to
/// TResult (decimal) a match is found.
/// </param>
/// <typeparam name="TArg1">The first parameter type</typeparam>
/// <typeparam name="TArg2">The second parameter type</typeparam>
/// <typeparam name="TResult">The return type</typeparam>
/// <param name="body">Body factory</param>
/// <returns>Compiled function delegate</returns>
public static Func<TArg1, TArg2, TResult> CreateExpression<TArg1, TArg2, TResult>(
Func<Expression, Expression, BinaryExpression> body, bool castArgsToResultOnFailure)
{
ParameterExpression lhs = Expression.Parameter(typeof(TArg1), "lhs");
ParameterExpression rhs = Expression.Parameter(typeof(TArg2), "rhs");
try
{
try
{
return Expression.Lambda<Func<TArg1, TArg2, TResult>>(body(lhs, rhs), lhs, rhs).Compile();
}
catch (InvalidOperationException)
{
if (castArgsToResultOnFailure && !( // if we show retry
typeof(TArg1) == typeof(TResult) && // and the args aren't
typeof(TArg2) == typeof(TResult)))
{ // already "TValue, TValue, TValue"...
// convert both lhs and rhs to TResult (as appropriate)
Expression castLhs = typeof(TArg1) == typeof(TResult) ?
(Expression)lhs :
(Expression)Expression.Convert(lhs, typeof(TResult));
Expression castRhs = typeof(TArg2) == typeof(TResult) ?
(Expression)rhs :
(Expression)Expression.Convert(rhs, typeof(TResult));
return Expression.Lambda<Func<TArg1, TArg2, TResult>>(
body(castLhs, castRhs), lhs, rhs).Compile();
}
else throw;
}
}
catch (Exception ex)
{
string msg = ex.Message; // avoid capture of ex itself
return delegate { throw new InvalidOperationException(msg); };
}
}
你看,Expression.Lambda<Func<TArg1, TArg2, TResult>>(body(lhs, rhs), lhs, rhs).Compile();
TArg1
的任意组合均失败和TArg2
的int
和decimal
(例如,如果将 decimal
更改为 double
,它实际上具有相同的行为,因此它不仅 decimal
受到影响),然后 catch
block 试图将两个参数转换为 TResult
类型,而这又取决于参数顺序。这样你就得到了所有decimal
投第一种情况和所有int
排在第二位。
不幸的是,我无法回答为什么这个 lambda 编译实际上失败了,这是一个错误还是语言限制。
关于c# - 这是 MiscUtils 中的错误吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20138124/