multiplicar - operaciones basicas en c# consola
¿Cómo hacer que el operador de multiplicación(*) se comporte como cortocircuito? (4)
Tengo muchos cálculos, especialmente la multiplicación, donde la primera parte a veces es cero y no quiero evaluar el segundo operando en ese caso. Hay al menos dos operadores de cortocircuito en C #: &&
y ||
que evalúan el segundo operando solo si es necesario. Quiero lograr un comportamiento similar con el operador de multiplicación.
En .net no puede sobrecargar directamente a &&
operador, pero puede sobrecargar y false
operadores, por lo que puede usar sus puntos de extensión para cambiar el comportamiento del operador de cortocircuito. Puede encontrar más detalles en este artículo Sobrecarga del operador de C #: el operador ''&&''
¿Hay algún medio para lograr este o similar comportamiento para el operador de multiplicación?
Esta es una pregunta de sintaxis pura, porque la implementación es muy simple. El siguiente método logra exactamente lo que quiero en términos de funcionalidad:
public static double ShortCircuitMultiply(double val, Func<double> anotherValue)
{
var epsilon = 0.00000001;
return Math.Abs(val) < epsilon ? 0 : val * anotherValue();
}
Nota: esta implementación no está completa: en C # si multiplicas 0.0
con Double.NaN
o Double.NegativeInfinity
o Double.PositiveInfinity
, obtendrás NaN
, pero en términos de ShortCircuitMultiply
- solo cero. Ignoremos este detalle y es realmente irrelevante en mi dominio.
Entonces, si lo llamo ShortCircuitMultiply(0.0, longOperation)
donde longOperation
es Func<double>
, el último término no se evaluará y el resultado de una operación será efectivamente cero.
El problema es que, como ya dije, tendría muchas llamadas ShortCircuitMultiply
y quiero hacer el código más legible. Quiero que el código sea similar a 0.0 * longOperation()
si eso es posible.
Otra nota: he intentado crear una envoltura al double
y crear la conversión implícita para duplicar y sobrecargar al operador *
. Entiendo que esto es probablemente redundante: quería lograr la legibilidad, pero tratando de construir otro envoltorio . De todos modos, el siguiente código demuestra mi intención:
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);
}
}
Ahora si voy con:
MyDouble zero = 0;
Console.WriteLine (zero * longOperation()); //longOperation is still Func<double>
He recibido:
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
Pero como puede ver, longOperation
se evalúa mucho antes de llamar al operador sobrecargado, y no puedo sustituir uno de los parámetros con Func
o Expression
para hacerlo perezoso.
Bueno, podrías escribir un método de extensión para el doble, pero no estoy seguro de si realmente es lo que estás buscando.
Entonces podrías tener un código como este:
double z = someNumberThatMightBeZero();
double r = z.Times(number);
donde number
es un método que devuelve un doble.
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();
}
}
}
El problema con su clase de envoltorio MyDouble
es que lo usa llamando directamente a longOperation
. Como *
no es cortocircuito, se llamará directamente.
En su lugar, simplemente podría hacer que su envoltorio acepte un Func<double>
como segundo parámetro en lugar del doble valor en sí mismo. Así que funcionaría como la función ShortCircuitMultiply
:
public static MyDouble operator *(MyDouble left, Func<double> right)
{
return Math.Abs(left.value) < epsilon ? new MyDouble(0) : new MyDouble(left.value * right());
}
Entonces lo usarías así:
MyDouble x = 0;
Console.WriteLine(x * LongOperation);
E incluso el encadenamiento funciona:
MyDouble x = 5;
Console.WriteLine(x * OperationReturingZero * LongOperation);
Ejemplo completo
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);
}
}
Salida:
cast to MyDouble
* (MyDouble, Func<double>)
cast to double
0
cast to MyDouble
* (MyDouble, Func<double>)
OperationReturningZero
* (MyDouble, Func<double>)
cast to double
0
Lo más cercano que puedo ver es algo como esto:
struct LazyDouble {
Func<double> Func;
public LazyDouble(Func<double> func) : this() { Func = func; }
public static implicit operator LazyDouble (double value) {
return new LazyDouble(()=>value);
}
public static LazyDouble operator * (LazyDouble lhs, LazyDouble rhs) {
var lhsValue = lhs.Func();
if ( lhsValue == 0)
return 0;
else
return new LazyDouble(() => lhsValue * rhs.Func());
}
// other operators as necessary
}
No hay manera de hacer fácilmente lo que quieres. El lenguaje C # es un lenguaje muy "ávido" en el sentido de que siempre evalúa los operandos antes de que los operadores se ejecuten, incluso cuando, como se observa, posiblemente pueda omitir uno conociendo el otro. Las únicas excepciones son ? :
? :
, y sus equivalentes, &&
, ||
y ??
. (Todo lo cual puede reducirse a ? :
.)
Como nota correctamente, puede lograr la pereza mediante el uso de un lambda; una función Func<T>
representa una T
que se calculará según la demanda. Pero como también lo señala correctamente, la sintaxis para hacerlo es bastante pesada.
Considera escribir tu programa en Haskell si debes tener aritmética perezosa. Es muy perezoso, y creo que es muy fácil definir la semántica de su propio operador. F # también es una opción y probablemente sea más fácil de aprender para el programador de C #.