Gibt es eine Einschränkung, die meine generische Methode auf numerische Typen beschränkt?

364

Kann mir jemand sagen, ob es mit Generika eine Möglichkeit gibt, ein generisches Typargument Tnur auf Folgendes zu beschränken :

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

Ich kenne das whereSchlüsselwort, kann aber keine Schnittstelle nur für diese Typen finden.

Etwas wie:

static bool IntegerFunction<T>(T value) where T : INumeric 
Corin Blaikie
quelle
2
Es gibt jetzt verschiedene C # -Vorschläge, die dies ermöglichen würden, aber AFAIK keiner von ihnen ist weiter als vorläufige Erkundungen / Diskussionen. Siehe Exploration: Formen und Erweiterungen , Exploration: Rollen, Erweiterungsschnittstellen und statische Schnittstellenelemente , Champion "Typklassen (auch bekannt als Konzepte, strukturelle generische Einschränkungen)" und Vorschlag: Generische Typen sollten Operatoren unterstützen
Chris Yungmann

Antworten:

140

C # unterstützt dies nicht. Hejlsberg hat in einem Interview mit Bruce Eckel die Gründe für die Nichtimplementierung des Features beschrieben :

Und es ist nicht klar, dass die zusätzliche Komplexität den geringen Ertrag wert ist, den Sie erhalten. Wenn etwas, das Sie tun möchten, im Einschränkungssystem nicht direkt unterstützt wird, können Sie dies mit einem Factory-Muster tun. Sie könnten beispielsweise eine haben Matrix<T>und Matrixmöchten darin eine Punktproduktmethode definieren. Das ist natürlich , dass Mittel müssen Sie schließlich zu verstehen , wie man multiplizieren zwei Ts, aber Sie nicht , dass als Einschränkung sagen kann, zumindest nicht , wenn Tist int, doubleoder float. Aber was Sie tun könnten, ist, Ihre MatrixAnnahme als Argument a zu verwenden Calculator<T>und Calculator<T>eine Methode aufzurufen multiply. Sie setzen das um und geben es an die weiter Matrix.

Dies führt jedoch zu einem ziemlich komplizierten Code, bei dem der Benutzer Calculator<T>für jeden T, den er verwenden möchte, eine eigene Implementierung bereitstellen muss . Solange es nicht erweiterbar sein muss, dh wenn Sie nur eine feste Anzahl von Typen unterstützen möchten, wie z. B. intund double, können Sie mit einer relativ einfachen Oberfläche davonkommen:

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

( Minimale Implementierung in einem GitHub Gist. )

Sobald Sie jedoch möchten, dass der Benutzer seine eigenen benutzerdefinierten Typen bereitstellen kann, müssen Sie diese Implementierung öffnen, damit der Benutzer seine eigenen CalculatorInstanzen bereitstellen kann . Um beispielsweise eine Matrix zu instanziieren, die eine benutzerdefinierte Dezimal-Gleitkomma-Implementierung verwendet, DFPmüssen Sie diesen Code schreiben:

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

… Und implementieren alle Mitglieder für DfpCalculator : ICalculator<DFP>.

Eine Alternative, die leider dieselben Einschränkungen aufweist, besteht darin, mit Richtlinienklassen zu arbeiten, wie in der Antwort von Sergey Shandar erläutert .

Konrad Rudolph
quelle
25
Übrigens bietet MiscUtil eine generische Klasse, die genau dies tut. Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
1
@ Mark: guter Kommentar. Um ganz klar zu sein, ich glaube nicht, dass Hejlsberg die Codegenerierung als Lösung für das Problem bezeichnet hat, wie Sie es im Operator<T>Code tun (da das Interview lange vor der Existenz des ExpressionsFrameworks gegeben wurde, obwohl man es könnte Kursgebrauch Reflection.Emit) - und ich würde mich wirklich für seine Problemumgehung interessieren .
Konrad Rudolph
@Konrad Rudolph: Ich denke, diese Antwort auf eine ähnliche Frage erklärt Hejlsbergs Problemumgehung. Die andere generische Klasse wird abstrakt gemacht. Da Sie für jeden Typ, den Sie unterstützen möchten, die andere generische Klasse implementieren müssen, führt dies zu doppeltem Code. Dies bedeutet jedoch, dass Sie die ursprüngliche generische Klasse nur mit einem unterstützten Typ instanziieren können.
Ergwun
14
Ich stimme Heijsbergs Satz nicht zu: "In gewissem Sinne sind C ++ - Vorlagen tatsächlich untypisiert oder lose typisiert. Während C # -Generika stark typisiert sind." Das ist wirklich Marketing BS, um C # zu fördern. Starkes / schwaches Tippen hat nichts mit der Qualität der Diagnose zu tun. Ansonsten: Interessanter Fund.
Sebastian Mach
100

Angesichts der Popularität dieser Frage und des Interesses an einer solchen Funktion bin ich überrascht zu sehen, dass es noch keine Antwort für T4 gibt.

In diesem Beispielcode werde ich ein sehr einfaches Beispiel dafür zeigen, wie Sie die leistungsstarke Template-Engine verwenden können, um das zu tun, was der Compiler hinter den Kulissen mit Generika tut.

Anstatt die Rahmen zu durchlaufen und die Sicherheit der Kompilierungszeit zu opfern, können Sie einfach die gewünschte Funktion für jeden gewünschten Typ generieren und diese entsprechend verwenden (zur Kompilierungszeit!).

Um dies zu tun:

  • Erstellen Sie eine neue Textvorlagendatei mit dem Namen GenericNumberMethodTemplate.tt .
  • Entfernen Sie den automatisch generierten Code (Sie behalten das meiste davon, aber einige werden nicht benötigt).
  • Fügen Sie das folgende Snippet hinzu:
<#@ 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;
        }
    <#
    } #>
}

Das ist es. Du bist jetzt fertig.

Wenn Sie diese Datei speichern, wird sie automatisch in diese Quelldatei kompiliert:

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

In Ihrer mainMethode können Sie überprüfen, ob Sie über eine Sicherheit zur Kompilierungszeit verfügen:

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

Geben Sie hier die Bildbeschreibung ein

Ich komme einer Bemerkung voraus: Nein, dies ist kein Verstoß gegen das DRY-Prinzip. Das DRY-Prinzip soll verhindern, dass Benutzer Code an mehreren Stellen duplizieren, wodurch die Wartung der Anwendung schwierig wird.

Dies ist hier überhaupt nicht der Fall: Wenn Sie eine Änderung wünschen, können Sie einfach die Vorlage ändern (eine einzige Quelle für Ihre gesamte Generation!) Und fertig.

Um es mit Ihren eigenen benutzerdefinierten Definitionen zu verwenden, fügen Sie Ihrem generierten Code eine Namespace-Deklaration hinzu (stellen Sie sicher, dass es dieselbe ist wie die, in der Sie Ihre eigene Implementierung definieren), und markieren Sie die Klasse als partial. Fügen Sie anschließend diese Zeilen zu Ihrer Vorlagendatei hinzu, damit sie in die spätere Kompilierung aufgenommen werden:

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

Seien wir ehrlich: Das ist ziemlich cool.

Haftungsausschluss: Dieses Beispiel wurde stark von Metaprogramming in .NET von Kevin Hazzard und Jason Bock, Manning Publications, beeinflusst .

Jeroen Vannevel
quelle
Das ist ziemlich cool, aber wäre es möglich, diese Lösung so zu ändern, dass die Methoden einen generischen Typ akzeptieren T, der von den verschiedenen IntXKlassen stammt oder von diesen erbt ? Ich mag diese Lösung, weil sie Zeit spart, aber damit sie das Problem zu 100% löst (obwohl sie nicht so gut ist, als ob C # diese Art von eingebauter Einschränkung unterstützt hätte), sollte jede der generierten Methoden immer noch generisch sein, damit Sie können ein Objekt eines Typs zurückgeben, der von einer der IntXXKlassen erbt .
Zachary Kniebel
1
@ZacharyKniebel: Die IntXXTypen sind Strukturen, was bedeutet, dass sie die Vererbung überhaupt nicht unterstützen . Und selbst wenn dies der Fall wäre, gilt das Liskov-Substitutionsprinzip (das Sie möglicherweise aus dem SOLID-Idiom kennen): Wenn die Methode definiert ist Xund Yein Kind von Xdann ist, sollte per Definition jede Yals Ersatz für diese Methode übergeben werden können sein Basistyp.
Jeroen Vannevel
1
Diese Problemumgehung mithilfe der Richtlinien stackoverflow.com/questions/32664/… verwendet T4 zum Generieren von Klassen.
Sergey Shandar
2
+1 für diese Lösung, da im Gegensatz zu richtlinienbasierten Lösungen die Betriebseffizienz der integrierten Integraltypen erhalten bleibt. Das Aufrufen integrierter CLR-Operatoren (wie Hinzufügen) über eine zusätzliche (möglicherweise virtuelle) Methode kann die Leistung erheblich beeinträchtigen, wenn sie häufig verwendet werden (wie in mathematischen Bibliotheken). Und da die Anzahl der Integraltypen konstant ist (und nicht von ihnen geerbt werden kann), müssen Sie nur den Code für Fehlerkorrekturen neu generieren.
Attila Klenik
1
Sehr cool und ich wollte gerade damit anfangen, dann erinnerte ich mich, wie abhängig ich von Resharper für das Refactoring bin und Sie Refactor nicht über die T4-Vorlage umbenennen können. Es ist nicht kritisch, aber eine Überlegung wert.
Bradgonesurfing
86

Hierfür gibt es keine Einschränkungen. Es ist ein echtes Problem für alle, die Generika für numerische Berechnungen verwenden möchten.

Ich würde weiter gehen und sagen, wir brauchen

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

Oder auch

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

Leider haben Sie nur Schnittstellen, Basisklassen und die Schlüsselwörter struct(müssen Werttyp sein), class(müssen Referenztyp sein) und new()(müssen Standardkonstruktor haben)

Sie könnten die Nummer in etwas anderes (ähnlich INullable<T>) wie hier auf codeproject einschließen .


Sie können die Einschränkung zur Laufzeit anwenden (indem Sie für die Operatoren nachdenken oder nach Typen suchen), aber das verliert den Vorteil, dass das Generikum überhaupt vorhanden ist.

Keith
quelle
2
Ich frage mich, ob Sie MiscUtils Unterstützung für generische Operatoren gesehen haben ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
10
Ja - Jon Skeet hat mich vor einiger Zeit wegen etwas anderem auf sie hingewiesen (aber nach dieser einjährigen Antwort) - sie sind eine clevere Idee, aber ich hätte immer noch gerne die richtige Unterstützung für Einschränkungen.
Keith
1
Warten Sie, where T : operators( +, -, /, * )ist legal C #? Entschuldigung für die Frage des Neulings.
Kdbanman
@ kdbanman Ich glaube nicht. Keith sagt, dass C # nicht unterstützt, was OP verlangt, und schlägt vor, dass wir dazu in der Lage sein sollten where T : operators( +, -, /, * ), aber nicht können.
AMTerp
62

Problemumgehung mithilfe von Richtlinien:

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

Algorithmen:

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

}

Verwendungszweck:

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.

Die Lösung ist kompilierzeitsicher. CityLizard Framework bietet eine kompilierte Version für .NET 4.0. Die Datei lautet lib / NETFramework4.0 / CityLizard.Policy.dll.

Es ist auch in Nuget verfügbar: https://www.nuget.org/packages/CityLizard/ . Siehe CityLizard.Policy.I- Struktur.

Sergey Shandar
quelle
Ich hatte Probleme mit diesem Muster, wenn es weniger Funktionsargumente als generische Parameter gibt. Geöffnete stackoverflow.com/questions/36048248/…
xvan
Gibt es einen Grund, warum struct? was ist, wenn ich Singleton-Klasse statt und ändern Instanz zu verwenden public static NumericPolicies Instance = new NumericPolicies();und fügen Sie dann diesen Konstruktor private NumericPolicies() { }.
M. Kazem Akhgary 16.
@ M.kazemAkhgary Sie können den Singleton verwenden. Ich bevorzuge struct. Theoretisch kann es von Compiler / CLR optimiert werden, da die Struktur keine Informationen enthält. Im Falle von Singleton übergeben Sie immer noch eine Referenz, was zusätzlichen Druck auf die GC ausüben kann. Ein weiterer Vorteil ist, dass struct nicht null sein kann :-).
Sergey Shandar
Ich wollte sagen, dass Sie eine sehr intelligente Lösung gefunden haben, aber die Lösung ist zu begrenzt für mich: Ich wollte sie verwenden T Add<T> (T t1, T t2), Sum()funktioniert aber nur, wenn sie ihren eigenen T-Typ aus ihren Parametern abrufen kann, was nicht möglich ist wenn es in eine andere generische Funktion eingebettet ist.
Tobias Knauss
16

Diese Frage ist ein bisschen wie eine FAQ, also poste ich sie als Wiki (da ich vorher ähnliche gepostet habe, aber dies ist eine ältere); wie auch immer...

Welche Version von .NET verwenden Sie? Wenn Sie .NET 3.5 verwenden, habe ich eine generische Operator-Implementierung in MiscUtil (kostenlos usw.).

Dies hat Methoden wie T Add<T>(T x, T y)und andere Varianten für die Arithmetik für verschiedene Typen (wie DateTime + TimeSpan).

Darüber hinaus funktioniert dies für alle eingebauten, angehobenen und maßgeschneiderten Bediener und speichert den Delegaten für die Leistung zwischen.

Einige zusätzliche Hintergrundinformationen darüber, warum dies schwierig ist, finden Sie hier .

Vielleicht möchten Sie auch wissen, dass dynamic(4.0) dieses Problem auch indirekt löst - dh

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Marc Gravell
quelle
14

Leider können Sie in dieser Instanz nur struct in der where-Klausel angeben. Es scheint seltsam, dass Sie Int16, Int32 usw. nicht speziell angeben können, aber ich bin sicher, dass der Entscheidung, Werttypen in einer where-Klausel nicht zuzulassen, ein tiefgreifender Implementierungsgrund zugrunde liegt.

Ich denke, die einzige Lösung besteht darin, eine Laufzeitprüfung durchzuführen, die leider verhindert, dass das Problem beim Kompilieren erkannt wird. Das würde ungefähr so ​​aussehen: -

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

Was ein bisschen hässlich ist, weiß ich, aber zumindest die erforderlichen Einschränkungen bietet.

Ich würde auch mögliche Auswirkungen auf die Leistung dieser Implementierung untersuchen. Vielleicht gibt es da draußen einen schnelleren Weg.

ljs
quelle
13
+1 kann jedoch // Rest of code...möglicherweise nicht kompiliert werden, wenn dies von den durch die Einschränkungen definierten Operationen abhängt.
Nick
1
Convert.ToIntXX (Wert) kann dazu beitragen, dass "// Rest of Code" kompiliert wird - zumindest bis der Rückgabetyp von IntegerFunction ebenfalls vom Typ T ist, dann sind Sie gespannt. :-p
yoyo
-1; Dies funktioniert aus dem von @Nick angegebenen Grund nicht. In dem Moment, in dem Sie versuchen, arithmetische Operationen in // Rest of code...like value + valueoder value * valueauszuführen, tritt ein Kompilierungsfehler auf.
Mark Amery
13

Wahrscheinlich ist das nächste, was Sie tun können

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

Ich bin mir nicht sicher, ob Sie Folgendes tun könnten

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

Für etwas so Spezifisches, warum nicht einfach Überladungen für jeden Typ haben, ist die Liste so kurz und es würde möglicherweise weniger Speicherbedarf haben.

Gehackt
quelle
6

Ab C # 7.3 können Sie eine nähere Annäherung verwenden - die nicht verwaltete Einschränkung, um anzugeben, dass ein Typparameter ein nicht zeigerfreier, nicht nullfähiger, nicht verwalteter Typ ist.

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

Die nicht verwaltete Einschränkung impliziert die Strukturbeschränkung und kann weder mit der Struktur noch mit den Einschränkungen new () kombiniert werden.

Ein Typ ist ein nicht verwalteter Typ, wenn es sich um einen der folgenden Typen handelt:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal oder bool
  • Beliebiger Aufzählungstyp
  • Beliebiger Zeigertyp
  • Jeder benutzerdefinierte Strukturtyp, der nur Felder mit nicht verwalteten Typen enthält und in C # 7.3 und früheren Versionen kein konstruierter Typ ist (ein Typ, der mindestens ein Typargument enthält).

Um weitere Einschränkungen vorzunehmen und Zeiger- und benutzerdefinierte Typen zu entfernen, die IComparable nicht implementieren, fügen Sie IComparable hinzu (Enum wird jedoch weiterhin von IComparable abgeleitet. Beschränken Sie also Enum durch Hinzufügen von IEquatable <T>. Sie können je nach Ihren Umständen weitere Schritte ausführen und zusätzliche Schnittstellen hinzufügen. unmanaged ermöglicht es, diese Liste kürzer zu halten):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }
Vlad Novakovsky
quelle
Schön, aber nicht genug ... Zum Beispiel DateTimefällt unter unmanaged, IComparable, IEquatable<T>Zwang ..
Adam Calvet Bohl
Ich weiß, aber Sie können je nach Ihren Umständen noch weiter gehen und zusätzliche Schnittstellen hinzufügen. Unmanaged ermöglicht es, diese Liste kürzer zu halten. Ich habe gerade den Ansatz gezeigt, Annäherung mit nicht verwaltet. In den meisten Fällen reicht dies aus
Vlad Novakovsky
4

Es gibt keine Möglichkeit, Vorlagen auf Typen zu beschränken, Sie können jedoch je nach Typ verschiedene Aktionen definieren. Als Teil eines generischen numerischen Pakets benötigte ich eine generische Klasse, um zwei Werte hinzuzufügen.

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

Beachten Sie, dass die typeofs zur Kompilierungszeit ausgewertet werden, sodass die if-Anweisungen vom Compiler entfernt werden. Der Compiler entfernt auch falsche Casts. Also würde sich etwas im Compiler auflösen

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Rob Deary
quelle
Vielen Dank für die Bereitstellung einer empirischen Lösung!
zsf222
Ist es nicht dasselbe wie für jeden Typ dieselbe Methode zu erstellen?
Luis
3

Ich habe eine kleine Bibliotheksfunktionalität erstellt, um diese Probleme zu lösen:

Anstatt:

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.

Sie könnten schreiben:

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.

Den Quellcode finden Sie hier: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number

Martin Mulder
quelle
2

Ich habe mich genauso gefragt wie Samjudson, warum nur zu ganzen Zahlen? In diesem Fall möchten Sie möglicherweise eine Hilfsklasse oder ähnliches erstellen, um alle gewünschten Typen aufzunehmen.

Wenn Sie nur Ganzzahlen wünschen, verwenden Sie kein Generikum, das ist kein Generikum. oder noch besser, lehnen Sie jeden anderen Typ ab, indem Sie seinen Typ überprüfen.

Martin Marconcini
quelle
2

Hierfür gibt es noch keine „gute“ Lösung. Sie können das Typargument jedoch erheblich eingrenzen, um viele Fehlanpassungen für Ihre hypotetische 'INumeric'-Einschränkung auszuschließen, wie Haacked oben gezeigt hat.

static bool IntegerFunction <T> (T-Wert) wobei T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...

dmihailescu
quelle
2

Wenn Sie .NET 4.0 und höher verwenden, können Sie einfach dynamic als Methodenargument verwenden und zur Laufzeit überprüfen, ob der übergebene dynamische Argumenttyp ein numerischer / ganzzahliger Typ ist.

Wenn der Typ der übergebenen dynamischen ist nicht numerisch / Integer - Typ dann Ausnahme auslösen.

Ein Beispiel kurzer Code, implementiert ist die Idee , so etwas wie:

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

Natürlich funktioniert diese Lösung nur zur Laufzeit, aber niemals zur Kompilierungszeit.

Wenn Sie eine Lösung suchen, die immer zur Kompilierungszeit und nie zur Laufzeit funktioniert, müssen Sie die Dynamik mit einer öffentlichen Struktur / Klasse umschließen, deren überladene öffentliche Konstruktoren nur Argumente des gewünschten Typs akzeptieren und der Struktur / Klasse den entsprechenden Namen geben.

Es ist sinnvoll, dass die umschlossene Dynamik immer ein privates Mitglied der Klasse / Struktur ist und das einzige Mitglied der Struktur / Klasse ist und der Name des einzigen Mitglieds der Struktur / Klasse "Wert" ist.

Sie müssen bei Bedarf auch öffentliche Methoden und / oder Operatoren definieren und implementieren , die mit den gewünschten Typen für das private dynamische Mitglied der Klasse / Struktur arbeiten.

Es ist auch sinnvoll, dass die Struktur / Klasse einen speziellen / eindeutigen Konstruktor hat, der die Dynamik als Argument akzeptiert , das das einzige private dynamische Mitglied namens "Wert" initialisiert, aber der Modifikator dieses Konstruktors ist natürlich privat .

Sobald die Klasse / Struktur fertig ist, definieren Sie den Typ der IntegerFunction des Arguments als die Klasse / Struktur, die definiert wurde.

Ein Beispiel für einen langen Code, der die Idee implementiert, ist wie folgt:

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

Beachten Sie, dass Sie zur Verwendung von Dynamic in Ihrem Code einen Verweis auf Microsoft.CSharp hinzufügen müssen

Wenn die Version des .NET-Frameworks unter / unter / kleiner als 4.0 liegt und die Dynamik in dieser Version nicht definiert ist, müssen Sie stattdessen ein Objekt verwenden und in den Integer-Typ umwandeln, was problematisch ist. Ich empfehle daher, dass Sie at verwenden Mindestens .NET 4.0 oder neuer, wenn Sie können, damit Sie dynamisch anstelle von Objekt verwenden können .


quelle
2

Leider bietet .NET keine Möglichkeit, dies nativ zu tun.

Um dieses Problem zu beheben, habe ich die OSS-Bibliothek Genumerics erstellt, die die meisten numerischen Standardoperationen für die folgenden integrierten numerischen Typen und ihre nullbaren Entsprechungen bietet und die Unterstützung für andere numerische Typen bietet.

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

Die Leistung entspricht einer numerischen typspezifischen Lösung, mit der Sie effiziente generische numerische Algorithmen erstellen können.

Hier ist ein Beispiel für die Codeverwendung.

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;
}
TylerBrinkley
quelle
1

Was ist der Sinn der Übung?

Wie bereits erwähnt, könnte eine nicht generische Funktion das größte Element übernehmen, und der Compiler konvertiert automatisch kleinere Ints für Sie.

static bool IntegerFunction(Int64 value) { }

Wenn sich Ihre Funktion auf einem leistungskritischen Pfad befindet (sehr unwahrscheinlich, IMO), können Sie Überlastungen für alle erforderlichen Funktionen bereitstellen.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
dbkk
quelle
1
Ich arbeite viel mit numerischen Methoden. Manchmal möchte ich ganze Zahlen und manchmal möchte ich Gleitkomma. Beide haben 64-Bit-Versionen, die für die Verarbeitungsgeschwindigkeit optimal sind. Das Konvertieren zwischen diesen ist eine schreckliche Idee, da es in jeder Hinsicht Verluste gibt. Während ich dazu neige, Doppelte zu verwenden, finde ich es manchmal besser, ganze Zahlen zu verwenden, weil sie anderswo verwendet werden. Aber es wäre sehr schön, wenn ich einen Algorithmus schreibe, um dies einmal zu tun und die Typentscheidung den Instanzanforderungen zu überlassen.
VoteCoffee
1

Ich würde ein generisches verwenden, das Sie extern handhaben könnten ...

/// <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;
}
Marc Roussel
quelle
1

Diese Einschränkung betraf mich, als ich versuchte, Operatoren für generische Typen zu überladen. Da es keine "INumeric" -Einschränkung gab und aus einer Reihe anderer Gründe, die die guten Leute im Stackoverflow gerne bereitstellen, können Operationen für generische Typen nicht definiert werden.

Ich wollte so etwas wie

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

Ich habe dieses Problem mithilfe der dynamischen Laufzeiteingabe von .net4 umgangen.

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

Die beiden Dinge bei der Verwendung dynamicsind

  1. Performance. Alle Werttypen werden eingerahmt.
  2. Laufzeitfehler. Sie "schlagen" den Compiler, verlieren aber die Typensicherheit. Wenn für den generischen Typ der Operator nicht definiert ist, wird während der Ausführung eine Ausnahme ausgelöst.
Pomeroy
quelle
1

Die numerischen Primitivtypen von .NET haben keine gemeinsame Schnittstelle, über die sie für Berechnungen verwendet werden könnten. Es wäre möglich , eigene Schnittstellen (zB zu definieren ISignedWholeNumber) , die solche Operationen durchführen würde, definieren Strukturen , die eine einzelne enthalten Int16, Int32usw. und diese Schnittstellen implementieren und dann Methoden , die auf generische Typen akzeptieren eingeschränkt ISignedWholeNumber, aber mit numerischen Werten konvertieren für Ihre Strukturtypen wäre wahrscheinlich ein Ärgernis.

Ein alternativer Ansatz wäre statische Klasse zu definieren , Int64Converter<T>mit einer statischen Eigenschaft bool Available {get;};und statisch Delegierten Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). Der Klassenkonstruktor kann hartcodiert sein, um Delegaten für bekannte Typen zu laden, und möglicherweise Reflection verwenden, um zu testen, ob der Typ TMethoden mit den richtigen Namen und Signaturen implementiert (falls es sich um eine Struktur handelt, die eine Int64und eine Zahl enthält, diese aber hat eine benutzerdefinierte ToString()Methode). Dieser Ansatz würde die mit der Typprüfung zur Kompilierungszeit verbundenen Vorteile verlieren, aber dennoch Boxoperationen vermeiden, und jeder Typ müsste nur einmal "geprüft" werden. Danach werden Operationen, die diesem Typ zugeordnet sind, durch einen Delegatenversand ersetzt.

Superkatze
quelle
@KenKin: IConvertible bietet ein Mittel, mit dem eine beliebige Ganzzahl zu einem anderen Ganzzahltyp hinzugefügt werden kann, um z. B. ein Int64Ergebnis zu erhalten, bietet jedoch kein Mittel, mit dem z. B. eine Ganzzahl eines beliebigen Typs erhöht werden kann, um eine andere Ganzzahl desselben Typs zu erhalten .
Supercat
1

Ich hatte eine ähnliche Situation, in der ich mit numerischen Typen und Zeichenfolgen umgehen musste. scheint ein bisschen bizarr zu sein, aber los geht's.

Wie viele andere habe ich mich auch hier mit Einschränkungen befasst und eine Reihe von Schnittstellen entwickelt, die unterstützt werden mussten. A) es war jedoch nicht 100% wasserdicht und b) jeder, der sich diese lange Liste von Einschränkungen noch einmal ansieht, wäre sofort sehr verwirrt.

Mein Ansatz war es also, meine gesamte Logik in eine generische Methode ohne Einschränkungen zu integrieren, diese generische Methode jedoch privat zu machen. Ich habe es dann mit öffentlichen Methoden verfügbar gemacht, von denen eine explizit den Typ behandelt, den ich behandeln wollte - meiner Meinung nach ist der Code sauber und explizit, z

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
}
DrGriff
quelle
0

Wenn Sie nur einen numerischen Typ verwenden möchten , können Sie einen Alias ​​in C ++ mit erstellen using.

Also anstatt das sehr generische zu haben

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

du könntest haben

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

Auf diese Weise können Sie bei Bedarf problemlos von doublezu intoder zu anderen wechseln, können diese jedoch nicht ComputeSomethingmit doubleund intim selben Programm verwenden.

Aber warum nicht alle doublebis intdahin ersetzen ? Weil Ihre Methode möglicherweise verwenden möchte, doubleob die Eingabe doubleoder ist int. Mit dem Alias ​​können Sie genau wissen, welche Variable den dynamischen Typ verwendet.

user276648
quelle
0

Thema ist alt, aber für zukünftige Leser:

Diese Funktion ist eng verwandt und Discriminated Unionsin C # bisher nicht implementiert. Ich habe das Problem hier gefunden:

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

Diese Ausgabe ist noch offen und die Funktion ist für geplant C# 10

Wir müssen also noch etwas länger warten, aber nach der Veröffentlichung können Sie dies folgendermaßen tun:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
Arman Ebrahimpour
quelle
-11

Ich denke, Sie verstehen Generika falsch. Wenn der Vorgang, den Sie ausführen möchten, nur für bestimmte Datentypen geeignet ist, führen Sie keine "generischen" Vorgänge aus.

Da Sie nur zulassen möchten, dass die Funktion für int-Datentypen funktioniert, sollten Sie für jede bestimmte Größe keine separate Funktion benötigen. Durch einfaches Aufnehmen eines Parameters im größten spezifischen Typ kann das Programm die kleineren Datentypen automatisch auf diesen übertragen. (dh das Übergeben eines Int16 wird beim Aufruf automatisch in Int64 konvertiert).

Wenn Sie verschiedene Operationen ausführen, basierend auf der tatsächlichen Größe von int, die an die Funktion übergeben wird, sollten Sie ernsthaft überlegen, ob Sie versuchen, das zu tun, was Sie tun. Wenn Sie die Sprache zum Narren halten müssen, sollten Sie etwas mehr darüber nachdenken, was Sie erreichen möchten, als darüber, wie Sie das tun, was Sie wollen.

Andernfalls kann ein Parameter vom Typ Object verwendet werden. Anschließend müssen Sie den Typ des Parameters überprüfen und entsprechende Maßnahmen ergreifen oder eine Ausnahme auslösen.

Tom Welch
quelle
10
Betrachten Sie ein Klassenhistogramm <T>. Es ist sinnvoll, einen generischen Parameter zu verwenden, damit der Compiler ihn für Bytes, Ints, Doubles, Dezimalzahlen, BigInt usw. optimieren kann. Gleichzeitig müssen Sie jedoch verhindern, dass Sie beispielsweise ein Histogramm <Hashset erstellen können >, weil es - im Gespräch mit Tron - nicht berechnet. (buchstäblich :))
Sunside
15
Du bist derjenige, der Generika falsch versteht. Die Metaprogrammierung arbeitet nicht nur mit Werten, die ein beliebiger Typ sein können , sondern auch mit Typen, die verschiedenen Einschränkungen entsprechen .
Jim Balter