2012-06-29 19 views
9

Sospecho que la respuesta es no, pero me gustaría saber si es posible hacer algo como esto:¿Es posible restringir un parámetro de tipo de método genérico de C# como "asignable desde" el parámetro de tipo de clase que contiene?

public class MyGenericClass<TSomeClass> { 
    public void MyGenericMethod<TSomeInterface>() 
     // This doesn't compile. 
     where TSomeClass : TSomeInterface 
    { 
     //... 
    } 
} 

Lo que quiero decir para indicar en el ejemplo anterior (que no funciona) es restringir TSomeInterface tales que puede ser cualquier clase base, interfaz implementada o (si realmente desea obtener fantasía) conversión implícita de MyGenericClass.

NOTA: sospecho que la razón por la que esto nunca fue implementado en C# es que las limitaciones genéricas no son realmente destinado a ser contratos de código, que es la forma en que estoy tratando de utilizarlos aquí. Realmente no me importa qué tipo es TSomeInterface, siempre y cuando esté implementado por TSomeClass.

Hasta ahora, he hackeado esto juntos:

public class MyGenericClass<TSomeClass> { 
    public void MyGenericMethod<TIntermediateType, TSomeInterface>() 
     where TIntermediateType : TSomeClass, TSomeInterface 
    { 
     //... 
    } 
} 

Esto más o menos hace cumplir la restricción de que yo quiero (que TSomeClass debe heredar de, o en el caso de una interfaz, implementar, TSomeInterface), pero llamar es muy torpe, porque tengo que especificar TIntermediateType (a pesar de que realmente lo quiero para evaluar contra TSomeClass):

var myGenericInstance = new MyGenericClass<TSomeClass>(); 
myGenericInstance.MyGenericMethod(TSomeClass, TSomeInterface); 

Además, el truco anterior se rompe debido a una persona que llama pudo en el o bien especifique una subclase de TSomeClass como primer parámetro de tipo, donde solo la subclase implementa TSomeInterface.

La razón por la que yo quiero hacer esto es que estoy escribiendo un patrón de fábrica de fluidez para un servicio WCF, y me como para evitar que la persona que llama (en tiempo de compilación) de tratar de crear un punto final con un contrato que la clase de servicio no implementa. Obviamente puedo verificar esto en tiempo de ejecución (WCF de hecho hace esto por mí), pero soy un gran admirador de la verificación en tiempo de compilación.

¿Hay una manera mejor/más elegante de lograr lo que busco aquí?

+1

Esto es prácticamente imposible ya que está intentando mezclar genéricos con el tiempo de ejecución. – Polity

+0

No, no lo soy. Solo estoy tratando de aplicar una restricción genérica que (por lo que puedo decir) el lenguaje C# no permite (restringir un parámetro de tipo tal que es una superclase o una interfaz implementada de un parámetro de tipo en la clase que lo contiene). –

+0

Por lo que yo veo, intenta restringir la clase genérica en función de los datos de tipo que alimenta a un método.Si desea restringir el genérico de su clase, debe hacerlo a nivel de clase (lógicamente), de lo contrario no hay forma de que el compilador JIT cree una clase concreta. – Polity

Respuesta

3

La manera en que yo era capaz de envolver mi cabeza alrededor de la razón para que esto no se compila es la siguiente:

consideran este programa se compila:

class Program { 
    class Class1 { } 
    class Class2 { } 
    public class MyGenericClass<TSomeClass> { 
     public void MyGenericMethod<TSomeInterface>() where TSomeClass : TSomeInterface { 
     } 
    } 
    static void Main(string[] args) { 
     var inst = new MyGenericClass<Class1>(); 
    } 
} 

Todo es bueno. El compilador está feliz. Ahora considero que cambie el método Main:

static void Main(string[] args) { 
    var inst = new MyGenericClass<Class1>(); 
    inst.MyGenericMethod<Class2>(); 
} 

El compilador se quejará de que no implementa Class1Class2. Pero ¿qué línea está equivocada? La restricción está en la llamada al MyGenericMethod, pero la línea de código ofensiva es la creación de MyGenericClass.

En otras palabras, ¿cuál obtiene la línea roja ondulada?

+1

In mi mundo ideal sería la llamada al método, por supuesto, tal como lo sería si trataras de hacer esto: 'var x = new List (); s.Add (5);'. El compilador asume que la declaración es correcta y la llamada al método está usando un argumento no válido. Sin embargo, para pensar en la sintaxis, +1. –

3

Como se discutió en this linked question, no puede usar un parámetro de tipo que no sea de la declaración actual, en el lado izquierdo de una cláusula where.

Así como lo sugiere W0lf en esa otra pregunta, lo que puede hacer es proporcionar ambos tipos en su interfaz (en lugar de método) Declaración:

public class MyGenericClass<TSomeClass, TSomeInterface> { 
    where TSomeClass : TSomeInterface 
    public void MyGenericMethod() // not so generic anymore :( 
    { 
     //... 
    } 
} 

Eso, sin embargo, limita en gran medida su MyGenericMethod y fuerza a su clase para declarar de antemano con qué interfaz base puede permitir.

Así otra opción es el uso de un método estático con más parámetros de tipo:

public class MyGenericClass<TSomeClass> { 
    public static void MyGenericMethod<TSomeClass, TSomeInterface> 
             (MyGenericClass<TSomeClass> that) 
     where TSomeClass : TSomeInterface 
    { 
     // use "that" instead of this 
    } 
} 

lo que puedas hacer que sea un método de extensión para hacer que aparece al usuario como un método real.

Ninguno de estos es exactamente lo que quería, pero probablemente es mejor que la solución de tipo intermedio.

En cuanto a la razón de ¿por qué no?, supongo que complicaría el compilador sin agregar suficiente valor. Aquí hay un discussion by Angelika Langer of the same subject but about Java. Aunque hay diferencias significativas entre C# y Java, creo que su conclusión podría aplicar aquí también:

La conclusión es que la utilidad de los límites inferiores del tipo de parámetros es algo discutible. Serían confusos y tal vez incluso engañosos cuando se usan como parámetros de tipo de una clase genérica. En , por otro lado, los métodos genéricos ocasionalmente se beneficiarían de un parámetro de tipo con un límite inferior. Para los métodos, a menudo se puede encontrar una solución temporal para la falta de un parámetro de tipo de límite inferior . Tal solución generalmente implica un método genérico estático o un comodín vinculado inferior.

Ella también da un buen caso de uso, consulte el enlace de arriba.

1

Un método de extensión proporciona la mejor solución, aunque no resuelve por completo todas sus inquietudes.

public class MyGenericClass<TSomeClass> 
{ 
} 

public static class MyGenericClassExtensions 
{ 
    public static void MyGenericMethod<TSomeClass, TSomeInterface>(this MyGenericClass<TSomeClass> self) 
     where TSomeClass : TSomeInterface 
    { 
     //... 
    } 
} 

Todavía es necesario especificar ambos tipos al llamar MyGenericMethod, pero evita que la persona que llama desde la especificación de un tipo incorrecto de TSomeClass como es posible con el enfoque que se le ocurrió. Con este enfoque, el método se puede llamar así:

var myGenericInstance = new MyGenericClass<TSomeClass>(); 
myGenericInstance.MyGenericMethod<TSomeClass, TSomeInterface>(); 

Será un error de compilación si el parámetro de tipo MyGenericClass se declara con no coincide con el primer parámetro de tipo de MyGenericMethod.

Como el primer parámetro de tipo se puede inferir por el argumento this, a menudo es posible que el compilador infiera ambos parámetros de tipo si sus parámetros adicionales al método.

Cuestiones relacionadas