Existe-t-il un moyen d'obtenir la déclaration de fonction suivante?
public bool Foo<T>() where T : interface;
c'est à dire. où T est un type d'interface (similaire à where T : class
, et struct
).
Actuellement, je me suis installé pour:
public bool Foo<T>() where T : IBase;
Où IBase est défini comme une interface vide héritée de toutes mes interfaces personnalisées ... Pas idéal, mais cela devrait fonctionner ... Pourquoi ne pouvez-vous pas définir qu'un type générique doit être une interface?
Pour ce que ça vaut, je veux cela parce que je Foo
fais une réflexion là où il faut un type d'interface ... Je pourrais le passer en paramètre normal et faire le contrôle nécessaire dans la fonction elle-même, mais cela semblait beaucoup plus sûr (et je supposons un peu plus performant, puisque toutes les vérifications sont effectuées à la compilation).
la source
IBase
- utilisées de cette manière - sont appelées interfaces marqueurs . Ils permettent des comportements spéciaux pour les types «marqués».Réponses:
Le plus proche que vous puissiez faire (à l'exception de votre approche d'interface de base) est "
where T : class
", qui signifie type de référence. Il n'y a pas de syntaxe pour signifier "n'importe quelle interface".Ceci ("
where T : class
") est utilisé, par exemple, dans WCF pour limiter les clients aux contrats de service (interfaces).la source
interface
contrainte surT
devrait permettre des comparaisons de références entreT
et tout autre type de référence, car les comparaisons de référence sont autorisées entre toute interface et presque tout autre type de référence, et autoriser des comparaisons même dans ce cas ne poserait aucun problème.Je sais que c'est un peu tard, mais pour ceux qui sont intéressés, vous pouvez utiliser une vérification d'exécution.
la source
Foo(Type type)
.if (new T() is IMyInterface) { }
vérifier si une interface est implémentée par la classe T. Ce n'est peut-être pas le plus efficace, mais cela fonctionne.Non, en fait, si vous pensez
class
etstruct
moyennesclass
es etstruct
s, vous avez tort.class
signifie tout type de référence (par exemple, inclut également les interfaces) etstruct
signifie tout type de valeur (par exemplestruct
,enum
).la source
where T : struct
contrainte de correspondance .class
, mais déclarer un emplacement de stockage d'un type d'interface déclare réellement l'emplacement de stockage comme une référence de classe qui implémente ce type.where T : struct
correspond àNotNullableValueTypeConstraint
, donc cela signifie qu'il doit s'agir d'un type valeur différent deNullable<>
. (Il enNullable<>
va de même pour un type struct qui ne satisfait pas lawhere T : struct
contrainte.)Pour faire suite à la réponse de Robert, c'est encore plus tard, mais vous pouvez utiliser une classe d'assistance statique pour effectuer la vérification d'exécution une seule fois par type:
Je note également que votre solution «devrait fonctionner» ne fonctionne pas, en fait. Considérer:
Maintenant, rien ne vous empêche d'appeler Foo ainsi:
La
Actual
classe, après tout, satisfait laIBase
contrainte.la source
static
constructeur ne peut pas l'êtrepublic
, donc cela devrait donner une erreur de compilation. De plus, votrestatic
classe contient une méthode d'instance, c'est également une erreur de compilation.Depuis un certain temps, je réfléchis aux contraintes de quasi-temps de compilation, c'est donc une opportunité parfaite pour lancer le concept.
L'idée de base est que si vous ne pouvez pas effectuer une vérification de compilation, vous devez le faire le plus tôt possible, c'est-à-dire au moment où l'application démarre. Si toutes les vérifications sont correctes, l'application s'exécutera; si une vérification échoue, l'application échouera instantanément.
Comportement
Le meilleur résultat possible est que notre programme ne compile pas si les contraintes ne sont pas satisfaites. Malheureusement, ce n'est pas possible dans l'implémentation actuelle de C #.
La meilleure chose suivante est que le programme plante au moment où il a démarré.
La dernière option est que le programme plante au moment où le code est frappé. Il s'agit du comportement par défaut de .NET. Pour moi, c'est totalement inacceptable.
Pré-requis
Nous avons besoin d'un mécanisme de contrainte, donc faute de mieux ... utilisons un attribut. L'attribut sera présent au-dessus d'une contrainte générique pour vérifier s'il correspond à nos conditions. Si ce n'est pas le cas, nous donnons une vilaine erreur.
Cela nous permet de faire des choses comme ça dans notre code:
(J'ai gardé
where T:class
ici, car je préfère toujours les contrôles à la compilation aux contrôles à l'exécution)Donc, cela ne nous laisse qu'un seul problème, qui consiste à vérifier si tous les types que nous utilisons correspondent à la contrainte. À quel point cela peut-il être dur?
Brisons-le
Les types génériques sont toujours soit sur une classe (/ struct / interface), soit sur une méthode.
Le déclenchement d'une contrainte vous oblige à effectuer l'une des opérations suivantes:
À ce stade, je tiens à préciser que vous devez toujours éviter de faire (4) dans tout programme IMO. Quoi qu'il en soit, ces contrôles ne le soutiendront pas, car cela signifierait effectivement résoudre le problème en suspens.
Cas 1: utilisation d'un type
Exemple:
Exemple 2:
Fondamentalement, cela implique l'analyse de tous les types, l'héritage, les membres, les paramètres, etc., etc., etc. Si un type est un type générique et a une contrainte, nous vérifions la contrainte; s'il s'agit d'un tableau, nous vérifions le type d'élément.
À ce stade, je dois ajouter que cela cassera le fait que par défaut .NET charge les types «paresseux». En analysant tous les types, nous forçons le runtime .NET à tous les charger. Pour la plupart des programmes, cela ne devrait pas être un problème; quand même, si vous utilisez des initialiseurs statiques dans votre code, vous pourriez rencontrer des problèmes avec cette approche ... Cela dit, je ne conseillerais à personne de le faire de toute façon (sauf pour des choses comme celle-ci :-), donc cela ne devrait pas donner vous beaucoup de problèmes.
Cas 2: utilisation d'un type dans une méthode
Exemple:
Pour vérifier cela, nous n'avons qu'une seule option: décompiler la classe, vérifier tous les jetons membres qui sont utilisés et si l'un d'entre eux est de type générique - vérifier les arguments.
Cas 3: Réflexion, construction générique d'exécution
Exemple:
Je suppose qu'il est théoriquement possible de vérifier cela avec des astuces similaires à celles du cas (2), mais sa mise en œuvre est beaucoup plus difficile (vous devez vérifier si elle
MakeGenericType
est appelée dans un chemin de code). Je n'entrerai pas dans les détails ici ...Cas 4: Réflexion, RTTI d'exécution
Exemple:
C'est le pire des cas et comme je l'ai expliqué avant généralement une mauvaise idée à mon humble avis. Quoi qu'il en soit, il n'y a aucun moyen pratique de comprendre cela à l'aide de chèques.
Tester le lot
Créer un programme qui teste les cas (1) et (2) se traduira par quelque chose comme ceci:
Utiliser le code
Eh bien, c'est la partie facile :-)
la source
Vous ne pouvez pas faire cela dans aucune version publiée de C #, ni dans le prochain C # 4.0. Ce n'est pas non plus une limitation C # - il n'y a pas de contrainte "d'interface" dans le CLR lui-même.
la source
Si possible, je suis allé avec une solution comme celle-ci. Cela ne fonctionne que si vous voulez que plusieurs interfaces spécifiques (par exemple celles auxquelles vous avez accès à la source) soient passées en tant que paramètre générique, pas n'importe laquelle.
IInterface
.IInterface
Dans la source, cela ressemble à ceci:
Toute interface que vous souhaitez passer en tant que paramètre générique:
IInterface:
La classe sur laquelle vous souhaitez mettre la contrainte de type:
la source
T
n'est pas contraint aux interfaces, il est contraint à tout ce qui implémenteIInterface
- ce que n'importe quel type peut faire s'il le veut, par exemplestruct Foo : IInterface
puisque votreIInterface
est probablement public (sinon tout ce qui l'accepte devrait être interne).Ce que vous avez choisi est le mieux que vous puissiez faire:
la source
J'ai essayé de faire quelque chose de similaire et j'ai utilisé une solution de contournement: j'ai pensé à un opérateur implicite et explicite sur la structure: L'idée est d'envelopper le Type dans une structure qui peut être convertie implicitement en Type.
Voici une telle structure:
public struct InterfaceType {private Type _type;
}
utilisation de base:
Vous devez imaginer votre propre mécanisme autour de cela, mais un exemple pourrait être une méthode prenant un InterfaceType en paramètre au lieu d'un type
Une méthode à remplacer qui devrait renvoyer des types d'interface:
Il y a peut-être des choses à faire avec les génériques aussi, mais je n'ai pas essayé
J'espère que cela peut aider ou donner des idées :-)
la source
Solution A: Cette combinaison de contraintes doit garantir qu'il
TInterface
s'agit d'une interface:Il nécessite une structure unique en
TStruct
tant que témoin pour prouver qu'ilTInterface
s'agit d'une structure.Vous pouvez utiliser une structure unique comme témoin pour tous vos types non génériques:
Solution B: Si vous ne souhaitez pas créer de structures en tant que témoins, vous pouvez créer une interface
et utilisez une contrainte:
Implémentation pour les interfaces:
Cela résout certains des problèmes, mais nécessite la confiance que personne n'implémente
ISInterface<T>
pour les types non-interface, mais c'est assez difficile à faire accidentellement.la source
Utilisez plutôt une classe abstraite. Donc, vous auriez quelque chose comme:
la source