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

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

  •  09-06-2019
  •  | 
  •  

Frage

Kann mir jemand sagen, ob es mit Generika eine Möglichkeit gibt, ein generisches Typargument einzuschränken? T nur:

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

Ich bin mir dessen bewusst where Schlüsselwort, kann aber keine Schnittstelle dafür finden nur diese Typen,

Etwas wie:

static bool IntegerFunction<T>(T value) where T : INumeric 
War es hilfreich?

Lösung

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

Und es ist nicht klar, ob die zusätzliche Komplexität den geringen Ertrag, den man erhält, wert ist.Wenn etwas, das Sie tun möchten, im Einschränkungssystem nicht direkt unterstützt wird, können Sie es mit einem Factory-Muster tun.Du könntest einen haben Matrix<T>, zum Beispiel, und darin Matrix Sie möchten eine Skalarproduktmethode definieren.Das bedeutet natürlich, dass Sie letztendlich verstehen müssen, wie man zwei multipliziert Ts, aber das kann man nicht als Einschränkung sagen, zumindest nicht if T Ist int, double, oder float.Aber was Sie tun könnten, ist, Ihre zu haben Matrix Nehmen Sie als Argument a Calculator<T>, und in Calculator<T>, habe eine Methode namens multiply.Sie implementieren das und geben es an die weiter Matrix.

Dies führt jedoch zu einem ziemlich komplizierten Code, bei dem der Benutzer seinen eigenen Code bereitstellen muss Calculator<T> Umsetzung, für jeden T die sie nutzen wollen.Solange es nicht erweiterbar sein muss, d.h.wenn Sie nur eine feste Anzahl von Typen unterstützen möchten, z int Und double, können Sie mit einer relativ einfachen Schnittstelle 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 bereitstellen kann Calculator Instanzen.Um beispielsweise eine Matrix zu instanziieren, die eine benutzerdefinierte Dezimal-Gleitkomma-Implementierung verwendet, DFP, müssten Sie diesen Code schreiben:

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

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

Eine Alternative, die leider dieselben Einschränkungen aufweist, besteht darin, mit Richtlinienklassen zu arbeiten. wie in Sergey Shandars Antwort besprochen.

Andere Tipps

Angesichts der Beliebtheit dieser Frage und des Interesses an einer solchen Funktion bin ich überrascht, dass es bisher keine Antwort zu T4 gibt.

In diesem Beispielcode werde ich ein sehr einfaches Beispiel dafür zeigen, wie Sie mit der leistungsstarken Template-Engine das tun können, was der Compiler im Hintergrund mit Generika tut.

Anstatt viel Aufwand zu betreiben und die Sicherheit beim Kompilieren zu opfern, können Sie einfach die gewünschte Funktion für jeden gewünschten Typ generieren und diese entsprechend verwenden (zur Kompilierungszeit!).

Um das tun zu können:

  • Erstelle eine neue Textvorlage Datei aufgerufen GenericNumberMethodTemplate.tt.
  • Entfernen Sie den automatisch generierten Code (der größte Teil bleibt erhalten, einiges wird jedoch nicht benötigt).
  • Fügen Sie den folgenden Ausschnitt 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.

Durch das Speichern dieser Datei 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 deinem main Mit dieser Methode können Sie überprüfen, ob Sie Kompilierzeitsicherheit haben:

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

enter image description here

Eine Bemerkung greife ich vorweg:Nein, das ist kein Verstoß gegen das DRY-Prinzip.Das DRY-Prinzip soll verhindern, dass Benutzer Code an mehreren Stellen duplizieren, was dazu führen würde, dass die Anwendung schwer zu warten ist.

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 eventuelle Kompilierung einbezogen wird:

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

Lass uns ehrlich sein:Das ist ziemlich cool.

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

Hierfür gibt es keine Einschränkung.Es ist ein echtes Problem für jeden, der Generika für numerische Berechnungen verwenden möchte.

Ich würde noch 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 (muss vom Werttyp sein), class (muss Referenztyp sein) und new() (muss einen Standardkonstruktor haben)

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


Sie könnten die Einschränkung zur Laufzeit anwenden (indem Sie die Operatoren reflektieren oder nach Typen suchen), aber dadurch verlieren Sie den Vorteil, den das Generikum überhaupt hat.

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

}

Verwendung:

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 stellt eine kompilierte Version für .NET 4.0 bereit.Die Datei ist lib/NETFramework4.0/CityLizard.Policy.dll.

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

Bei dieser Frage handelt es sich eher um eine FAQ-Frage, daher veröffentliche ich sie als Wiki (da ich bereits ähnliche Beiträge gepostet habe, diese aber älter sind);Trotzdem...

Welche Version von .NET verwenden Sie?Wenn Sie .NET 3.5 verwenden, dann habe ich eine Implementierung generischer Operatoren In SonstigesUtil (kostenlos usw.).

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

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

Weitere Hintergrundinformationen dazu, warum dies schwierig ist, finden Sie hier Hier.

Vielleicht möchten Sie das auch wissen dynamic (4.0) löst dieses Problem auch indirekt, d. h.

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

Leider können Sie in diesem Fall nur struct in der where-Klausel angeben.Es scheint seltsam, dass Sie Int16, Int32 usw. nicht angeben können.Insbesondere, aber ich bin mir sicher, dass es einen tiefgreifenden Implementierungsgrund gibt, der der Entscheidung zugrunde liegt, keine Werttypen in einer where-Klausel zuzulassen.

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

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

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

Ich würde auch mögliche Auswirkungen auf die Leistung dieser Implementierung untersuchen, vielleicht gibt es da einen schnelleren Weg.

Das Beste, was Sie wahrscheinlich tun können, ist wahrscheinlich

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>

Warum gibt es für etwas so Spezifisches nicht einfach Überladungen für jeden Typ? Die Liste ist so kurz und hätte möglicherweise weniger Speicherbedarf.

Es gibt keine Möglichkeit, Vorlagen auf Typen zu beschränken, aber Sie können je nach Typ unterschiedliche 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 Kompilierzeit ausgewertet werden, sodass die if-Anweisungen vom Compiler entfernt werden.Der Compiler entfernt auch unerwünschte Umwandlungen.Also würde sich etwas im Compiler auflösen

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

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.

Du könntest 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: https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

Ich habe mich genauso gefragt wie Samjudson, warum nur auf ganze Zahlen?und wenn das der Fall ist, möchten Sie vielleicht eine Hilfsklasse oder ähnliches erstellen, um alle gewünschten Typen aufzunehmen.

Wenn Sie nur Ganzzahlen benötigen, verwenden Sie kein Generikum, das nicht generisch ist.oder noch besser, lehnen Sie jeden anderen Typ ab, indem Sie seinen Typ überprüfen.

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 (meiner Meinung nach sehr unwahrscheinlich), könnten Sie Überladungen für alle benötigten Funktionen bereitstellen.

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

Ich würde ein generisches verwenden, mit dem Sie extern umgehen können ...

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

Diese Einschränkung wirkte sich auf mich aus, als ich versuchte, Operatoren für generische Typen zu überladen.Da es keine „INumeric“-Einschränkung gab und die guten Leute von Stackoverflow eine Reihe anderer Gründe gerne zur Verfügung stellen, können Operationen nicht für generische Typen 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 Laufzeittypisierung 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 zwei Dinge über die Verwendung dynamic Sind

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

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

static bool IntegerFunction<T>(T-Wert) wobei T:Ikparierbar, orymattierbar, ikonvertierbar, ikparabelu003CT> , Iequatableu003CT> , struktur {...

Die numerischen primitiven .NET-Typen haben keine gemeinsame Schnittstelle, die ihre Verwendung für Berechnungen ermöglichen würde.Es wäre möglich, eigene Schnittstellen zu definieren (z.B. ISignedWholeNumber), die solche Operationen ausführen würden, definieren Strukturen, die eine einzelne enthalten Int16, Int32, usw.und implementieren Sie diese Schnittstellen und verfügen Sie dann über Methoden, die eingeschränkte generische Typen akzeptieren ISignedWholeNumber, aber numerische Werte in Ihre Strukturtypen konvertieren zu müssen, wäre wahrscheinlich lästig.

Ein alternativer Ansatz wäre die Definition einer statischen Klasse Int64Converter<T> mit einer statischen Eigenschaft bool Available {get;}; und statische Delegaten für Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest).Der Klassenkonstruktor könnte hartcodiert sein, um Delegaten für bekannte Typen zu laden, und möglicherweise Reflection verwenden, um den Typ zu testen T implementiert Methoden mit den richtigen Namen und Signaturen (falls es sich um so etwas wie eine Struktur handelt, die eine enthält Int64 und stellt eine Zahl dar, hat aber einen Brauch ToString() Methode).Bei diesem Ansatz würden die mit der Typprüfung zur Kompilierungszeit verbundenen Vorteile verloren gehen, aber dennoch könnten Boxing-Operationen vermieden werden, und jeder Typ müsste nur einmal „überprüft“ werden.Danach würden mit diesem Typ verknüpfte Vorgänge durch einen Delegate-Dispatch ersetzt.

Wenn Sie .NET 4.0 und höher verwenden, können Sie einfach verwenden dynamisch als Methodenargument und Prüfung zur Laufzeit dass das bestanden ist dynamisch Der Argumenttyp ist ein numerischer/ganzzahliger Typ.

Wenn der Typ des übergebenen ist dynamisch Ist nicht numerischer/ganzzahliger Typ und löst dann eine Ausnahme aus.

Ein Beispiel kurz Der Code, der die Idee umsetzt, sieht etwa so aus:

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 wünschen, die immer zur Kompilierungszeit und niemals zur Laufzeit funktioniert, müssen Sie die umschließen dynamisch mit einer öffentlichen Struktur/Klasse, deren überladen ist öffentlich Konstruktoren akzeptieren nur Argumente der gewünschten Typen und geben der Struktur/Klasse den entsprechenden Namen.

Es macht Sinn, dass das eingewickelt ist dynamisch ist immer Privat Mitglied der Klasse/Struktur und es ist das einzige Mitglied der Struktur/Klasse und der Name des einzigen Mitglieds der Struktur/Klasse ist „Wert“.

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

Es macht auch Sinn, dass die Struktur/Klasse hat besonders/einzigartig Konstruktor, der akzeptiert dynamisch als Argument, das es initialisiert, ist es nur ein privates dynamisches Mitglied namens „value“, aber das Modifikator dieses Konstruktors ist Privat Natürlich.

Sobald die Klasse/Struktur fertig ist, definieren Sie den Argumenttyp „IntegerFunction“ als die Klasse/Struktur, die definiert wurde.

Ein Beispiel lang Der Code, der die Idee umsetzt, sieht etwa so aus:

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 dies, um es zu verwenden dynamisch In Ihrem Code müssen Sie Füge Referenz hinzu Zu Microsoft.CSharp

Wenn die Version des .NET Frameworks unter/unter/kleiner als 4.0 ist und dynamisch in dieser Version undefiniert ist, müssen Sie es verwenden Objekt Führen Sie stattdessen eine Umwandlung in den Integer-Typ durch, was problematisch ist. Daher empfehle ich Ihnen, nach Möglichkeit mindestens .NET 4.0 oder neuer zu verwenden, damit Sie es verwenden können dynamisch anstatt Objekt.

Wenn Sie nur die Verwendung wünschen ein numerischer Typ, könnten Sie erwägen, in C++ etwas Ähnliches wie einen Alias ​​zu erstellen using.

Anstatt also das sehr Allgemeine 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) { ... }

Das könnte Ihnen einen einfachen Übergang ermöglichen double Zu int oder andere, wenn nötig, aber Sie könnten sie nicht verwenden ComputeSomething mit double Und int im selben Programm.

Aber warum nicht alles ersetzen? double Zu int Dann?Weil Ihre Methode möglicherweise a verwenden möchte double ob die Eingabe erfolgt double oder int.Mithilfe des Alias ​​können Sie genau wissen, welche Variable das verwendet dynamisch Typ.

Ich hatte eine ähnliche Situation, in der ich mit numerischen Typen und Zeichenfolgen umgehen musste.Scheint eine etwas bizarre Mischung zu sein, aber los geht's.

Wie viele andere habe ich mir auch hier die Einschränkungen angesehen und eine Reihe von Schnittstellen entwickelt, die unterstützt werden mussten.Allerdings war es a) nicht 100 % wasserdicht und b) wäre jeder, der sich diese lange Liste von Einschränkungen zum ersten Mal anschaut, sofort sehr verwirrt.

Mein Ansatz bestand also darin, meine gesamte Logik ohne Einschränkungen in eine generische Methode zu packen, 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 verarbeiten wollte. Meiner Meinung nach ist der Code sauber und explizit, z. B.

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
}

Es gibt keine einzelne Schnittstelle oder Basisklasse, die sie alle erben (die nicht auch von anderen Klassen geerbt wird), daher lautet die einfache Antwort „Nein“.

Ich frage mich allerdings, warum das ein Problem ist.Was möchten Sie in Ihrer IntegerFunction-Klasse tun, was nur mit ganzen Zahlen möglich ist?

Ich glaube, Sie verstehen Generika falsch.Wenn der Vorgang, den Sie ausführen möchten, nur für bestimmte Datentypen geeignet ist, tun Sie nichts „Allgemeines“.

Da Sie der Funktion außerdem nur erlauben möchten, mit int-Datentypen zu arbeiten, sollten Sie nicht für jede spezifische Größe eine separate Funktion benötigen.Wenn Sie einfach einen Parameter des größten spezifischen Typs übernehmen, kann das Programm automatisch die kleineren Datentypen dorthin umwandeln.(d. h.Die Übergabe eines Int16 wird beim Aufruf automatisch in Int64 konvertiert.

Wenn Sie basierend auf der tatsächlichen Größe von int, die an die Funktion übergeben wird, unterschiedliche Vorgänge ausführen, sollten Sie meines Erachtens den Versuch, das zu tun, was Sie tun, ernsthaft überdenken.Wenn Sie die Sprache täuschen müssen, sollten Sie etwas mehr darüber nachdenken, was Sie erreichen wollen, und nicht darüber, wie Sie das erreichen, was Sie wollen.

Andernfalls könnte 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top