2009-03-02 14 views
66

¿Cómo haría la especialización en C#? Voy a plantear un problema. Tienes un tipo de plantilla, no tienes idea de qué se trata. Pero usted sabe si es derivado de XYZ al que desea llamar .alternativeFunc(). Una buena forma es llamar a una función o clase especializada y hacer que normal Call devuelva .normalFunc() mientras tenga la otra especialización en cualquier tipo derivado de XYZ para llamar a .alternativeFunc(). ¿Cómo se haría esto en C#?Cómo hacer especialización de plantilla en C#

+1

No entiendo la pregunta. ¿Esto no es herencia normal? –

Respuesta

53

asumiendo que está hablando de especialización de plantilla, ya que se puede hacer con plantillas de C++: una característica como esta no está realmente disponible en C#. Esto se debe a que los genéricos C# no se procesan durante la compilación y son más una característica del tiempo de ejecución.

Sin embargo, puede lograr un efecto similar con los métodos de extensión C# 3.0. Aquí hay un ejemplo que muestra cómo agregar el método de extensión solo para el tipo "Mi Clase", que es como la especialización de plantilla. Tenga en cuenta sin embargo, que no se puede usar esto para ocultar implementación predeterminada del método, porque compilador de C# siempre prefiere métodos estándar a métodos de extensión:

class MyClass<T> { 
    public int Foo { get { return 10; } } 
} 
static class MyClassSpecialization { 
    public static void Bar(this MyClass<int> cls) { 
    return cls.Foo + 20; 
    } 
} 

Ahora se puede escribir lo siguiente:

var cls = new MyClass<int>(); 
cls.Bar(); 

Si usted quiere tener un caso por defecto para el método que se utiliza cuando no se proporciona la especialización, lo que yo creo creando un método extensión genérica "bar" debe hacer el truco:

public static void Bar<T>(this MyClass<T> cls) { 
    return cls.Foo + 42; 
    } 
+0

Foo propiedad vs Bar método ... realmente no parece una especialización típica ... –

+0

Esa es una gran solución, +1 –

+1

No, no es una especilación típica, pero es la única cosa fácil que puede hacer ... (AFAIK) –

0

Si lo que desea es probar si un tipo se derrived de XYZ, entonces usted puede utilizar:

theunknownobject.GetType().IsAssignableFrom(typeof(XYZ)); 

Si es así, puede convertir "theunknownobject" a XYZ e invocar alternativeFunc() así:

XYZ xyzObject = (XYZ)theunknownobject; 
xyzObject.alternativeFunc(); 

Espero que esto ayude.

+1

No sé mucho C#, pero quienquiera que lo haya votado debería decir por qué. No tengo idea de qué está mal cuando tu respuesta o si algo está mal con eso. –

+0

No estoy seguro tampoco. Me parece lo suficientemente válido. Aunque es un poco más detallado de lo necesario. – jalf

+2

No fui yo, pero es porque la respuesta es completamente irrelevante para la pregunta. Búsqueda '" especialización de plantillas de C++ "' – georgiosd

74

En C#, lo más cercano a la especialización es usar una sobrecarga más específica; sin embargo, esto es frágil y no cubre todos los usos posibles. Por ejemplo:

void Foo<T>(T value) {Console.WriteLine("General method");} 
void Foo(Bar value) {Console.WriteLine("Specialized method");} 

Aquí, si el compilador sabe los tipos en tiempo de compilación, se escogerá el más específico:

Bar bar = new Bar(); 
Foo(bar); // uses the specialized method 

Sin embargo ....

void Test<TSomething>(TSomething value) { 
    Foo(value); 
} 

utilizará Foo<T> incluso para TSomething=Bar, ya que se graba en tiempo de compilación.

Otro enfoque es utilizar la prueba de tipo dentro de, un método genérico; sin embargo, esta suele ser una mala idea y no se recomienda.

Básicamente, C# simplemente no quiere trabajar con especializaciones, a excepción de polimorfismo:

class SomeBase { public virtual void Foo() {...}} 
class Bar : SomeBase { public override void Foo() {...}} 

Aquí Bar.Foo siempre resolverá a la anulación correcta.

13

Al agregar una clase intermedia y un diccionario, es posible la especialización .

Para especializarse en T, creamos una interfaz genérica, que tiene un método llamado (por ejemplo) Aplicar. Para las clases específicas que implementa la interfaz, definiendo el método Apply specific para esa clase. Esta clase intermedia se llama clase de rasgos.

Esa clase de rasgos se puede especificar como un parámetro en la llamada del método genérico, que luego (por supuesto) siempre toma la implementación correcta.

En lugar de especificarlo manualmente, la clase de rasgos también se puede almacenar en un IDictionary<System.Type, object> global. Entonces se puede mirar hacia arriba y listo, tienes una especialización real allí.

Si es conveniente, puede exponerlo en un método de extensión.

class MyClass<T> 
{ 
    public string Foo() { return "MyClass"; } 
} 

interface BaseTraits<T> 
{ 
    string Apply(T cls); 
} 

class IntTraits : BaseTraits<MyClass<int>> 
{ 
    public string Apply(MyClass<int> cls) 
    { 
     return cls.Foo() + " i"; 
    } 
} 

class DoubleTraits : BaseTraits<MyClass<double>> 
{ 
    public string Apply(MyClass<double> cls) 
    { 
     return cls.Foo() + " d"; 
    } 
} 

// Somewhere in a (static) class: 
public static IDictionary<Type, object> register; 
register = new Dictionary<Type, object>(); 
register[typeof(MyClass<int>)] = new IntTraits(); 
register[typeof(MyClass<double>)] = new DoubleTraits(); 

public static string Bar<T>(this T obj) 
{ 
    BaseTraits<T> traits = register[typeof(T)] as BaseTraits<T>; 
    return traits.Apply(obj); 
} 

var cls1 = new MyClass<int>(); 
var cls2 = new MyClass<double>(); 

string id = cls1.Bar(); 
string dd = cls2.Bar(); 

ver este link a mi reciente blog y las continuaciones de una extensa descripción y muestras.

+0

Este es el patrón de fábrica y es una forma decente de * algunas de las deficiencias de los genéricos – Yaur

+1

@Yaur. Me parece un patrón de decorador de libros de texto. –

5

Algunas de las respuestas propuestas utilizan información del tipo de tiempo de ejecución: inherentemente más lenta que las llamadas a métodos en tiempo de compilación.

El compilador no aplica la especialización tan bien como en C++.

Recomendaría buscar en PostSharp una forma de insertar código después de que se haya realizado el compilador habitual para lograr un efecto similar al de C++.

2

Estaba buscando un patrón para simular la especialización de plantillas, también. Hay algunos enfoques que pueden funcionar en algunas circunstancias. Sin embargo, ¿qué pasa con el caso

static void Add<T>(T value1, T value2) 
{ 
    //add the 2 numeric values 
} 

Se podría elegir la acción mediante declaraciones, p. if (typeof(T) == typeof(int)). Pero hay una mejor manera de simular especialización de plantilla real con la sobrecarga de una sola función llamada virtual:

public interface IMath<T> 
{ 
    T Add(T value1, T value2); 
} 

public class Math<T> : IMath<T> 
{ 
    public static readonly IMath<T> P = Math.P as IMath<T> ?? new Math<T>(); 

    //default implementation 
    T IMath<T>.Add(T value1, T value2) 
    { 
     throw new NotSupportedException();  
    } 
} 

class Math : IMath<int>, IMath<double> 
{ 
    public static Math P = new Math(); 

    //specialized for int 
    int IMath<int>.Add(int value1, int value2) 
    { 
     return value1 + value2; 
    } 

    //specialized for double 
    double IMath<double>.Add(double value1, double value2) 
    { 
     return value1 + value2; 
    } 
} 

Ahora podemos escribir, sin tener que conocer el tipo de antemano:

static T Add<T>(T value1, T value2) 
{ 
    return Math<T>.P.Add(value1, value2); 
} 

private static void Main(string[] args) 
{ 
    var result1 = Add(1, 2); 
    var result2 = Add(1.5, 2.5); 

    return; 
} 

Si la especialización no solo debe invocarse para los tipos implementados, sino también para los tipos derivados, uno podría usar un parámetro In para la interfaz. Sin embargo, en este caso, los tipos de devolución de los métodos ya no pueden ser del tipo genérico T.

0

Creo que hay una manera de lograr con el uso de .NET 4+ resolución dinámica:

static class Converter<T> 
{ 
    public static string Convert(T data) 
    { 
     return Convert((dynamic)data); 
    } 

    private static string Convert(Int16 data) => $"Int16 {data}"; 
    private static string Convert(UInt16 data) => $"UInt16 {data}"; 
    private static string Convert(Int32 data) => $"Int32 {data}"; 
    private static string Convert(UInt32 data) => $"UInt32 {data}"; 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Console.WriteLine(Converter<Int16>.Convert(-1)); 
     Console.WriteLine(Converter<UInt16>.Convert(1)); 
     Console.WriteLine(Converter<Int32>.Convert(-1)); 
     Console.WriteLine(Converter<UInt32>.Convert(1)); 
    } 
} 

Salida:

Int16 -1 
UInt16 1 
Int32 -1 
UInt32 1 

lo que demuestra que una implementación diferente es llamada para diferentes tipos.

Cuestiones relacionadas