¿Hay alguna manera de obtener la siguiente declaración de función?
public bool Foo<T>() where T : interface;
es decir. donde T es un tipo de interfaz (similar a where T : class
y struct
).
Actualmente me he conformado con:
public bool Foo<T>() where T : IBase;
Donde IBase se define como una interfaz vacía que es heredada por todas mis interfaces personalizadas ... No es ideal, pero debería funcionar ... ¿Por qué no puede definir que un tipo genérico debe ser una interfaz?
Para lo que vale, quiero esto porque Foo
está haciendo una reflexión donde necesita un tipo de interfaz ... Podría pasarlo como un parámetro normal y hacer la verificación necesaria en la función en sí, pero esto parecía mucho más seguro (y yo supongamos un poco más de rendimiento, ya que todas las comprobaciones se realizan en tiempo de compilación).
c#
generics
interface
constraints
Matthew Scharley
fuente
fuente
IBase
, utilizadas de esta manera, se denominan interfaces de marcador . Permiten comportamientos especiales para tipos 'marcados'.Respuestas:
Lo más cercano que puede hacer (excepto su enfoque de interfaz base) es "
where T : class
", que significa tipo de referencia. No hay sintaxis que signifique "cualquier interfaz".Este ("
where T : class
") se usa, por ejemplo, en WCF para limitar clientes a contratos de servicio (interfaces).fuente
interface
restricciónT
debería permitir las comparaciones de referencia entreT
y cualquier otro tipo de referencia, ya que las comparaciones de referencia están permitidas entre cualquier interfaz y casi cualquier otro tipo de referencia, y permitir comparaciones incluso en ese caso no plantearía ningún problema.Sé que esto es un poco tarde, pero para aquellos que estén interesados, pueden usar una verificación de tiempo de ejecución.
fuente
Foo(Type type)
.if (new T() is IMyInterface) { }
para verificar si la clase T implementa una interfaz. Puede que no sea el más eficiente, pero funciona.No, en realidad, si estás pensando
class
y quieresstruct
decirclass
es ystruct
s, estás equivocado.class
significa cualquier tipo de referencia (por ejemplo, también incluye interfaces) ystruct
significa cualquier tipo de valor (por ejemplostruct
,enum
).fuente
where T : struct
restricciones de coincidencia .class
, pero declarar una ubicación de almacenamiento de un tipo de interfaz realmente declara que la ubicación de almacenamiento es una referencia de clase que implementa ese tipo.where T : struct
corresponde aNotNullableValueTypeConstraint
, por lo que significa que debe ser un tipo de valor distinto deNullable<>
. (Entonces,Nullable<>
es un tipo de estructura que no satisface lawhere T : struct
restricción.)Para seguir la respuesta de Robert, esto es aún más tarde, pero puede usar una clase auxiliar estática para hacer que el tiempo de ejecución se verifique solo una vez por tipo:
También noto que su solución "debería funcionar" no funciona, de hecho. Considerar:
Ahora no hay nada que te impida llamar a Foo así:
La
Actual
clase, después de todo, satisface laIBase
restricción.fuente
static
constructor no puede serpublic
, así que esto debería dar un error en tiempo de compilación. Además, sustatic
clase contiene un método de instancia, que también es un error en tiempo de compilación.Hace tiempo que pienso en las limitaciones de tiempo de compilación, por lo que esta es una oportunidad perfecta para lanzar el concepto.
La idea básica es que si no puede hacer una verificación del tiempo de compilación, debe hacerlo lo antes posible, que es básicamente el momento en que se inicia la aplicación. Si todas las comprobaciones están bien, la aplicación se ejecutará; Si falla un cheque, la aplicación fallará instantáneamente.
Comportamiento
El mejor resultado posible es que nuestro programa no compila si no se cumplen las restricciones. Desafortunadamente, eso no es posible en la implementación actual de C #.
La siguiente mejor opción es que el programa se bloquea en el momento en que se inicia.
La última opción es que el programa se bloqueará en el momento en que se presione el código. Este es el comportamiento predeterminado de .NET. Para mí, esto es completamente inaceptable.
Pre requisitos
Necesitamos tener un mecanismo de restricción, así que por falta de algo mejor ... usemos un atributo. El atributo estará presente sobre una restricción genérica para verificar si cumple con nuestras condiciones. Si no es así, damos un error feo.
Esto nos permite hacer cosas como esta en nuestro código:
(He guardado el
where T:class
aquí, porque siempre prefiero las comprobaciones en tiempo de compilación a las comprobaciones en tiempo de ejecución)Entonces, eso solo nos deja con 1 problema, que es verificar si todos los tipos que usamos coinciden con la restricción. ¿Qué tan difícil puede ser?
Vamos a romperlo
Los tipos genéricos siempre están en una clase (/ struct / interface) o en un método.
Activar una restricción requiere que realice una de las siguientes cosas:
En este punto, me gustaría decir que siempre debe evitar hacer (4) en cualquier programa IMO. De todos modos, estas comprobaciones no lo admitirán, ya que significaría efectivamente resolver el problema de detención.
Caso 1: usando un tipo
Ejemplo:
Ejemplo 2
Básicamente, esto implica escanear todos los tipos, herencia, miembros, parámetros, etc., etc., etc. Si un tipo es genérico y tiene una restricción, verificamos la restricción; si es una matriz, verificamos el tipo de elemento.
En este punto, debo agregar que esto romperá el hecho de que, por defecto, .NET carga los tipos 'perezosos'. Al escanear todos los tipos, forzamos el tiempo de ejecución de .NET para cargarlos a todos. Para la mayoría de los programas esto no debería ser un problema; aún así, si usa inicializadores estáticos en su código, puede encontrar problemas con este enfoque ... Dicho esto, no recomendaría a nadie que haga esto de todos modos (excepto por cosas como esta :-), por lo que no debería dar Tienes muchos problemas.
Caso 2: uso de un tipo en un método
Ejemplo:
Para verificar esto, solo tenemos 1 opción: descompilar la clase, verificar todos los tokens de miembros que se usan y si uno de ellos es del tipo genérico, verifique los argumentos.
Caso 3: Reflexión, construcción genérica en tiempo de ejecución
Ejemplo:
Supongo que es teóricamente posible verificar esto con trucos similares al caso (2), pero la implementación es mucho más difícil (debe verificar si
MakeGenericType
se llama en alguna ruta de código). No entraré en detalles aquí ...Caso 4: Reflexión, tiempo de ejecución RTTI
Ejemplo:
Este es el peor de los casos y, como expliqué antes, generalmente es una mala idea en mi humilde opinión. De cualquier manera, no hay una forma práctica de resolver esto usando cheques.
Probar el lote
Crear un programa que pruebe los casos (1) y (2) resultará en algo como esto:
Usando el código
Bueno, esa es la parte fácil :-)
fuente
No puede hacer esto en ninguna versión lanzada de C #, ni en la próxima C # 4.0. Tampoco es una limitación de C #: no hay ninguna restricción de "interfaz" en el CLR.
fuente
Si es posible, elegí una solución como esta. Solo funciona si desea que se pasen varias interfaces específicas (por ejemplo, aquellas a las que tiene acceso a la fuente) como un parámetro genérico, no ninguno.
IInterface
.IInterface
En origen, se ve así:
Cualquier interfaz que desee pasar como parámetro genérico:
Interfaz:
La clase en la que desea poner la restricción de tipo:
fuente
T
no está restringido a las interfaces, está limitado a todo lo que implementeIInterface
, lo que cualquier tipo puede hacer si lo desea, por ejemplo,struct Foo : IInterface
dado queIInterface
es muy probable que sea público (de lo contrario, todo lo que acepte debería ser interno).Lo que ha decidido es lo mejor que puede hacer:
fuente
Intenté hacer algo similar y utilicé una solución alternativa: pensé en el operador implícito y explícito en la estructura: la idea es envolver el Tipo en una estructura que se pueda convertir en Tipo implícitamente.
Aquí hay tal estructura:
public struct InterfaceType {tipo privado _type;
}
uso básico:
Tienes que imaginar tu propio mecanismo en torno a esto, pero un ejemplo podría ser un método tomado un parámetro InterfaceType en lugar de un tipo
Un método para anular que debería devolver los tipos de interfaz:
Quizás también haya cosas que hacer con los genéricos, pero no lo intenté
Espero que esto pueda ayudar o dar ideas :-)
fuente
Solución A: esta combinación de restricciones debería garantizar que
TInterface
es una interfaz:Requiere una sola estructura
TStruct
como Testigo para demostrar queTInterface
es una estructura.Puede usar una estructura única como testigo para todos sus tipos no genéricos:
Solución B: si no desea hacer estructuras como testigos, puede crear una interfaz
y usa una restricción:
Implementación para interfaces:
Esto resuelve algunos de los problemas, pero requiere la confianza de que nadie implementa
ISInterface<T>
para tipos que no son de interfaz, pero eso es bastante difícil de hacer accidentalmente.fuente
Use una clase abstracta en su lugar. Entonces, tendrías algo como:
fuente