2010-10-28 12 views
19

Estoy escribiendo una clase que hace 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 un método para calcular el promedio de una serie de valores, p.Código genérico C# y el operador Plus

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 apoyar que la misma operación para el doble, flotador y tal vez otras clases que definen operador + y el operador /, mi primer pensamiento fue simplemente usar los 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# es incapaz de determinar si T admite operadores + y/o no compila el fragmento de arriba. Mi siguiente pensamiento fue restringir T a los tipos que admiten esos operadores, pero mi investigación inicial indica que esto no se puede hacer.

Sin duda, es posible encasillar cada uno de los tipos que deseo admitir en una clase que implemente una interfaz personalizada, p. IMath y restrinja T a eso, pero este código se llamará una gran cantidad de veces y quiero evitar la sobrecarga del boxeo.

¿Existe una manera elegante y eficiente de resolver esto sin duplicación de código?

+1

¿No puedes simplemente usar LINQ? Suma y promedio son compatibles con IEnumerable. – Mathias

+2

Consulte esta otra pregunta ASÍ: http://stackoverflow.com/questions/32664/c-generic-constraint-for-only-integers – spinon

+0

@Mathias: No, utilicé esto como un ejemplo simplificado del cálculo real. –

Respuesta

13

Terminé usando Expressions, un enfoque delineado por Marc Gravell que encontré al seguir enlaces fuera del comentario de spinon.

http://www.yoda.arachsys.com/csharp/genericoperators.html

+0

Solo asegúrese de guardar en la memoria caché a los delegados y volver a utilizarlos; p –

+3

Tenga en cuenta también que MiscUtils tiene una clase 'Operador' preconstruida que proporciona todos los métodos de utilidad necesarios que probablemente necesite. –

+0

@Marc: Sí, implementé su comentario ;-) –

2

Hay un enfoque dinámico utilizando en C# 4.0, no es perfecto, obviamente, pero puede traer una nueva luz a la materia.

detalles son in this blog post

+0

El problema con 'dinámico' es que introduce otro nivel de indirección. Estoy realizando una gran cantidad de cálculos complejos y sospecho (aunque no sé, para ser honesto) que la indirección tendrá un impacto mensurable en el rendimiento general de la aplicación. –

1

he encontrado otro enfoque interesante, que es más fácil de código y depuración de la solución árbol de expresión Solía ​​originalmente:

http://www.codeproject.com/KB/cs/genericnumerics.aspx

Esta solución utiliza restricciones de tipo genérico en una una forma interesante de garantizar que todas las operaciones requeridas sean compatibles, pero sin introducir ninguna llamada de boxeo o método virtual.

4

(perdón si lo pongo hoy, pero yo estaba buscando un lugar donde poner esta pieza de código, y esta cuestión parecía ser perfecto)

Como una extensión en el artículo de la 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(); 
    } 
} 

que lo utilice como:

int sum = Add<int>.Do(x, y); 

la ventaja es que utilizamos el sistema de tipos de .NET para su custodia las distintas "variantes" de Add y creat nuevos si es necesario. Por lo tanto, la primera vez que llame al Add<int>.Do(...) se compilará el Expression, pero si lo llama por segunda vez, el Add<int> ya estará completamente inicializado.

En un 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

método se puede ampliar con meta-codificación para que pueda manejar los casos de T1operaciónT2. Por ejemplo, aquí si T1 es un número, entonces debe convertirse a T2 == double primero antes de operator * y luego lo convierte de nuevo. Mientras que cuando T1 es Foo y Foo tiene operador para multiplicar con 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(); 
     } 
    } 
} 
+0

@Meirion No me gusta mucho lo que hiciste (agregar editando) ... Pero el código era claro. He vuelto a editar un poco y he cambiado un poco la explicación – xanatos

+0

Sí, lo sé, lo siento. Pensé que sería útil (era para mí), y estaba a punto de agregarlo como una respuesta separada, pero este hilo está bloqueado y el otro hilo estaría fuera de tema. :/ –

+0

@MeirionHughes Simplemente la próxima vez, ponga su nombre en la pieza de texto agregada, como lo hice * Adición de Meirion Hughes *, por lo que está claro lo que es del autor 1 y lo que es del autor 2 – xanatos