2010-12-01 8 views
22

Recientemente encontré el problema, quería que una función funcionara en dobles y enteros y me pregunté por qué no hay una interfaz común para todos los tipos de números (que contiene operadores aritméticos y comparaciones).¿Por qué los tipos de números no comparten una interfaz común?

Haría que las funciones de escritura como Math.Min (que existen en una gran cantidad de sobrecargas) sean más convenientes.

¿Introducir una interfaz adicional supondría un cambio radical?

Editar: pienso en el uso de este como

public T Add<T>(T a, T b) where T: INumber 
{ 
    return a+b; 
} 

o

public T Range<T>(T x, T min, T max) where T:INumber 
{ 
    return Max(x, Min(x, max), min); 
} 
+0

@Saeed: Eso es el punto. =) Mi método Add up es solo un simple ejemplo. En mi caso, estoy haciendo un análisis estadístico básico sobre una matriz dada de números, y no me importa si son int, doble, decimal o BigIntegers, siempre que pueda agregarlos. (y no tiene que escribir este método en muchas sobrecargas) – Jens

+0

https://referencesource.microsoft.com/#mscorlib/system/int32.cs,31 - 'IArithmetic ' parece estar comentada, por desgracia :( – Caramiriel

Respuesta

8

Si usted quiere hacer este tipo de aritmética "genéricas" de su opción, en un lenguaje fuertemente tipado como C# son bastante limitadas. Marc Gravell described the problem as follows:

.NET 2.0 introdujo los genéricos en el mundo .NET, lo que abrió la puerta a muchas soluciones elegantes para los problemas existentes. Las restricciones genéricas se pueden usar para restringir los argumentos de tipo a interfaces conocidas, etc., para garantizar el acceso a la funcionalidad; o para simples pruebas de igualdad/desigualdad, los Comparer<T>.Default y EqualityComparer<T>.Default implementan IComparer<T> y IEqualityComparer<T> respectivamente (lo que nos permite ordenar elementos, por ejemplo, sin tener saber algo sobre la "T" en cuestión).

Con todo esto, todavía hay una gran brecha en lo que respecta a los operadores. Debido a que los operadores se declaran como métodos estáticos, no hay IMath<T> o una interfaz similar equivalente implementada por todos los tipos numéricos; y de hecho, la flexibilidad de los operadores haría que esto fuera muy difícil de hacer de una manera significativa. Peor aún: muchos de los operadores en tipos primitivos ni siquiera existen como operadores; en cambio, hay métodos IL directos. Para hacer que la situación sea aún más compleja, Nullable <> exige el concepto de "operadores levantados", donde la "T" interior describe los operadores aplicables al tipo anulable, pero esto se implementa como una función de idioma y no lo proporciona el tiempo de ejecución (haciendo que la reflexión sea aún más divertida).

Sin embargo, C# 4.0 introdujo la palabra clave dynamic que se puede utilizar para elegir la sobrecarga correcta en tiempo de ejecución:

using System; 

public class Program 
{ 
    static dynamic Min(dynamic a, dynamic b) 
    { 
     return Math.Min(a, b);   
    } 

    static void Main(string[] args) 
    { 
     int i = Min(3, 4); 
     double d = Min(3.0, 4.0); 
    } 
} 

Debe tener en cuenta que esto elimina seguridad de tipos y puede obtener excepciones en tiempo de ejecución si el tiempo de ejecución dinámico no puede encontrar una sobrecarga adecuada para llamar, por ejemplo porque mezcló tipos.

Si desea obtener la seguridad de tipo, es posible que desee consultar las clases disponibles en la biblioteca MiscUtil que proporciona operadores genéricos para operaciones básicas.

Tenga en cuenta que si solo está realizando operaciones específicas, es posible que utilice las interfaces que los tipos incorporados ya implementan. Por ejemplo, un genérico función de tipo seguro Min podría tener este aspecto:

public static T Min<T>(params T[] values) where T : IComparable<T> 
{ 
    T min = values[0]; 
    foreach (var item in values.Skip(1)) 
    { 
     if (item.CompareTo(min) < 0) 
      min = item; 
    } 
    return min; 
} 
+2

Su generalización es un buen enfoque, pero no es compatible, por ejemplo, con el método específico de 'Agregar', solo es útil para acciones comparables, no para todas las acciones posibles en números, y el enfoque dinámico no es bueno debido a su velocidad. –

+0

Usar la honestidad de forma dinámica es el ejemplo más simple aquí. Sin embargo, si no garantiza que solo está pasando tipos numéricos en su diseño, tendrá un accidente desagradable. Aunque un error de diseño como este probablemente sea un grave error de rotura y se encuentre en las pruebas de la unidad de todos modos. – rolls

1

Bueno, no se puede definir operadores en las interfaces de todos modos y estructuras (a pesar de que soportan las interfaces) wouldn No funciona bien a través de implementaciones de interfaz, ya que esto requeriría boxeo y unboxing, lo que por supuesto sería un gran golpe de rendimiento cuando se realizan operaciones matemáticas solo a través de implementaciones de interfaz.

También destacaría que cuando lanzas una estructura a su interfaz, el objeto resultado es un tipo de referencia (un objeto en caja), que realiza operaciones en, no la propia estructura original:

interface IDoSomething 
{ 
    void DoSomething(); 
} 

struct MyStruct : IDoSomething 
{ 
    public MyStruct(int age) 
    { 
    this.Age = age; 
    } 

    public int Age; 

    pubblic void DoSomething() 
    { 
    Age++; 
    } 
} 

public void DoSomething(IDoSomething something) 
{ 
    something.DoSomething(); 
} 

Cuando Paso en mi instancia de mi estructura, su caja (se convierte en un tipo de referencia) en la que realizo mi operación DoSomething, pero mi instancia original de mi estructura no cambiará.

+0

En realidad, funcionará, siempre y cuando 'lance' a una interfaz, la estructura estará encuadrada. – leppie

+0

Vea mi respuesta, para lo que quiero decir. – leppie

+0

Estoy lanzando a una interfaz cuando llamo al 'DoSomething (algo le sucede algo) 'método. –

-3

Esa es la característica principal de un "lenguaje fuertemente tipado". Esto es algo que evita miles de millones de errores por minuto. Por supuesto, queremos que int sea totalmente diferente como doble.

+2

Con diferentes clases implementar una interfaz común aún deja todo fuertemente escrito. C# usa muchas interfaces en muchos lugares. Mi pregunta es más de: ¿Por qué? es obvio que falta uno? – Jens

+0

Como se respondió a sí mismo, las plantillas le permiten hacer lo mismo en objetos similares. Si Math.Min está tan sobrecargado, es solo para evitar el uso de plantillas, y tal vez establecer la micro-optimización para tipos especiales. –

+2

Tenga en cuenta que C# no tiene plantillas. Tiene genéricos, que es algo diferente. –

4

i.e ¿Cómo hacer 2 + 2.35? devuelve 4 o 4.35 o 4.349999? ¿Cómo entiende la interfaz cuál es la salida apropiada? Puede escribir su método de extensión y usar sobrecarga para resolver su problema, pero si queremos tener una interfaz para todos los propósitos cuánto tiempo será el tamaño de la interfaz y encontrar una función útil es difícil, también la interfaz agrega algunos gastos generales y los números son generalmente la base de cálculo así que necesita una forma rápida.

pienso escribir una clase de extensión es mejor en su caso:

public static class ExtensionNumbers 
{ 
    public static T Range<T>(this T input, T min, T max) where T : class 
    { 
     return input.Max(input.Min(max), min); 
    } 

    public static T Min<T>(this T input, params T[] param) where T : class 
    { 
     return null; 
    } 

    private static T Max<T>(this T input, params T[] number) where T : class 
    { 
     return null; 
    }  

} 

Solía ​​where T : class sólo para estar compilar

+1

No lo necesito en mi entendimiento. Ver mi pregunta editada. T hay int, o doble, pero no ambos, ¿no? – Jens

+0

Creo que todos los operadores están disponibles para todos los números, y agregar la interfaz solo agrega un poco de sobrecarga, la interfaz es útil para administrar algo. Los números se administran en mente del desarrollador antes de aprender a programar. –

+0

Ahh ... Veo lo que quiere decir ahora ... y la interfaz como 'Public INumber Add (INumber a, INumber b);' de hecho conduciría al problema que describe ... déjenme pensar en eso =) – Jens

0

El problema es que en la arquitectura de diferentes tipos de números cómo los números se encuentran almacenadas tratado fundimentaly de manera diferente. Para empezar, su verborrea es incorrecta y las interfaces no funcionarán, pero lo que creo que ustedes quieren decir es que quieren que los números se tipeen libremente.

Para empezar, por qué no le gustaría hacer esto, considere que los tipos enteros son un mapeo uno a uno en el rango de valores que pueden representar mientras que los tipos de punto flotante tienen un componente de persisión y exponente ya que infantilmente muchos números de coma flotante. Los desarrolladores de lenguaje tendrían que hacer algunas suposiciones fundamentales y potencialmente causantes de errores en el diseño del lenguaje.

Eche un vistazo a este article sobre matemáticas de punto flotante para obtener más información.

+2

No, no quiero que los números se escriban sin apretar. Eso sería horrible. =) Simplemente no quiero escribir mi método 'Range' (Rango (x, min, max) = Máx. (Min (x, max), min)) para los 16 tipos numéricos. – Jens

1

Respondiendo a Matthew, note la diferencia entre las 3 invocaciones.

void DoSomething(ref MyStruct something) 
{ 
    something.DoSomething(); 
} 

static void Main(string[] args) 
{ 
    var s = new MyStruct(10); 
    var i = (IDoSomething)s; 

    DoSomething(s); // will not modify s 
    DoSomething(i); // will modify i 
    DoSomething(ref s); // will modify s, but with no reassignment 

} 
+0

¡Gracias! Nunca he pensado en eso. Los tipos numéricos ya implementan varias interfaces. ¿Mi número "propuesto" es diferente de ellos o este problema existe hoy en día? – Jens

1

No es tan simple como la introducción de una interfaz, como los operadores disponibles son diferentes según el tipo, y no siempre son aún homogénea (es decir, DateTime + TimeSpan => DateTime, DateTime - DateTime => TimeSpan).

En el nivel técnico, puede haber mucho boxeo, etc. involucrado aquí, más operadores regulares son static.

En realidad, para Min/Max, Comparer<T>.Default.Compare(x,y) hace casi todo lo que pueda desear.

Para otros operadores: en .NET 4.0 dynamic es de gran ayuda aquí:

T x = ..., y = ...; 
T sum = (dynamic)x + (dynamic)y; 

pero por lo demás "MiscUtil" tiene generic support for operators través de la clase Operator, es decir

T x = ..., y = ...; 
T sum = Operator.Add(x, y); // actually Add<T> 
Cuestiones relacionadas