Esiste un vincolo che limita il mio metodo generico a tipi numerici?


364

Qualcuno può dirmi se c'è un modo con generics per limitare un argomento di tipo generico Ta solo:

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

Sono a conoscenza della whereparola chiave, ma non riesco a trovare un'interfaccia solo per questi tipi,

Qualcosa di simile a:

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

2
Ora ci sono varie proposte C # che consentirebbero di raggiungere questo obiettivo, ma AFAIK nessuna di queste è oltre le esplorazioni / discussioni preliminari. Vedi Esplorazione: forme ed estensioni , Esplorazione: ruoli, interfacce di estensione e membri dell'interfaccia statica , campione "Classi di tipo (alias concetti, vincoli generici strutturali)" e proposta: i tipi generici dovrebbero supportare gli operatori
Chris Yungmann,

Risposte:


140

C # non supporta questo. Hejlsberg ha descritto le ragioni per non aver implementato la funzione in un'intervista a Bruce Eckel :

E non è chiaro che la complessità aggiunta valga la piccola resa che si ottiene. Se qualcosa che si desidera fare non è direttamente supportato nel sistema di vincoli, è possibile farlo con un modello di fabbrica. Potresti avere un Matrix<T>, per esempio, e nel senso che Matrixvorresti definire un metodo di prodotto punto. Questo, naturalmente, che significa in ultima analisi, è necessario capire come moltiplicare due Ts, ma non si può dire che come un vincolo, almeno non se Tè int, doubleo float. Ma quello che potresti fare è avere il tuo Matrixtake come argomento a Calculator<T>, e in Calculator<T>, avere un metodo chiamato multiply. Lo metti in atto e lo passi al Matrix.

Tuttavia, questo porta a un codice abbastanza contorto, in cui l'utente deve fornire la propria Calculator<T>implementazione, per ciascuno di Tessi che desidera utilizzare. Fintanto che non deve essere estensibile, ad esempio se si desidera supportare un numero fisso di tipi, come inte double, è possibile cavarsela con un'interfaccia relativamente semplice:

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

( Implementazione minima in GitHub Gist. )

Tuttavia, non appena si desidera che l'utente sia in grado di fornire i propri tipi personalizzati, è necessario aprire questa implementazione in modo che l'utente possa fornire le proprie Calculatoristanze. Ad esempio, per creare un'istanza di una matrice che utilizza un'implementazione decimale in virgola mobile personalizzata DFP, è necessario scrivere questo codice:

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

... e implementare tutti i membri per DfpCalculator : ICalculator<DFP>.

Un'alternativa, che purtroppo condivide gli stessi limiti, è quella di lavorare con le classi politiche, come discusso nella risposta di Sergey Shandar .


25
tra l'altro, MiscUtil fornisce una classe generica che fa esattamente questo; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

1
@Mark: buon commento. Tuttavia, per essere chiari, non penso che Hejlsberg si riferisse alla generazione del codice come soluzione al problema come si fa nel Operator<T>codice (poiché l'intervista è stata rilasciata molto prima dell'esistenza del Expressionsframework, anche se si potrebbe uso del corso Reflection.Emit) - e sarei davvero interessato alla sua soluzione alternativa.
Konrad Rudolph,

@Konrad Rudolph: Penso che questa risposta a una domanda simile spieghi la soluzione alternativa di Hejlsberg. L'altra classe generica è resa astratta. Poiché richiede l'implementazione dell'altra classe generica per ciascun tipo che si desidera supportare, si otterrà un codice duplicato, ma ciò significa che è possibile creare un'istanza della classe generica originale solo con un tipo supportato.
Ergwun,

14
Non sono d'accordo con la frase di Heijsberg "Quindi, in un certo senso, i modelli C ++ sono in realtà non tipizzati o tipizzati in modo approssimativo. Considerando che i generici C # sono fortemente tipizzati". Questo è davvero Marketing BS per promuovere C #. La tipizzazione forte / debole non ha a che fare con la qualità della diagnostica. Altrimenti: scoperta interessante.
Sebastian Mach,

100

Considerando la popolarità di questa domanda e l'interesse alla base di tale funzione, sono sorpreso di vedere che non esiste ancora una risposta su T4.

In questo codice di esempio mostrerò un esempio molto semplice di come è possibile utilizzare il potente motore di template per fare ciò che il compilatore fa praticamente dietro le quinte con generici.

Invece di passare attraverso i cerchi e sacrificare la certezza in fase di compilazione puoi semplicemente generare la funzione che desideri per ogni tipo che ti piace e usarla di conseguenza (in fase di compilazione!).

Per fare ciò:

  • Creare un nuovo file di modello di testo chiamato GenericNumberMethodTemplate.tt .
  • Rimuovi il codice generato automaticamente (ne manterrai la maggior parte, ma alcuni non sono necessari).
  • Aggiungi il seguente frammento:
<#@ 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;
        }
    <#
    } #>
}

Questo è tutto. Hai finito adesso.

Il salvataggio di questo file lo compilerà automaticamente in questo file di origine:

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;
    }
}

Nel tuo mainmetodo puoi verificare di avere la certezza in fase di compilazione:

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

inserisci qui la descrizione dell'immagine

Anticiperò un'osservazione: no, questa non è una violazione del principio DRY. Il principio DRY è lì per impedire alle persone di duplicare il codice in più punti che renderebbe difficile mantenere l'applicazione.

Questo non è affatto il caso qui: se vuoi un cambiamento, puoi semplicemente cambiare il modello (una singola fonte per tutta la tua generazione!) Ed è fatto.

Per usarlo con le tue definizioni personalizzate, aggiungi una dichiarazione dello spazio dei nomi (assicurati che sia la stessa di quella in cui definirai la tua implementazione) al codice generato e contrassegna la classe come partial. Successivamente, aggiungi queste righe al tuo file modello in modo che sia incluso nell'eventuale compilazione:

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

Siamo onesti: è abbastanza bello.

Disclaimer: questo esempio è stato fortemente influenzato dalla metaprogrammazione in .NET di Kevin Hazzard e Jason Bock, Manning Publications .


Questo è piuttosto interessante, ma sarebbe possibile modificare questa soluzione per fare in modo che i metodi accettino un tipo generico Tche è o eredita dalle varie IntXclassi? Mi piace questa soluzione perché consente di risparmiare tempo, ma al 100% risolve il problema (nonostante non sia bello come se C # avesse il supporto per questo tipo di vincolo, integrato) ciascuno dei metodi generati dovrebbe essere comunque generico in modo che possono restituire un oggetto di un tipo che eredita da una delle IntXXclassi.
Zachary Kniebel,

1
@ZacharyKniebel: i IntXXtipi sono strutture, il che significa che in primo luogo non supportano l'ereditarietà . E anche se lo facesse, si applica il principio di sostituzione di Liskov (che potresti conoscere dall'idioma SOLIDO): se il metodo è definito come Xed Yè figlio di Xallora per definizione, si Ydovrebbe poter passare a quel metodo come sostituto di il suo tipo di base.
Jeroen Vannevel,

1
Questa soluzione alternativa utilizzando le politiche stackoverflow.com/questions/32664/… utilizza T4 per generare classi.
Sergey Shandar,

2
+1 per questa soluzione poiché preserva l'efficienza operativa dei tipi integrali integrati, a differenza delle soluzioni basate su criteri. La chiamata di operatori CLR integrati (come Aggiungi) tramite un metodo aggiuntivo (possibilmente virtuale) può influire negativamente sulle prestazioni se utilizzata più volte (come nelle librerie matematiche). E poiché il numero di tipi integrali è costante (e non può essere ereditato da) è necessario rigenerare il codice solo per le correzioni di errori.
Attila Klenik,

1
Molto bello e stavo per iniziare a usarlo, poi mi sono ricordato di quanto dipenda da Resharper per il refactoring e non è possibile rinominare il refactor attraverso il modello T4. Non è critico ma vale la pena considerare.
bradgonesurfing,

86

Non ci sono vincoli per questo. È un vero problema per chiunque desideri utilizzare i generici per i calcoli numerici.

Andrei oltre e direi che ne abbiamo bisogno

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

O anche

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

Sfortunatamente hai solo interfacce, classi base e parole chiave struct(deve essere di tipo valore), class(deve essere di tipo di riferimento) e new()(deve avere un costruttore predefinito)

Potresti racchiudere il numero in qualcos'altro (simile a INullable<T>) come qui su codeproject .


È possibile applicare la restrizione in fase di esecuzione (riflettendo per gli operatori o verificando la presenza di tipi) ma ciò perde il vantaggio di avere il generico in primo luogo.


2
Mi chiedo se hai visto il supporto di MiscUtil per operatori generici ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

10
Sì - Jon Skeet mi ha indicato loro per qualcos'altro qualche tempo fa (ma dopo la risposta di quest'anno) - sono un'idea intelligente, ma mi piacerebbe comunque un adeguato supporto dei vincoli.
Keith,

1
Aspetta, where T : operators( +, -, /, * )è legale C #? Ci scusiamo per la domanda da principiante.
kdbanman,

@kdbanman Non la penso così. Keith sta dicendo che C # non supporta ciò che OP sta chiedendo e sta suggerendo che dovremmo essere in grado di fare where T : operators( +, -, /, * ), ma non è possibile.
AMTerp

62

Soluzione alternativa utilizzando i criteri:

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();
}

algoritmi:

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;
    }

}

Uso:

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 soluzione è sicura durante la compilazione. CityLizard Framework fornisce la versione compilata per .NET 4.0. Il file è lib / NETFramework4.0 / CityLizard.Policy.dll.

È disponibile anche in Nuget: https://www.nuget.org/packages/CityLizard/ . Vedi la struttura CityLizard.Policy.I .


Ho avuto problemi con questo modello quando ci sono meno argomenti di funzioni rispetto ai parametri generici. Aperto stackoverflow.com/questions/36048248/...
xvan

qualche motivo per cui usando struct? cosa succede se uso invece la classe singleton e cambio istanza in public static NumericPolicies Instance = new NumericPolicies();e quindi aggiungo questo costruttore private NumericPolicies() { }.
M.kazem Akhgary,

@ M.kazemAkhgary puoi usare il singleton. Preferisco struct. In teoria, può essere ottimizzato dal compilatore / CLR perché la struttura non contiene informazioni. In caso di singleton, passerai comunque un riferimento, che potrebbe aggiungere ulteriore pressione su GC. Un altro vantaggio è che la struttura non può essere nulla :-).
Sergey Shandar il

Stavo per dire che hai trovato una soluzione molto intelligente, ma la soluzione è troppo limitata per me: stavo per usarla T Add<T> (T t1, T t2), ma Sum()funziona solo quando può recuperare il proprio tipo di T dai suoi parametri, il che non è possibile quando è incorporato in un'altra funzione generica.
Tobias Knauss,

16

Questa domanda è un po 'una FAQ, quindi sto postando questo come wiki (dal momento che ho pubblicato simili prima, ma questa è una più vecchia); Comunque...

Quale versione di .NET stai usando? Se stai usando .NET 3.5, allora ho un'implementazione di operatori generici in MiscUtil (gratuito ecc.).

Questo ha metodi simili T Add<T>(T x, T y)e altre varianti dell'aritmetica su diversi tipi (come DateTime + TimeSpan).

Inoltre, questo funziona per tutti gli operatori integrati, sollevati e su misura e memorizza nella cache il delegato per le prestazioni.

Qualche ulteriore sfondo sul perché questo è difficile è qui .

Potresti anche sapere che dynamic(4.0) risolve indirettamente anche questo problema, ad es

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

14

Sfortunatamente, puoi specificare struct nella clausola where in questa istanza. Sembra strano che non sia possibile specificare Int16, Int32, ecc. In particolare, ma sono sicuro che ci sono alcuni motivi di implementazione alla base della decisione di non consentire tipi di valore in una clausola where.

Immagino che l'unica soluzione sia fare un controllo di runtime che sfortunatamente impedisce che il problema venga colto al momento della compilazione. Andrebbe qualcosa di simile: -

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...
}

Il che è un po 'brutto, lo so, ma almeno fornisce i vincoli richiesti.

Esaminerei anche le possibili implicazioni delle prestazioni per questa implementazione, forse c'è una via più veloce là fuori.


13
+1, tuttavia, // Rest of code...potrebbe non essere compilato se dipende dalle operazioni definite dai vincoli.
Nick,

1
Convert.ToIntXX (valore) potrebbe aiutare a compilare "// Resto del codice", almeno fino a quando anche il tipo restituito di IntegerFunction è di tipo T, quindi sei inserito. :-p
yoyo

-1; questo non funziona per il motivo fornito da @Nick. Nel momento in cui provi a fare qualsiasi operazione aritmetica in // Rest of code...like value + valueo value * value, hai un errore di compilazione.
Mark Amery,

13

Probabilmente il più vicino che puoi fare è

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

Non sono sicuro che potresti fare quanto segue

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

Per qualcosa di così specifico, perché non avere solo sovraccarichi per ogni tipo, l'elenco è così breve e avrebbe probabilmente meno spazio di memoria.


6

A partire da C # 7.3, è possibile utilizzare un'approssimazione più ravvicinata : il vincolo non gestito per specificare che un parametro di tipo è un tipo non gestito non puntatore, non annullabile .

class SomeGeneric<T> where T : unmanaged
{
//...
}

Il vincolo non gestito implica il vincolo struct e non può essere combinato con i vincoli struct o new ().

Un tipo è un tipo non gestito se è uno dei seguenti tipi:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal o bool
  • Qualsiasi tipo di enum
  • Qualsiasi tipo di puntatore
  • Qualsiasi tipo di struttura definito dall'utente che contiene solo campi di tipi non gestiti e, in C # 7.3 e precedenti, non è un tipo costruito (un tipo che include almeno un argomento di tipo)

Per limitare ulteriormente ed eliminare i tipi di puntatore e definiti dall'utente che non implementano IComparable aggiungere IComparable (ma enum è ancora derivato da IComparable, quindi limitare enum aggiungendo IEquatable <T>, è possibile andare oltre a seconda delle circostanze e aggiungere interfacce aggiuntive. unmanaged consente di ridurre questo elenco):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }

Bello, ma non abbastanza ... Ad esempio, DateTimecade sotto unmanaged, IComparable, IEquatable<T>vincolo ..
Adam Calvet Bohl,

Lo so, ma puoi andare oltre a seconda delle circostanze e aggiungere ulteriori interfacce. unmanaged consente di ridurre questo elenco. ho appena mostrato l'approccio, approssimazione usando non gestito. Nella maggior parte dei casi questo è sufficiente
Vlad Novakovsky

4

Non è possibile limitare i modelli ai tipi, ma è possibile definire diverse azioni in base al tipo. Come parte di un pacchetto numerico generico, avevo bisogno di una classe generica per aggiungere due valori.

    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;
        }
    }

Si noti che i tipi di test vengono valutati al momento della compilazione, quindi le istruzioni if ​​verranno rimosse dal compilatore. Il compilatore rimuove anche i cast spuri. Quindi qualcosa dovrebbe essere risolto nel compilatore

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

Grazie per aver fornito una soluzione empirica!
zsf222,

Non è lo stesso che creare lo stesso metodo per ogni tipo?
Luis

3

Ho creato una piccola funzionalità di libreria per risolvere questi problemi:

Invece di:

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.

Puoi scrivere:

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.

Puoi trovare il codice sorgente qui: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number


2

Mi chiedevo lo stesso di Samjudson, perché solo agli interi? e in tal caso, potresti voler creare una classe di supporto o qualcosa del genere per contenere tutti i tipi che desideri.

Se tutto ciò che desideri sono numeri interi, non utilizzare un generico, che non sia generico; o meglio ancora, rifiuta qualsiasi altro tipo verificandone il tipo.


2

Non esiste ancora una "buona" soluzione per questo. Tuttavia, puoi restringere in modo significativo l'argomento tipo per escludere molti svantaggi per il tuo ipotetico vincolo "INumeric" come ha mostrato Haacked sopra.

static bool IntegerFunction <T> (valore T) dove T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...


2

Se si utilizza .NET 4.0 e versioni successive, è possibile utilizzare dinamico come argomento del metodo e verificare in fase di esecuzione che il tipo di argomento dinamico passato sia di tipo numerico / intero.

Se il tipo di dinamica passata non è di tipo numerico / intero, genera un'eccezione.

Un breve codice di esempio che implementa l'idea è qualcosa di simile:

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.
    }

Naturalmente questa soluzione funziona solo in fase di esecuzione ma mai in fase di compilazione.

Se vuoi una soluzione che funzioni sempre in fase di compilazione e mai in fase di esecuzione, dovrai avvolgere la dinamica con una struttura / classe pubblica i cui costruttori pubblici sovraccarichi accettano solo argomenti dei tipi desiderati e danno il nome appropriato alla struttura / classe.

Ha senso che la dinamica di wrapping sia sempre membro privato della classe / struttura ed è l'unico membro della struttura / classe e il nome dell'unico membro della struttura / classe è "valore".

Dovrai anche definire e implementare metodi pubblici e / o operatori che lavorano con i tipi desiderati per il membro dinamico privato della classe / struttura, se necessario.

Ha anche senso che la struttura / classe abbia un costruttore speciale / unico che accetta la dinamica come argomento che inizializza solo il suo membro dinamico privato chiamato "valore", ma il modificatore di questo costruttore è ovviamente privato .

Una volta che la classe / struttura è pronta, definire il tipo di argomento di IntegerFunction in modo che sia quella classe / struttura che è stata definita.

Un codice lungo di esempio che implementa l'idea è qualcosa di simile:

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"
    }
}

Si noti che per utilizzare la dinamica nel codice è necessario aggiungere riferimento a Microsoft.CSharp

Se la versione di .NET framework è inferiore / inferiore / inferiore a 4.0 e la dinamica non è definita in quella versione, dovrai invece usare l' oggetto e fare il casting al tipo intero, il che è un problema, quindi ti consiglio di usare su almeno .NET 4.0 o più recente, se possibile, in modo da poter utilizzare dinamico anziché oggetto .


2

Purtroppo .NET non fornisce un modo per farlo in modo nativo.

Per risolvere questo problema ho creato la libreria OSS Genumerics che fornisce la maggior parte delle operazioni numeriche standard per i seguenti tipi numerici incorporati e i loro equivalenti nullable con la possibilità di aggiungere supporto per altri tipi numerici.

sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, EBigInteger

Le prestazioni sono equivalenti a una soluzione specifica di tipo numerico che consente di creare algoritmi numerici generici efficienti.

Ecco un esempio dell'uso del codice.

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}

1

Qual è il punto dell'esercizio?

Come già sottolineato dalle persone, potresti avere una funzione non generica che prende l'elemento più grande e il compilatore convertirà automaticamente ints più piccoli per te.

static bool IntegerFunction(Int64 value) { }

Se la tua funzione si trova su un percorso critico per le prestazioni (molto improbabile, IMO), potresti fornire sovraccarichi per tutte le funzioni necessarie.

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

1
Lavoro molto con metodi numerici. A volte voglio numeri interi ea volte voglio virgola mobile. Entrambi hanno versioni a 64 bit che sono ottimali per la velocità di elaborazione. La conversione tra questi è un'idea terribile in quanto vi sono perdite in ogni modo. Mentre tendo ad usare i doppi, a volte trovo meglio usare numeri interi a causa di come vengono usati altrove. Ma sarebbe molto bello quando scrivo un algoritmo per farlo una volta e lasciare la decisione del tipo all'altezza dei requisiti dell'istanza.
VoteCoffee,

1

Ne userei uno generico che potresti gestire esternamente ...

/// <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;
}

1

Questa limitazione mi ha colpito quando ho provato a sovraccaricare gli operatori per tipi generici; poiché non vi era alcun vincolo "INumeric" e per una serie di altri motivi che le brave persone su stackoverflow sono felici di fornire, le operazioni non possono essere definite su tipi generici.

Volevo qualcosa del genere

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; };
    }
}

Ho risolto questo problema usando la tipizzazione runtime dinamica .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 };
    }
}

Le due cose sull'uso dynamicsono

  1. Prestazione. Tutti i tipi di valore vengono inscatolati.
  2. Errori di runtime. "Batti" il compilatore, ma perdi la sicurezza del tipo. Se il tipo generico non ha l'operatore definito, durante l'esecuzione verrà generata un'eccezione.

1

I tipi primitivi numerici .NET non condividono alcuna interfaccia comune che ne consentirebbe l'utilizzo per i calcoli. Sarebbe possibile definire delle interfacce (ad esempio ISignedWholeNumber) che eseguire tali operazioni, definire strutture che contengono un singolo Int16, Int32ecc e implementare tali interfacce, e quindi hanno metodi che accettano tipi generici vincolate a ISignedWholeNumber, ma dover convertire valori numerici per i tuoi tipi di struttura sarebbe probabilmente un fastidio.

Un approccio alternativo potrebbe essere quello di definire la classe statica Int64Converter<T>con una proprietà statica bool Available {get;};e delegati statici per Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). Il costruttore della classe potrebbe usare un codice fisso per caricare i delegati per tipi noti e possibilmente usare Reflection per verificare se il tipo Timplementa metodi con nomi e firme appropriati (nel caso in cui sia qualcosa come una struttura che contiene un Int64e rappresenta un numero, ma ha un ToString()metodo personalizzato ). Questo approccio perderebbe i vantaggi associati al controllo del tipo in fase di compilazione, ma riuscirà comunque a evitare le operazioni di inscatolamento e ogni tipo dovrebbe essere "controllato" una sola volta. Successivamente, le operazioni associate a quel tipo verrebbero sostituite con una spedizione delegata.


@KenKin: IConvertible fornisce un mezzo mediante il quale qualsiasi numero intero può essere aggiunto a un altro tipo intero per produrre ad esempio un Int64risultato, ma non fornisce un mezzo tramite il quale ad esempio un numero intero di tipo arbitrario possa essere incrementato per produrre un altro numero intero dello stesso tipo .
supercat

1

Ho avuto una situazione simile in cui dovevo gestire tipi e stringhe numerici; sembra un po 'un bizzarro mix, ma il gioco è fatto.

Ancora una volta, come molte persone, ho esaminato i vincoli e ho trovato un sacco di interfacce che doveva supportare. Tuttavia, a) non era a tenuta stagna al 100% eb), chiunque fosse nuovo a guardare questo lungo elenco di vincoli sarebbe immediatamente molto confuso.

Quindi, il mio approccio era quello di mettere tutta la mia logica in un metodo generico senza vincoli, ma di rendere privato quel metodo generico. L'ho quindi esposto con metodi pubblici, uno che gestiva esplicitamente il tipo che volevo gestire - a mio avviso, il codice è pulito ed esplicito, ad es.

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
}

0

Se tutto ciò che desideri è utilizzare un tipo numerico , potresti prendere in considerazione la creazione di qualcosa di simile a un alias in C ++ con using.

Quindi, invece di avere il generico

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

potresti avere

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

Che potrebbero permettere di passare facilmente da doublead into altri, se necessario, ma non sarebbe in grado di utilizzare ComputeSomethingcon doublee intnello stesso programma.

Ma perché non sostituire tutto doublefino ad intallora? Perché il tuo metodo potrebbe voler usare a doublese l'input è doubleo int. L'alias ti consente di sapere esattamente quale variabile utilizza il tipo dinamico .


0

L'argomento è vecchio ma per i futuri lettori:

Questa funzionalità è strettamente correlata a quella Discriminated Unionsche finora non è stata implementata in C #. Ho trovato il suo problema qui:

https://github.com/dotnet/csharplang/issues/113

Questo problema è ancora aperto e la funzionalità è stata pianificata per C# 10

Quindi dobbiamo ancora aspettare un po 'di più, ma dopo averlo rilasciato puoi farlo in questo modo:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

-11

Penso che tu stia fraintendendo i generici. Se l'operazione che si sta tentando di eseguire è valida solo per tipi di dati specifici, non si sta facendo qualcosa di "generico".

Inoltre, poiché si desidera consentire alla funzione di funzionare solo con tipi di dati int, non è necessario disporre di una funzione separata per ciascuna dimensione specifica. La semplice acquisizione di un parametro nel tipo specifico più grande consentirà al programma di eseguire l'upgrade automatico dei tipi di dati più piccoli. (cioè passando un Int16 si convertirà automaticamente in Int64 quando si chiama).

Se stai eseguendo diverse operazioni in base alla dimensione effettiva di int che viene passata nella funzione, penso che dovresti riconsiderare seriamente anche cercando di fare quello che stai facendo. Se devi ingannare la lingua, dovresti pensare un po 'di più a ciò che stai cercando di realizzare piuttosto che a come fare ciò che vuoi.

In caso contrario, è possibile utilizzare un parametro di tipo Object, quindi sarà necessario verificare il tipo di parametro e intraprendere le azioni appropriate o generare un'eccezione.


10
Prendi in considerazione un istogramma di classe <T>. Ha senso lasciare che prenda un parametro generico, quindi il compilatore può ottimizzarlo per byte, ints, doppi, decimali, BigInt, ... ma allo stesso tempo è necessario impedire che si possa creare un, diciamo, Istogramma <Hashset >, perché - parlando con Tron - non calcola. (letteralmente :))
lato sole

15
Sei tu quello che fraintende i generici. La metaprogrammazione non si limita a operare su valori che potrebbero essere di qualsiasi tipo , ma per operare su tipi che soddisfano vari vincoli .
Jim Balter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.