c# - remarks - tipos de datos genericos en java
Código C#genérico y el operador Plus (4)
Esta pregunta ya tiene una respuesta aquí:
Estoy escribiendo una clase que realiza esencialmente el mismo tipo de cálculo para cada uno de los tipos numéricos primitivos en C #. Aunque el cálculo real es más complejo, considérelo como un método para calcular el promedio de un número de valores, por ejemplo,
class Calc
{
public int Count { get; private set; }
public int Total { get; private set; }
public int Average { get { return Count / Total; } }
public int AddDataPoint(int data)
{
Total += data;
Count++;
}
}
Ahora para admitir esa misma operación para clases dobles, flotantes y quizás otras que definen operador + y operador /, mi primer pensamiento fue simplemente usar genéricos:
class Calc<T>
{
public T Count { get; private set; }
public T Total { get; private set; }
public T Average { get { return Count / Total; } }
public T AddDataPoint(T data)
{
Total += data;
Count++;
}
}
Desafortunadamente, C # no puede determinar si T admite operadores + y / así que no compila el fragmento de código anterior. Mi siguiente pensamiento fue restringir la T a los tipos que admiten esos operadores, pero mi investigación inicial indica que esto no se puede hacer.
Ciertamente es posible marcar cada uno de los tipos que quiero admitir en una clase que implementa una interfaz personalizada, por ejemplo, IMath y restringir T a eso, pero este código se llamará una gran cantidad de veces y quiero evitar los gastos generales de boxeo.
¿Existe una manera elegante y eficiente de resolver esto sin duplicación de código?
(disculpe si lo publico hoy, pero estaba buscando un lugar donde colocar este fragmento de código, y esta pregunta parecía ser perfecta)
Como extensión del artículo de Gravell:
public static class Add<T>
{
public static readonly Func<T, T, T> Do;
static Add()
{
var par1 = Expression.Parameter(typeof(T));
var par2 = Expression.Parameter(typeof(T));
var add = Expression.Add(par1, par2);
Do = Expression.Lambda<Func<T, T, T>>(add, par1, par2).Compile();
}
}
Lo usas como:
int sum = Add<int>.Do(x, y);
La ventaja es que usamos el sistema de tipos de .NET para proteger las diversas "variantes" de Add
y crear otras nuevas si es necesario. Entonces, la primera vez que llame a Add<int>.Do(...)
se construirá la Expression
, pero si la llama por segunda vez, Add<int>
ya estará completamente inicializado.
En algún punto de referencia simple, es 2 veces más lento que la adición directa. Creo que es muy bueno. Ah ... es compatible con objetos que redefinen el operator+
. Claramente construir las otras operaciones es fácil.
Adición de Meirion Hughes
El método se puede ampliar con la meta-codificación para que pueda manejar los casos de la operación T1
T2
. Por ejemplo, aquí si T1
es un número, entonces se debe convertir a T2 == double
antes de que el operator *
lo vuelva a convertir. Mientras que T1
es Foo
y Foo
tiene un operador para multiplicar con un T2 == double
, puede omitir la conversión. El try
catch
es necesario porque es la forma más fácil de verificar si el T operator *(T, double)
está presente.
public static class Scale<T>
{
public static Func<T, double, T> Do { get; private set; }
static Scale()
{
var par1 = Expression.Parameter(typeof(T));
var par2 = Expression.Parameter(typeof(double));
try
{
Do = Expression
.Lambda<Func<T, double, T>>(
Expression.Multiply(par1, par2),
par1, par2)
.Compile();
}
catch
{
Do = Expression
.Lambda<Func<T, double, T>>(
Expression.Convert(
Expression.Multiply(
Expression.Convert(par1, typeof (double)),
par2),
typeof(T)),
par1, par2)
.Compile();
}
}
}
Encontré otro enfoque interesante, que es más fácil de codificar y depurar que la solución del árbol de expresiones que usé originalmente:
http://www.codeproject.com/KB/cs/genericnumerics.aspx
Esta solución utiliza restricciones de tipo genérico de una manera interesante para garantizar que todas las operaciones requeridas sean compatibles, pero sin introducir ningún método de boxeo o de método virtual.
Hay un enfoque que utiliza la dinámica en C # 4.0, obviamente no es perfecto, pero puede aportar una nueva luz al asunto.
Los detalles están en esta entrada del blog.
Terminé usando Expresiones, un enfoque descrito por Marc Gravell que encontré siguiendo los enlaces del comentario de spinon.