如何使乘法运算符(*)的行为如此短
我有很多计算,特别是乘法,其中第一部分有时是零,我不想在这种情况下评估第二个操作数。 C#中至少有两个短路操作符: &&
和||
仅在必要时才评估第二个操作数。 我想用乘法运算符来实现类似的行为。
在.net中,不能直接重载&&
操作符,但是可以重载&
和false
操作符,因此可以使用扩展点来改变短路操作符的行为。 你可以在这篇文章中找到更多的细节C#操作符重载:'&&'操作符
是否有任何方法来实现乘法运算符的这种或类似行为?
这是一个纯粹的语法问题,因为实现非常简单。 下一个方法在功能方面达到了我想要的水平:
public static double ShortCircuitMultiply(double val, Func<double> anotherValue)
{
var epsilon = 0.00000001;
return Math.Abs(val) < epsilon ? 0 : val * anotherValue();
}
注意:这个实现并不是完整的:在C#中,如果你将0.0
与Double.NaN
或Double.NegativeInfinity
或Double.PositiveInfinity
,你会得到NaN
,但是就ShortCircuitMultiply
- 只有零。 让我们忽略这个细节,这在我的领域中是无关紧要的。
所以现在如果我将它称为ShortCircuitMultiply(0.0, longOperation)
,其中longOperation
是Func<double>
,则不会评估上一项,并且操作的结果将实际为零。
正如我已经说过的那样,问题是我会有很多ShortCircuitMultiply
调用,并且希望使代码更具可读性。 我希望代码类似于0.0 * longOperation()
如果可能的话。
另一个说明:我试图在double
上构建包装,并创建隐式转换以加倍并重载*
运算符。 我明白,这可能是多余的:我想达到可读性,但试图建立另一个包装。 无论如何,下面的代码表明了我的意图:
class MyDouble
{
double value;
public MyDouble(double value)
{
this.value = value;
}
public static MyDouble operator *(MyDouble left, MyDouble right)
{
Console.WriteLine ("* operator call");
return new MyDouble(left.value * right.value);
}
public static implicit operator double(MyDouble myDouble)
{
Console.WriteLine ("cast to double");
return myDouble.value;
}
public static implicit operator MyDouble(double value)
{
Console.WriteLine ("cast to MyDouble");
return new MyDouble(value);
}
}
现在,如果我去:
MyDouble zero = 0;
Console.WriteLine (zero * longOperation()); //longOperation is still Func<double>
我已收到:
cast to MyDouble
called longOperation <-- want to avoid this (it's printed from longOperation body)
cast to double
cast to MyDouble
* operator call
cast to double
0
但是,正如你所看到的, longOperation
在重载运算符被调用之前longOperation
被评估过了,我不能用Func
或Expression
来替换其中的一个参数来使其懒惰。
你的MyDouble
包装类的问题是你通过直接调用longOperation
使用它。 由于*
不是短路,它将被直接调用。
相反,你可以让你的包装接受一个Func<double>
作为第二个参数,而不是双重值本身。 所以它可以像ShortCircuitMultiply
函数一样工作:
public static MyDouble operator *(MyDouble left, Func<double> right)
{
return Math.Abs(left.value) < epsilon ? new MyDouble(0) : new MyDouble(left.value * right());
}
那么你会这样使用它:
MyDouble x = 0;
Console.WriteLine(x * LongOperation);
甚至连锁作品:
MyDouble x = 5;
Console.WriteLine(x * OperationReturingZero * LongOperation);
完整的例子
class Program
{
static void Main()
{
MyDouble x = 0;
Console.WriteLine(x * LongOperation);
MyDouble y = 5;
Console.WriteLine(y * OperationReturningZero * LongOperation);
Console.ReadLine();
}
private static double LongOperation()
{
Console.WriteLine("LongOperation");
return 5;
}
private static double OperationReturningZero()
{
Console.WriteLine("OperationReturningZero");
return 0;
}
}
class MyDouble
{
private static double epsilon = 0.00000001;
private double value;
public MyDouble(double value)
{
this.value = value;
}
public static MyDouble operator *(MyDouble left, Func<double> right)
{
Console.WriteLine("* (MyDouble, Func<double>)");
return Math.Abs(left.value) < epsilon ? new MyDouble(0) : new MyDouble(left.value * right());
}
public static MyDouble operator *(MyDouble left, MyDouble right)
{
Console.WriteLine("* (MyDouble, MyDouble)");
return new MyDouble(left.value * right.value);
}
public static implicit operator double(MyDouble myDouble)
{
Console.WriteLine("cast to double");
return myDouble.value;
}
public static implicit operator MyDouble(double value)
{
Console.WriteLine("cast to MyDouble");
return new MyDouble(value);
}
}
输出:
cast to MyDouble
* (MyDouble, Func<double>)
cast to double
0
cast to MyDouble
* (MyDouble, Func<double>)
OperationReturningZero
* (MyDouble, Func<double>)
cast to double
0
没有办法轻松做到你想要的。 C#语言是一种非常“热切”的语言,因为它在运行操作符之前总是对操作数进行评估,即使正如您所注意的那样,您可能会通过了解其他语言而忽略它。 唯一的例外是? :
? :
,及其等价物, &&
, ||
和??
。 (所有这些都可以缩减为? :
。
正如你正确地指出的那样,你可以通过使用lambda达到懒惰; Func<T>
表示将根据需要计算的T
但正如你也正确地指出,这样做的语法是相当重量级的。
考虑在Haskell中编写程序,如果你必须使用懒算术。 这是非常懒惰的,我收集它很容易定义你自己的操作符语义。 F#也是一个选项,对于C#程序员来说可能更容易学习。
那么你可以写一个扩展方法来加倍,但我不确定它是否真的是你想要的。
你可以有这样的代码:
double z = someNumberThatMightBeZero();
double r = z.Times(number);
其中number
是一个返回double的方法。
using System;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
double z = zero();
double r = z.Times(number);
Console.WriteLine(r);
}
static double zero()
{
return 0;
}
static double number()
{
Console.WriteLine("in number()");
return 100;
}
}
public static class DoubleExt
{
public static double Times(this double val, Func<double> anotherValue)
{
const double epsilon = 0.00000001;
return Math.Abs(val) < epsilon ? 0 : val * anotherValue();
}
}
}
链接地址: http://www.djcxy.com/p/75161.html
上一篇: How to make multiplication operator (*) behave as short