Quelqu'un peut-il me dire s'il existe un moyen avec les génériques de limiter un argument de type générique T
à seulement:
Int16
Int32
Int64
UInt16
UInt32
UInt64
Je connais le where
mot clé, mais je ne trouve pas d'interface uniquement pour ces types,
Quelque chose comme:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Corin Blaikie
la source
la source
Réponses:
C # ne prend pas en charge cela. Hejlsberg a décrit les raisons de ne pas mettre en œuvre la fonctionnalité dans une interview avec Bruce Eckel :
Cependant, cela conduit à un code assez alambiqué, où l'utilisateur doit fournir sa propre
Calculator<T>
implémentation, pour chacunT
qu'il souhaite utiliser. Tant qu'il ne doit pas être extensible, c'est-à-dire si vous souhaitez simplement prendre en charge un nombre fixe de types, tels queint
etdouble
, vous pouvez vous en sortir avec une interface relativement simple:( Implémentation minimale dans un GitHub Gist. )
Cependant, dès que vous souhaitez que l'utilisateur puisse fournir ses propres types personnalisés, vous devez ouvrir cette implémentation afin que l'utilisateur puisse fournir ses propres
Calculator
instances. Par exemple, pour instancier une matrice qui utilise une implémentation décimale décimale flottante personnaliséeDFP
, vous devez écrire ce code:… Et implémentez tous les membres de
DfpCalculator : ICalculator<DFP>
.Une alternative, qui partage malheureusement les mêmes limites, est de travailler avec des classes de politique, comme discuté dans la réponse de Sergey Shandar .
la source
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
code (puisque l'interview a été donnée bien avant l'existence duExpressions
cadre, même si l'on pouvait utilisation du coursReflection.Emit
) - et je serais vraiment intéressé par sa solution.Compte tenu de la popularité de cette question et de l'intérêt derrière une telle fonction, je suis surpris de voir qu'il n'y a pas encore de réponse concernant T4.
Dans cet exemple de code, je vais démontrer un exemple très simple de la façon dont vous pouvez utiliser le puissant moteur de modélisation pour faire ce que le compilateur fait à peu près en arrière-plan avec des génériques.
Au lieu de passer par des cercles et de sacrifier la certitude au moment de la compilation, vous pouvez simplement générer la fonction que vous voulez pour chaque type que vous aimez et l'utiliser en conséquence (au moment de la compilation!).
Pour ce faire:
C'est ça. Vous avez terminé maintenant.
L'enregistrement de ce fichier le compilera automatiquement dans ce fichier source:
Dans votre
main
méthode, vous pouvez vérifier que vous avez une certitude au moment de la compilation:Je vais devancer une remarque: non, ce n'est pas une violation du principe DRY. Le principe DRY est là pour empêcher les gens de dupliquer du code à plusieurs endroits, ce qui rendrait l'application difficile à maintenir.
Ce n'est pas du tout le cas ici: si vous voulez un changement, vous pouvez simplement changer le modèle (une seule source pour toute votre génération!) Et c'est fait.
Afin de l'utiliser avec vos propres définitions personnalisées, ajoutez une déclaration d'espace de noms (assurez-vous qu'elle est la même que celle où vous définirez votre propre implémentation) à votre code généré et marquez la classe comme
partial
. Ensuite, ajoutez ces lignes à votre fichier modèle afin qu'il soit inclus dans la compilation éventuelle:Soyons honnêtes: c'est plutôt cool.
Avertissement: cet échantillon a été fortement influencé par la métaprogrammation en .NET par Kevin Hazzard et Jason Bock, Manning Publications .
la source
T
qui est ou hérite des différentesIntX
classes? J'aime cette solution car elle fait gagner du temps, mais pour qu'elle résout à 100% le problème (bien qu'elle ne soit pas aussi agréable que si C # avait un support pour ce type de contrainte, intégré), chacune des méthodes générées devrait toujours être générique pour que ils peuvent renvoyer un objet d'un type qui hérite d'une desIntXX
classes.IntXX
types sont des structures, ce qui signifie qu'ils ne prennent pas en charge l'héritage en premier lieu . Et même si c'était le cas, le principe de substitution de Liskov (que vous connaissez peut-être dans l'idiome SOLID) s'applique: si la méthode est définie commeX
etY
est un enfant de,X
par définition, toutY
devrait pouvoir être transmis à cette méthode comme substitut de son type de base.Il n'y a aucune contrainte pour cela. C'est un vrai problème pour quiconque souhaite utiliser des génériques pour les calculs numériques.
J'irais plus loin et je dirais que nous avons besoin
Ou même
Malheureusement, vous n'avez que des interfaces, des classes de base et les mots-clés
struct
(doit être de type valeur),class
(doit être de type référence) etnew()
(doit avoir un constructeur par défaut)Vous pouvez envelopper le nombre dans quelque chose d'autre (similaire à
INullable<T>
) comme ici sur codeproject .Vous pouvez appliquer la restriction lors de l'exécution (en réfléchissant pour les opérateurs ou en recherchant les types), mais cela perd l'avantage d'avoir le générique en premier lieu.
la source
where T : operators( +, -, /, * )
est C # légal? Désolé pour la question de débutant.where T : operators( +, -, /, * )
, mais pas.Solution de contournement à l'aide de stratégies:
Algorithmes:
Usage:
La solution est sûre au moment de la compilation. CityLizard Framework fournit une version compilée pour .NET 4.0. Le fichier est lib / NETFramework4.0 / CityLizard.Policy.dll.
Il est également disponible dans Nuget: https://www.nuget.org/packages/CityLizard/ . Voir la structure CityLizard.Policy.I .
la source
struct
? que se passe-t-il si j'utilise plutôt la classe singleton et que je change l'instance enpublic static NumericPolicies Instance = new NumericPolicies();
puis ajoute ce constructeurprivate NumericPolicies() { }
.T Add<T> (T t1, T t2)
, maisSum()
ne fonctionne que lorsqu'elle peut récupérer son propre type de T à partir de ses paramètres, ce qui n'est pas possible quand il est intégré dans une autre fonction générique.Cette question est un peu une FAQ, donc je poste ceci en tant que wiki (puisque j'ai posté similaire auparavant, mais c'est une ancienne); en tous cas...
Quelle version de .NET utilisez-vous? Si vous utilisez .NET 3.5, j'ai une implémentation d'opérateurs génériques dans MiscUtil (gratuit, etc.).
Cela a des méthodes comme
T Add<T>(T x, T y)
, et d'autres variantes pour l'arithmétique sur différents types (commeDateTime + TimeSpan
).De plus, cela fonctionne pour tous les opérateurs intégrés, levés et sur mesure, et met en cache le délégué pour les performances.
Un peu d' histoire supplémentaire pourquoi est - ce délicat est ici .
Vous voudrez peut-être également savoir que
dynamic
(4.0) résout ce problème indirectement aussi - c.-à-d.la source
Malheureusement, vous ne pouvez spécifier la structure que dans la clause where dans cette instance. Il semble étrange que vous ne puissiez pas spécifier spécifiquement Int16, Int32, etc., mais je suis sûr qu'il existe une raison profonde d'implémentation qui sous-tend la décision de ne pas autoriser les types de valeur dans une clause where.
Je suppose que la seule solution consiste à effectuer une vérification de l'exécution, ce qui empêche malheureusement le problème d'être détecté au moment de la compilation. Cela irait quelque chose comme: -
Ce qui est un peu moche je sais, mais au moins fournit les contraintes requises.
J'examinerais également les implications possibles en termes de performances pour cette implémentation, il existe peut-être un moyen plus rapide.
la source
// Rest of code...
peut ne pas compiler si cela dépend des opérations définies par les contraintes.// Rest of code...
commevalue + value
ouvalue * value
, vous avez une erreur de compilation.Probablement le plus proche que vous puissiez faire est
Je ne sais pas si vous pourriez faire ce qui suit
Pour quelque chose de si spécifique, pourquoi ne pas simplement avoir des surcharges pour chaque type, la liste est si courte et elle aurait peut-être moins d'espace mémoire.
la source
À partir de C # 7.3, vous pouvez utiliser une approximation plus étroite - la contrainte non managée pour spécifier qu'un paramètre de type est un type non managé non pointeur, non nullable .
La contrainte non gérée implique la contrainte struct et ne peut pas être combinée avec la contrainte struct ou new ().
Un type est un type non géré s'il s'agit de l'un des types suivants:
Pour restreindre davantage et éliminer les types de pointeur et définis par l'utilisateur qui n'implémentent pas IComparable, ajoutez IComparable (mais l'énumération est toujours dérivée de IComparable, alors restreignez l'énumération en ajoutant IEquatable <T>, vous pouvez aller plus loin en fonction de vos circonstances et ajouter des interfaces supplémentaires. non managé permet de garder cette liste plus courte):
la source
DateTime
tombe sousunmanaged, IComparable, IEquatable<T>
contrainte ..Il n'y a aucun moyen de restreindre les modèles aux types, mais vous pouvez définir différentes actions en fonction du type. Dans le cadre d'un package numérique générique, j'avais besoin d'une classe générique pour ajouter deux valeurs.
Notez que les types de fichiers sont évalués au moment de la compilation, donc les instructions if seront supprimées par le compilateur. Le compilateur supprime également les transtypages parasites. Donc, quelque chose résoudrait dans le compilateur pour
la source
J'ai créé une petite fonctionnalité de bibliothèque pour résoudre ces problèmes:
Au lieu de:
Vous pourriez écrire:
Vous pouvez trouver le code source ici: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
la source
Je me demandais la même chose que samjudson, pourquoi seulement aux entiers? et si c'est le cas, vous voudrez peut-être créer une classe d'assistance ou quelque chose comme ça pour contenir tous les types que vous voulez.
Si vous ne voulez que des entiers, n'utilisez pas de générique, ce n'est pas générique; ou mieux encore, rejetez tout autre type en vérifiant son type.
la source
Il n'y a pas encore de «bonne» solution pour cela. Cependant, vous pouvez restreindre considérablement l'argument type pour exclure de nombreuses inadaptations pour votre contrainte hypotétique 'INumeric' comme Haacked l'a montré ci-dessus.
static bool IntegerFunction <T> (valeur T) où T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...
la source
Si vous utilisez .NET 4.0 et versions ultérieures, vous pouvez simplement utiliser dynamic comme argument de méthode et vérifier au moment de l'exécution que le type d'argument dynamique transmis est de type numérique / entier.
Si le type de la dynamique transmise n'est pas de type numérique / entier, lève une exception.
Un exemple de code court qui implémente l'idée est quelque chose comme:
Bien sûr, cette solution ne fonctionne qu'en exécution mais jamais en compilation.
Si vous voulez une solution qui fonctionne toujours au moment de la compilation et jamais au moment de l'exécution, vous devrez encapsuler la dynamique avec une structure / classe publique dont les constructeurs publics surchargés acceptent uniquement les arguments des types souhaités et donnent à la structure / classe le nom approprié.
Il est logique que la dynamique encapsulée soit toujours un membre privé de la classe / struct et qu'elle soit le seul membre de la struct / classe et que le nom du seul membre de la struct / classe soit "value".
Vous devrez également définir et implémenter des méthodes publiques et / ou des opérateurs qui fonctionnent avec les types souhaités pour le membre dynamique privé de la classe / struct si nécessaire.
Il est également logique que la structure / classe ait un constructeur spécial / unique qui accepte la dynamique comme argument qui initialise ce n'est qu'un membre dynamique privé appelé "valeur" mais le modificateur de ce constructeur est privé bien sûr.
Une fois que la classe / structure est prête, définissez le type d'argument IntegerFunction comme étant la classe / structure qui a été définie.
Un exemple de code long qui implémente l'idée est quelque chose comme:
Notez que pour utiliser la dynamique dans votre code, vous devez ajouter une référence à Microsoft.CSharp
Si la version du framework .NET est inférieure / inférieure / inférieure à 4.0 et que la dynamique n'est pas définie dans cette version, vous devrez utiliser un objet à la place et effectuer une conversion vers le type entier, ce qui est un problème, donc je vous recommande d'utiliser à moins .NET 4.0 ou plus récent si vous le pouvez pour pouvoir utiliser dynamique au lieu d' objet .
la source
Malheureusement .NET ne fournit pas un moyen de le faire en mode natif.
Pour résoudre ce problème, j'ai créé la bibliothèque OSS Genumerics qui fournit la plupart des opérations numériques standard pour les types numériques intégrés suivants et leurs équivalents annulables avec la possibilité d'ajouter la prise en charge d'autres types numériques.
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,float
,double
,decimal
EtBigInteger
Les performances sont équivalentes à une solution spécifique au type numérique vous permettant de créer des algorithmes numériques génériques efficaces.
Voici un exemple d'utilisation du code.
la source
Quel est l'intérêt de l'exercice?
Comme les gens l'ont déjà fait remarquer, vous pourriez avoir une fonction non générique prenant le plus gros élément, et le compilateur convertira automatiquement les plus petits pouces pour vous.
Si votre fonction est sur un chemin critique en termes de performances (très peu probable, IMO), vous pouvez fournir des surcharges pour toutes les fonctions nécessaires.
la source
J'utiliserais un générique que vous pourriez gérer en externe ...
la source
Cette limitation m'a affecté lorsque j'ai essayé de surcharger les opérateurs pour les types génériques; comme il n'y avait pas de contrainte "INumeric", et pour une multitude d'autres raisons que les bonnes personnes sur stackoverflow sont heureuses de fournir, les opérations ne peuvent pas être définies sur des types génériques.
Je voulais quelque chose comme
J'ai contourné ce problème en utilisant la saisie dynamique de runtime .net4.
Les deux choses sur l'utilisation
dynamic
sontla source
Les types primitifs numériques .NET ne partagent aucune interface commune qui leur permettrait d'être utilisés pour les calculs. Il serait possible de définir vos propres interfaces (par exemple
ISignedWholeNumber
) qui effectuer de telles opérations, définir des structures qui contiennent un seulInt16
,Int32
etc. et mettre en œuvre ces interfaces, et ont des méthodes qui acceptent les types génériques de contraintesISignedWholeNumber
, mais avoir à convertir des valeurs numériques à vos types de structure serait probablement une nuisance.Une autre approche serait de définir la classe statique
Int64Converter<T>
avec une propriété statiquebool Available {get;};
et les délégués statiques pourInt64 GetInt64(T value)
,T FromInt64(Int64 value)
,bool TryStoreInt64(Int64 value, ref T dest)
. Le constructeur de classe pourrait être codé en dur pour charger les délégués pour les types connus, et éventuellement utiliser Reflection pour tester si le typeT
implémente des méthodes avec les noms et signatures appropriés (au cas où il s'agirait d'une structure qui contient unInt64
et représente un nombre, mais qui a uneToString()
méthode personnalisée ). Cette approche perdrait les avantages associés à la vérification de type au moment de la compilation, mais réussirait à éviter les opérations de boxe et chaque type n'aurait à être "vérifié" qu'une seule fois. Après cela, les opérations associées à ce type seraient remplacées par une répartition des délégués.la source
Int64
résultat, mais ne fournit pas un moyen par lequel par exemple un entier de type arbitraire pourrait être incrémenté pour produire un autre entier du même type .J'ai eu une situation similaire où j'avais besoin de gérer les types et les chaînes numériques; semble un peu bizarre, mais voilà.
Encore une fois, comme beaucoup de gens, j'ai examiné les contraintes et trouvé un tas d'interfaces qu'il devait prendre en charge. Cependant, a) ce n'était pas étanche à 100% et b), toute personne qui regarde cette longue liste de contraintes serait immédiatement très confuse.
Donc, mon approche était de mettre toute ma logique dans une méthode générique sans contraintes, mais de rendre cette méthode générique privée. Je l'ai ensuite exposé avec des méthodes publiques, une gérant explicitement le type que je voulais gérer - à mon avis, le code est propre et explicite, par exemple
la source
Si vous ne souhaitez utiliser qu'un seul type numérique , vous pouvez envisager de créer quelque chose de similaire à un alias en C ++ avec
using
.Donc, au lieu d'avoir le très générique
tu aurais pu
Cela pourrait vous permettre d'accéder facilement
double
àint
ou à d'autres si nécessaire, mais vous ne pourriez pas l'utiliserComputeSomething
avecdouble
etint
dans le même programme.Mais pourquoi pas remplacer tout
double
àint
alors? Parce que votre méthode peut vouloir utiliserdouble
si l'entrée estdouble
ouint
. L'alias vous permet de savoir exactement quelle variable utilise le type dynamique .la source
Le sujet est ancien mais pour les futurs lecteurs:
Cette fonctionnalité est étroitement liée à celle
Discriminated Unions
qui n'est pas implémentée en C # jusqu'à présent. J'ai trouvé son problème ici:https://github.com/dotnet/csharplang/issues/113
Ce problème est toujours ouvert et une fonctionnalité est prévue pour
C# 10
Nous devons donc encore attendre un peu plus, mais après la sortie, vous pouvez le faire de cette façon:
la source
Je pense que vous comprenez mal les génériques. Si l'opération que vous essayez d'effectuer n'est valable que pour des types de données spécifiques, vous ne faites pas quelque chose de "générique".
De plus, étant donné que vous souhaitez uniquement autoriser la fonction à fonctionner sur les types de données int, vous ne devriez pas avoir besoin d'une fonction distincte pour chaque taille spécifique. Le simple fait de prendre un paramètre dans le plus grand type spécifique permettra au programme de lui transférer automatiquement les types de données plus petits. (c'est-à-dire que passer un Int16 se convertira automatiquement en Int64 lors de l'appel).
Si vous effectuez différentes opérations en fonction de la taille réelle de l'int. Passé dans la fonction, je pense que vous devriez sérieusement reconsidérer même essayer de faire ce que vous faites. Si vous devez tromper la langue, vous devriez penser un peu plus à ce que vous essayez d'accomplir plutôt qu'à la façon de faire ce que vous voulez.
À défaut de quoi, un paramètre de type Object pourrait être utilisé et vous devrez alors vérifier le type du paramètre et prendre les mesures appropriées ou lever une exception.
la source