2012-06-08 10 views
7

¿Es posible implementar aritmética básica (al menos adición) en genéricos C#, como puede with C++ templates? He intentado durante un tiempo ponerlos en funcionamiento, pero C# no te permite declarar el mismo tipo genérico varias veces, como lo haces con las plantillas.¿Implementando la aritmética en genéricos?

La búsqueda extensa en Google no proporcionó una respuesta.

EDITAR: Gracias, pero lo que estoy buscando es una forma de hacer la aritmética en tiempo de compilación, incorporando algo así como Números de la Iglesia en tipos genéricos. Es por eso que vinculé el artículo que hice. Aritmética en tipos genéricos, no aritmética en instancias de tipos genéricos.

+3

Desafortunadamente, el tipo de restricción no le permite a exigir que el tipo es compatible con los operadores de Aritmética. Lo que me parece interesante es que en el código fuente BCL para, p. Ej. 'Int32' encontrará una interfaz' IArithmetic 'en la lista de herencia que está comentada. Esto es pura especulación por mi parte, pero si Microsoft habilitó esa interfaz en el BCL, entonces quizás podría especificar 'IArithmetic ' como una restricción que le permita escribir sus propias clases genéricas con aritmética. –

+0

Enlace a una pregunta similar: http://stackoverflow.com/q/4039694/613130 ​​ – xanatos

Respuesta

4

Lamentablemente no se puede utilizar operaciones aritméticas sobre tipos genéricos

T Add(T a, T b) 
{ 
    return a + b; // compiler error here 
} 

no funcionará Cª#!

Pero puede crear sus propios tipos numéricos y sobrecargar los operadores (igualdad aritmética y implicit, explicit). Esto le permite trabajar con ellos de una manera bastante natural. Sin embargo, no puede crear una jerarquía de herencia con genéricos. Tendrá que usar una clase base o interfaz no genérica.

Lo acabo de hacer con un tipo de vector.Una versión abreviada aquí:

public class Vector 
{ 
    private const double Eps = 1e-7; 

    public Vector(double x, double y) 
    { 
     _x = x; 
     _y = y; 
    } 

    private double _x; 
    public double X 
    { 
     get { return _x; } 
    } 

    private double _y; 
    public double Y 
    { 
     get { return _y; } 
    } 

    public static Vector operator +(Vector a, Vector b) 
    { 
     return new Vector(a._x + b._x, a._y + b._y); 
    } 

    public static Vector operator *(double d, Vector v) 
    { 
     return new Vector(d * v._x, d * v._y); 
    } 

    public static bool operator ==(Vector a, Vector b) 
    { 
     if (ReferenceEquals(a, null)) { 
      return ReferenceEquals(b, null); 
     } 
     if (ReferenceEquals(b, null)) { 
      return false; 
     } 
     return Math.Abs(a._x - b._x) < Eps && Math.Abs(a._y - b._y) < Eps; 
    } 

    public static bool operator !=(Vector a, Vector b) 
    { 
     return !(a == b); 
    } 

    public static implicit operator Vector(double[] point) 
    { 
     return new Vector(point[0], point[1]); 
    } 

    public static implicit operator Vector(PointF point) 
    { 
     return new Vector(point.X, point.Y); 
    } 

    public override int GetHashCode() 
    { 
     return _x.GetHashCode()^_y.GetHashCode(); 
    } 

    public override bool Equals(object obj) 
    { 
     var other = obj as Vector; 
     return other != null && Math.Abs(other._x - _x) < Eps && Math.Abs(other._y - _y) < Eps; 
    } 

    public override string ToString() 
    { 
     return String.Format("Vector({0:0.0000}, {1:0.0000})", _x, _y); 
    } 
} 
+0

¿Qué quiere decir con "restricciones' struct' no están permitidas "? –

+0

Usted realmente puede. Consulte [Restricciones sobre los parámetros de tipo (Guía de programación de C#)] (http://msdn.microsoft.com/en-us/library/d5x73970.aspx). –

+0

@AdamHouldsworth: ¡Structs puede implementar interfaces! Dado que las estructuras no pueden heredarse entre sí, restringir a un determinado tipo de estructura sería lo mismo que no tener un parámetro genérico (¡o tendría exactamente un parámetro de tipo genérico válido, a saber, esta estructura única)! –

3

Por favor, siéntase libre de ofrecer más aclaraciones si mi respuesta parece desviada.

No hay restricciones genéricas en los operadores en el lenguaje C#, al menos. Como Jon Skeet ha demostrado con Unconstrained Melody, las restricciones podrían ser perfectamente válidas en el CLR.

Lo mejor que puede hacer con las limitaciones es proporcionar interfaces/clases personalizadas que expongan las acciones que necesita. No podría proporcionar la primitiva (a menos que también implemente el operador implicit), pero al menos le permitiría crear un código genérico para la parte matemática.

Las restricciones genéricas permiten al compilador inferir los miembros disponibles en función del mínimo común denominador (según lo especificado por la restricción o la falta de). La mayoría de las veces, los genéricos no tienen restricciones y, por lo tanto, le dan solo object semántica.


Alternativamente, evitar el uso de restricciones y utilizar dynamic para almacenar temporalmente la variable genérica y luego hacer la suposición (a través de la tipificación de pato) que tiene los operadores relevantes:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var result = Add<int, long, float>(1, 2); 
     Console.WriteLine(result); // 3 
     Console.WriteLine(result.GetType().FullName); // System.Single 
     Console.Read(); 
    } 

    static T3 Add<T1, T2, T3>(T1 left, T2 right) 
    { 
     dynamic d1 = left; 
     dynamic d2 = right; 
     return (T3)(d1 + d2); 
    } 
} 

Esto implica el DLR y tendrá algunos gastos generales de rendimiento (no tengo cifras exactas), especialmente si tiene la intención de que los cálculos sean críticos para el rendimiento.


no estoy seguro de lo que quiere decir "declara el mismo tipo genérico varias veces", esto funciona:

class Tuple<T1, T2> // etc. 

var myTuple = new Tuple<int, int>(1, 2); 
0

Amigos, la respuesta intuitiva a esto en C# es RTTI y echando hacia atrás y adelante de la clase de objeto

enter code here 

class MyMath 
{ 
    public static T Add<T>(T a, T b) where T: struct 
    { 
     switch (typeof(T).Name) 
     { 
      case "Int32": 
       return (T) (object)((int)(object)a + (int)(object)b); 
      case "Double": 
       return (T)(object)((double)(object)a + (double)(object)b); 
      default: 
       return default(T); 
     } 
    } 
} 

class Program 
{ 
    public static int Main() 
    { 
     Console.WriteLine(MyMath.Add<double>(3.6, 2.12)); 
     return 0; 
    } 
} 
+0

Soy un principiante en C# y cualquier explicación simple es importante; por lo tanto, si alguien vota menos, ¡agradecería comentar sobre eso! Ahora, ¿podría alguien (más) explicar por qué esta respuesta recibió un voto negativo? ¿Por qué este enfoque no es correcto? – Celdor

+1

@Celdor: primero, va a ser lento, porque estás haciendo RTTI y comparando cadenas. En segundo lugar, no es realmente genérico si tengo que agregar cada tipo manualmente ... –

+1

Recientemente, el jit ha introducido optmization detectando typeof (T) y eliminando ramas muertas en función de las condiciones RTTI. Esta solución, con las modificaciones adecuadas, puede ejecutarse más rápido que otras respuestas. – Luca

-1

sí, se puede hacer mediante el uso de variables de tipo dinámico .

ejemplo:

T Add(T value1, T value2) 
{   
     dynamic a = value1; 
     dynamic b = value2; 
     return (a + b); 
} 

para más referencia por favor click here