2011-01-19 16 views
5

Estoy portando una aplicación C++ a C#, y me he encontrado con plantillas. He leído un poco sobre esto, y entiendo que algunas plantillas son similares a los genéricos .Net. Leí el SO answer en este caso que lo resumió muy bien.Portando C++ a C# - plantillas

Sin embargo, algunos usos de la plantilla C++ no parecen estar directamente relacionados con los genéricos. En el siguiente ejemplo del artículo de Wikipedia Template metaprogramming, la plantilla parece aceptar un valor, en lugar de un tipo. No estoy muy seguro de cómo esto se transporta a C#?

template <int N> 
struct Factorial 
{ 
    enum { value = N * Factorial<N - 1>::value }; 
}; 

template <> 
struct Factorial<0> 
{ 
    enum { value = 1 }; 
}; 

// Factorial<4>::value == 24 
// Factorial<0>::value == 1 
void foo() 
{ 
    int x = Factorial<4>::value; // == 24 
    int y = Factorial<0>::value; // == 1 
} 

Es evidente que para este ejemplo que podía hacer:

public int Factorial(int N){ 
    if(N == 0) return 1; 
    return Factorial(N - 1); 
} 

pero esto me parece ser una refactorización a una función, en lugar de un puerto de código semánticamente similar.

+0

¿Por qué usarías una función recursiva clásica como genéricos C#? Intente leer este artículo http://msdn.microsoft.com/en-us/library/bb549151.aspx para utilizar Func nemke

+1

El problema probablemente no sea con las constantes (frente a los tipos) como parámetros de las plantillas, sino con especializaciones y especializaciones parciales de clases o funciones si se usan actualmente en su base de códigos. Sin algún ejemplo particular, la respuesta deberá ser amplia y probablemente no sea realmente útil. Voy a votar para cerrar por esa razón, siéntase libre de agregar preguntas específicas sobre algún fragmento de código que tenga problemas para convertir. –

+1

@nemke: en este ejemplo, la plantilla Factorial se expande en tiempo de compilación, por lo que no se realiza ningún trabajo en tiempo de ejecución. El OP quiere portar el código sin tener que traducir demasiado en diferentes tipos de llamadas C#, por lo que preferiría una solución genérica si es posible. Lamentablemente, no lo es. @David - punto muy bueno también –

Respuesta

5

genéricos Desafortunadamente .Net sólo pueden aceptar tipos. Las plantillas de C++ toman otros valores que el compilador considera expresiones constantes, porque en realidad son solo macros que se expanden a más código.

Esto significa que su idea de convertir el código en una llamada a método es la mejor opción. Se podría hacer la llamada al método devuelve un tipo con una propiedad .Value (siguiendo su ejemplo) manteniendo así el código portado similar a la plantilla:

return Factorial(N-1).Value; 
+0

Hay un punto adicional que los compiladores C# y C++ son fundamentalmente diferentes en que C++ está plagado de funcionalidades que permiten escribir códigos en tiempo de compilación (macros y plantillas) mientras que el equipo C# deliberadamente se mantuvo al margen, citando la legibilidad como su razón . C# podría acercarse a él, pero nunca volverá a ser lo mismo. –

+0

+1 Los genéricos en C# son solo una conveniencia, de acuerdo con el enlace de MSDN todo se hace dinámicamente de todos modos, así que todo lo que gana es comprobar el tipo de compilación (lo cual es bastante bueno) pero no los cálculos en tiempo de compilación (que es tu ejemplo lo está haciendo). – MatiasFG

+0

@MatiasFG: exactamente el punto: los genéricos se compilan dinámicamente, pero la comprobación de tipos para su código se realiza en tiempo de compilación del tipo genérico. Extraño no tener la funcionalidad de la plantilla en C# habiendo comenzado a hacer la metaprogramación de plantillas en C++ justo antes de convertirme en desarrollador de C# a tiempo completo. Sin embargo, los genéricos son muy geniales por sí mismos y me alegra que los tengamos. –

3

vistazo a este artículo para las diferencias entre los genéricos de C# y C++ plantillas:

creo que su ejemplo se incluye allí.

MSDN Link

+0

Gracias, excluye este caso: "C# no permite parámetros de plantilla que no sean de tipo, como la plantilla C {}.". ¿Es la refactorización de una función, como he hecho en la pregunta anterior, la mejor manera de portar este tipo de plantillas? –

+1

En la circunstancia anterior, la refacturación a un método es probablemente lo mejor que se puede hacer. Pero cada situación necesita ser manejada caso por caso. A veces puede querer/necesitar proporcionar un valor al constructor de una clase. –

+1

Lo bueno es que C# no requiere constantes de tiempo de compilación como C++, por ejemplo al declarar una matriz, por lo que un cálculo de tiempo de ejecución en la mayoría de los casos arrojará el resultado esperado. –

1

La respuesta corta es que no todo lo que se puede hacer en C++ las plantillas se pueden hacer en C# generics. En el caso de plantillas que acepten valores que no sean de tipo, cada situación tendrá que ser manejada y refacturada apropiadamente caso por caso.

0

Esto es lo más cerca que podía pensar:

public class Factorial<T> 
    where T : IConvertible 
    { 
     public T GetFactorial(T t) 
     { 
      int int32 = Convert.ToInt32(t); 
      if (int32 == 0) 
       return (T) Convert.ChangeType(1, typeof(T)); 
      return GetFactorial((T) Convert.ChangeType(int32-1, typeof(T))); 
     } 
    } 

El problema es que no se puede definir genéricos y limitarlo a ValueTypes. Esto funcionará para byte, Int16 e Int32. También para valores pequeños de Int64.

+0

Para aclarar, el problema es que no puede restringir a valores numéricos. Podría poner algunas restricciones allí para struct e IComparable, pero eso no es suficiente para determinar numérico/no numérico. –

5

En el siguiente ejemplo ... la plantilla parece aceptar un valor, en lugar de un tipo.

Este no es su mayor problema. De hecho, esto podría resolverse teóricamente en C# utilizando una representación de Church numeral o Peano basándose en tipos genéricos anidados.

Sin embargo, el problema es que C# no permite la especialización de plantilla . La especialización de plantillas es responsable en su ejemplo de definir que el factorial de 0 es 1, en lugar de lo mismo que para los demás números. C# no permite hacer eso.

Así que no hay forma de especificar un caso base en una definición de plantilla recursiva (genérica) y, por lo tanto, no hay recursividad. Los genéricos C# no están completos, mientras que las plantillas C++ sí lo están.


Algo como esto:

class Zero { } 

class Successor<T> : Zero where T : Zero { } 

// one: 
Successor<Zero> 
// two: 
Successor<Successor<Zero>> 
// etc. 

operaciones sobre estos números Implementar se deja como ejercicio para el lector.