Existe-t-il une contrainte qui restreint ma méthode générique aux types numériques ?

StackOverflow https://stackoverflow.com/questions/32664

  •  09-06-2019
  •  | 
  •  

Question

Quelqu'un peut-il me dire s'il existe un moyen de limiter un argument de type générique avec les génériques T à seulement :

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

Je suis au courant du where mot-clé, mais je ne trouve pas d'interface pour seulement ces types,

Quelque chose comme:

static bool IntegerFunction<T>(T value) where T : INumeric 
Était-ce utile?

La solution

C# ne prend pas en charge cela.Hejlsberg a décrit les raisons pour lesquelles cette fonctionnalité n'a pas été implémentée. dans une interview avec Bruce Eckel:

Et il n’est pas évident que la complexité supplémentaire justifie le faible rendement obtenu.Si quelque chose que vous souhaitez faire n'est pas directement pris en charge dans le système de contraintes, vous pouvez le faire avec un modèle d'usine.Tu pourrais avoir un Matrix<T>, par exemple, et en ce sens Matrix vous souhaitez définir une méthode de produit scalaire.Bien sûr, cela signifie que vous devez en fin de compte comprendre comment multiplier deux Ts, mais on ne peut pas dire cela comme une contrainte, du moins pas si T est int, double, ou float.Mais ce que vous pourriez faire, c'est demander à votre Matrix prendre comme argument un Calculator<T>, et en Calculator<T>, ont une méthode appelée multiply.Vous allez mettre en œuvre cela et vous le transmettez au Matrix.

Cependant, cela conduit à un code assez compliqué, dans lequel l'utilisateur doit fournir son propre code. Calculator<T> mise en œuvre, pour chaque T qu'ils veulent utiliser.Tant qu'il n'est pas nécessaire qu'il soit extensible, c'est-à-diresi vous souhaitez simplement prendre en charge un nombre fixe de types, tels que int et double, vous pouvez vous en sortir avec une interface relativement simple :

var mat = new Matrix<int>(w, h);

(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 types personnalisés. Calculator instances.Par exemple, pour instancier une matrice qui utilise une implémentation personnalisée à virgule flottante décimale, DFP, il faudrait écrire ce code :

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

… et mettre en œuvre tous les membres pour DfpCalculator : ICalculator<DFP>.

Une alternative, qui partage malheureusement les mêmes limites, consiste à travailler avec des classes politiques, comme indiqué dans la réponse de Sergey Shandar.

Autres conseils

Compte tenu de la popularité de cette question et de l'intérêt derrière une telle fonction, je suis surpris de constater qu'il n'y a pas encore de réponse concernant T4.

Dans cet exemple de code, je vais montrer un exemple très simple de la façon dont vous pouvez utiliser le puissant moteur de création de modèles pour faire ce que le compilateur fait en coulisse avec les génériques.

Au lieu de passer par des obstacles et de sacrifier la certitude au moment de la compilation, vous pouvez simplement générer la fonction souhaitée pour chaque type que vous aimez et l'utiliser en conséquence (au moment de la compilation !).

Pour ce faire :

  • Créer un nouveau Modèle de texte fichier appelé GénériqueNumberMethodTemplate.tt.
  • Supprimez le code généré automatiquement (vous en conserverez la majeure partie, mais certains ne seront pas nécessaires).
  • Ajoutez l'extrait suivant :
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}

C'est ça.Vous avez terminé maintenant.

Enregistrer ce fichier le compilera automatiquement dans ce fichier source :

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}

Dans ton main méthode, vous pouvez vérifier que vous avez une certitude au moment de la compilation :

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

enter image description here

Je vais anticiper une remarque :non, ce n'est pas une violation du principe DRY.Le principe DRY est là pour empêcher les gens de dupliquer le code à plusieurs endroits, ce qui rendrait l'application difficile à maintenir.

Ce n'est pas du tout le cas ici :si vous souhaitez un changement alors vous pouvez simplement changer le modèle (une source unique 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 dans laquelle 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 :

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

Soyons honnêtes:C'est plutôt cool.

Clause de non-responsabilité:cet échantillon a été fortement influencé par Métaprogrammation dans .NET par Kevin Hazzard et Jason Bock, Manning Publications.

Il n'y a aucune contrainte pour cela.C'est un réel problème pour quiconque souhaite utiliser des génériques pour des calculs numériques.

J'irais plus loin et je dirais que nous avons besoin

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

Ou même

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

Malheureusement, vous n'avez que les interfaces, les classes de base et les mots-clés struct (doit être de type valeur), class (doit être de type référence) et new() (doit avoir un constructeur par défaut)

Vous pouvez envelopper le numéro dans autre chose (semblable à INullable<T>) comme ici sur codeproject.


Vous pouvez appliquer la restriction au moment de l'exécution (en réfléchissant aux opérateurs ou en vérifiant les types), mais cela perd l'avantage d'avoir le générique en premier lieu.

Solution de contournement à l'aide de stratégies :

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

Algorithmes :

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

Usage:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

La solution est sécurisée au moment de la compilation. Cadre CityLizard 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 CityLizard.Politique.I structure.

Cette question est un peu une question de FAQ, donc je la publie en tant que wiki (puisque j'ai déjà publié un article similaire, mais celui-ci est plus ancien) ;de toute façon...

Quelle version de .NET utilisez-vous ?Si vous utilisez .NET 3.5, j'ai un implémentation d'opérateurs génériques dans DiversUtil (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 (comme DateTime + 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.

Voici quelques informations supplémentaires sur les raisons pour lesquelles cela est délicat ici.

Vous voudrez peut-être aussi savoir que dynamic (4.0) résout en quelque sorte ce problème indirectement également - c'est-à-dire

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

Malheureusement, vous ne pouvez spécifier struct que dans la clause Where dans ce cas.Cela semble étrange que vous ne puissiez pas spécifier Int16, Int32, etc.plus précisément, mais je suis sûr qu'il y a une raison d'implémentation profonde qui sous-tend la décision de ne pas autoriser les types de valeur dans une clause Where.

Je suppose que la seule solution est d'effectuer une vérification à l'exécution, ce qui empêche malheureusement que le problème soit détecté au moment de la compilation.Cela donnerait quelque chose comme : -

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

Ce qui est un peu moche, je sais, mais fournit au moins 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 d'y parvenir.

Le plus proche que vous puissiez faire est probablement

static bool IntegerFunction<T>(T value) where T: struct

Je ne sais pas si vous pouvez faire ce qui suit

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

Pour quelque chose d'aussi spécifique, pourquoi ne pas simplement avoir des surcharges pour chaque type, la liste est si courte et elle aurait peut-être moins d'empreinte mémoire.

Il n'existe aucun moyen de limiter les modèles à des 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.

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

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 conversions parasites.Donc, quelque chose se résoudrait dans le compilateur pour

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

J'ai créé une petite fonctionnalité de bibliothèque pour résoudre ces problèmes :

Au lieu de:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

Vous pourriez écrire :

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

Vous pouvez trouver le code source ici : https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

Je me demandais la même chose que Samjudson, pourquoi seulement des nombres entiers ?et si tel est le cas, vous souhaiterez peut-être créer une classe d'assistance ou quelque chose comme ça pour contenir tous les types souhaités.

Si tout ce que vous voulez, ce sont 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.

Quel est l’intérêt de l’exercice ?

Comme certains l'ont déjà souligné, vous pourriez avoir une fonction non générique prenant le plus gros élément, et le compilateur convertirait automatiquement les plus petits entiers pour vous.

static bool IntegerFunction(Int64 value) { }

Si votre fonction se trouve sur un chemin critique en termes de performances (très peu probable, IMO), vous pouvez fournir des surcharges pour toutes les fonctions nécessaires.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

J'en utiliserais un générique que vous pourriez gérer en externe...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}

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 de 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

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

J'ai contourné ce problème en utilisant la saisie dynamique d'exécution .net4.

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

Les deux choses à propos de l'utilisation dynamic sont

  1. Performance.Tous les types de valeur sont encadrés.
  2. Erreurs d'exécution.Vous « battez » le compilateur, mais perdez la sécurité du type.Si l'opérateur n'est pas défini pour le type générique, une exception sera levée lors de l'exécution.

Il n’existe pas encore de « bonne » solution à ce problème.Cependant, vous pouvez restreindre considérablement l'argument de type pour exclure de nombreuses erreurs d'adaptation pour votre hypotétique contrainte 'INumeric', comme Haacked l'a montré ci-dessus.

static bool IntegerFunction<T>(T value) où T :Icomparable, iformattable, iconvertible, icomparableu003CT> , Iéquatableu003CT> , struct {...

Les types primitifs numériques .NET ne partagent aucune interface commune qui permettrait de les utiliser pour des calculs.Il serait possible de définir vos propres interfaces (par ex. ISignedWholeNumber) qui effectuerait de telles opérations, définirait des structures contenant un seul Int16, Int32, etc.et implémenter ces interfaces, puis disposer de méthodes qui acceptent les types génériques contraints à ISignedWholeNumber, mais devoir convertir des valeurs numériques en vos types de structure serait probablement une nuisance.

Une approche alternative serait de définir une classe statique Int64Converter<T> avec une propriété statique bool Available {get;}; et des délégués statiques pour Int64 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 des délégués pour les types connus, et éventuellement utiliser Reflection pour tester si le type T implémente des méthodes avec les noms et signatures propres (au cas où il s'agirait d'une structure contenant un Int64 et représente un nombre, mais a une coutume ToString() méthode).Cette approche perdrait les avantages associés à la vérification de type au moment de la compilation, mais permettrait toujours d'éviter les opérations de boxing et chaque type ne devrait être "vérifié" qu'une seule fois.Après cela, les opérations associées à ce type seraient remplacées par une répartition de délégué.

Si vous utilisez .NET 4.0 et versions ultérieures, vous pouvez simplement utiliser dynamique comme argument de méthode et vérifier en exécution que le passé dynamique Le type d’argument est de type numérique/entier.

Si le type de passé dynamique est pas type numérique/entier puis lève une exception.

Un exemple court le code qui implémente l'idée ressemble à ceci :

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

Bien sûr, cette solution fonctionne uniquement au moment de l’exécution mais jamais au moment de la compilation.

Si vous voulez une solution qui fonctionne toujours au moment de la compilation et jamais au moment de l'exécution, vous devrez envelopper le dynamique avec une structure/classe publique dont la surcharge publique les constructeurs acceptent uniquement les arguments des types souhaités et donnent le nom approprié à la structure/classe.

Il est logique que l'emballage dynamique est toujours privé membre de la classe/struct et c'est le seul membre de la struct/class et le nom du seul membre de la struct/class est "value".

Vous devrez également définir et mettre en œuvre publique méthodes et/ou opérateurs qui fonctionnent avec les types souhaités pour le membre dynamique privé de la classe/structure si nécessaire.

Il est également logique que la structure/classe ait spécial/unique constructeur qui accepte dynamique comme argument qui initialise, c'est uniquement 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 de IntegerFunction de l'argument comme étant la classe/structure qui a été définie.

Un exemple long le code qui implémente l'idée ressemble à ceci :

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

Notez que pour utiliser dynamique dans votre code vous devez Ajouter une référence à Microsoft.CSharp

Si la version du .NET framework est inférieure/sous/inférieure à 4.0 et dynamique n'est pas défini dans cette version, vous devrez alors utiliser objet à la place et effectuez un cast vers le type entier, ce qui pose problème, je vous recommande donc d'utiliser au moins .NET 4.0 ou plus récent si vous le pouvez afin de pouvoir utiliser dynamique au lieu de objet.

Si tout ce que tu veux c'est utiliser un type numérique, vous pourriez 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

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

tu aurais pu

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

Cela pourrait vous permettre de passer facilement de double à int ou d'autres si nécessaire, mais vous ne pourrez pas l'utiliser ComputeSomething avec double et int dans le même programme.

Mais pourquoi ne pas tout remplacer double à int alors?Parce que votre méthode voudra peut-être utiliser un double si l'entrée est double ou int.L'alias permet de savoir exactement quelle variable utilise le dynamique taper.

J'ai eu une situation similaire où je devais gérer des types numériques et des chaînes ;cela semble un mélange un peu bizarre mais voilà.

Encore une fois, comme beaucoup de gens, j’ai examiné les contraintes et j’ai proposé un certain nombre d’interfaces qu’il devait prendre en charge.Cependant, a) il n'était pas étanche à 100 % et b) toute personne nouvelle regardant cette longue liste de contraintes serait immédiatement très confuse.

Mon approche consistait donc à mettre toute ma logique dans une méthode générique sans contraintes, mais à rendre cette méthode générique privée.Je l'ai ensuite exposé avec des méthodes publiques, l'une gérant explicitement le type que je voulais gérer - à mon avis, le code est propre et explicite, par ex.

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}

Il n'y a pas d'interface ou de classe de base unique dont ils héritent tous (qui ne sont pas également hérités par d'autres classes), donc la réponse simple est non.

Je me demande cependant pourquoi c'est un problème.Que voulez-vous faire dans votre classe IntegerFunction qui ne peut être fait qu'avec des nombres entiers ?

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, puisque 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 type spécifique le plus grand permettra au programme de transtyper automatiquement les types de données les plus petits.(c'est à dire.passer un Int16 sera automatiquement converti en Int64 lors de l'appel).

Si vous effectuez différentes opérations en fonction de la taille réelle de l'int transmis à la fonction, je pense que vous devriez sérieusement reconsidérer même votre tentative de faire ce que vous faites.Si vous devez tromper le langage, vous devriez réfléchir un peu plus à ce que vous essayez d'accomplir plutôt qu'à la manière de faire ce que vous voulez.

À défaut de tout le reste, un paramètre de type Object pourrait être utilisé et vous devrez alors vérifier le type du paramètre et prendre l'action appropriée ou lever une exception.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top