Czy istnieje ograniczenie, które ogranicza moją ogólną metodę do typów numerycznych?

364

Czy ktoś może mi powiedzieć, czy istnieje sposób, aby za pomocą generyków ograniczyć argument typu ogólnego Tdo tylko:

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

Znam wheresłowo kluczowe, ale nie mogę znaleźć interfejsu tylko dla tych typów,

Coś jak:

static bool IntegerFunction<T>(T value) where T : INumeric 
Corin Blaikie
źródło
2
Istnieją teraz różne propozycje języka C #, które pozwoliłyby na osiągnięcie tego, ale AFAIK żadna z nich nie jest niczym więcej niż wstępnymi badaniami / dyskusjami. Zobacz eksploracja: kształty i rozszerzenia , eksploracja: role, interfejsy rozszerzeń i elementy interfejsu statycznego , Bohater „Klasy typów (zwane także koncepcjami, strukturalne ograniczenia ogólne)” oraz Propozycja: Typy ogólne powinny obsługiwać operatorów
Chris Yungmann,

Odpowiedzi:

140

C # nie obsługuje tego. Hejlsberg opisał powody niewdrożenia tej funkcji w wywiadzie dla Bruce'a Eckela :

I nie jest jasne, czy dodatkowa złożoność jest warta niewielkiej wydajności, którą otrzymujesz. Jeśli coś, co chcesz zrobić, nie jest bezpośrednio obsługiwane w systemie ograniczeń, możesz to zrobić przy użyciu wzorca fabrycznego. Możesz mieć Matrix<T>np Matrix. Metodę definiowania iloczynu punktowego. Że oczywiście oznacza to, że w końcu trzeba zrozumieć, w jaki sposób pomnożyć dwa Ts, ale nie można powiedzieć, że jako ograniczenie, przynajmniej jeśli nie Tjest int, doublealbo float. Ale to, co możesz zrobić, to Matrixpotraktować jako argument Calculator<T>i Calculator<T>mieć wywołaną metodę multiply. Idź zaimplementuj to i przekaż to Matrix.

Prowadzi to jednak do dość skomplikowanego kodu, w którym użytkownik musi dostarczyć własną Calculator<T>implementację dla każdego T, którego chce użyć. O ile nie musi to być rozszerzalne, tj. Jeśli chcesz obsługiwać określoną liczbę typów, takich jak inti double, możesz uciec ze stosunkowo prostym interfejsem:

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

( Minimalna implementacja w GitHub Gist. )

Jednak gdy tylko chcesz, aby użytkownik mógł dostarczyć własne, niestandardowe typy, musisz otworzyć tę implementację, aby użytkownik mógł dostarczyć własne Calculatorwystąpienia. Na przykład, aby utworzyć instancję macierzy korzystającej z niestandardowej dziesiętnej implementacji zmiennoprzecinkowej DFP, musisz napisać ten kod:

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

… I zaimplementuj wszystkich członków DfpCalculator : ICalculator<DFP>.

Alternatywą, która niestety łączy te same ograniczenia, jest praca z klasami polityki, jak omówiono w odpowiedzi Siergieja Shandara .

Konrad Rudolph
źródło
25
btw, MiscUtil zapewnia ogólną klasę, która robi dokładnie to; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
1
@ Mark: dobry komentarz. Jednak dla jasności nie sądzę, aby Hejlsberg odnosił się do generowania kodu jako rozwiązania problemu tak, jak to robisz w Operator<T>kodzie (ponieważ wywiad został udzielony na długo przed istnieniem Expressionsframeworka, chociaż można by Oczywiście użycie Reflection.Emit) - i byłbym bardzo zainteresowany jego obejścia.
Konrad Rudolph,
@Konrad Rudolph: Myślę, że ta odpowiedź na podobne pytanie wyjaśnia obejście Hejlsberga. Druga klasa ogólna jest abstrakcyjna. Ponieważ wymaga to zaimplementowania drugiej klasy ogólnej dla każdego typu, który chcesz obsługiwać, spowoduje to zduplikowanie kodu, ale oznacza to, że możesz utworzyć tylko oryginalną klasę ogólną z obsługiwanym typem.
Ergwun,
14
Nie zgadzam się z wyrażeniem Heijsberga: „W pewnym sensie szablony C ++ są w rzeczywistości bez typu lub luźno wpisane. Podczas gdy generyczne C # są mocno wpisane.”. To naprawdę Marketing BS promujący C #. Silne / słabe pisanie nie ma związku z jakością diagnostyki. W przeciwnym razie: ciekawe znalezisko.
Sebastian Mach
100

Biorąc pod uwagę popularność tego pytania i zainteresowanie taką funkcją, jestem zaskoczony, widząc, że nie ma jeszcze odpowiedzi dotyczącej T4.

W tym przykładowym kodzie pokażę bardzo prosty przykład, w jaki sposób można wykorzystać potężny silnik szablonów do robienia tego, co kompilator właściwie robi za kulisami za pomocą generyków.

Zamiast przechodzić przez obręcze i poświęcać pewność kompilacji, możesz po prostu wygenerować pożądaną funkcję dla każdego typu, który ci się podoba i użyć jej odpowiednio (w czasie kompilacji!).

Aby to zrobić:

  • Utwórz nowy plik szablonu tekstowego o nazwie GenericNumberMethodTemplate.tt .
  • Usuń automatycznie wygenerowany kod (zachowasz większość, ale niektóre nie są potrzebne).
  • Dodaj następujący fragment kodu:
<#@ 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;
        }
    <#
    } #>
}

Otóż ​​to. Już skończyłeś.

Zapisanie tego pliku automatycznie skompiluje go do tego pliku źródłowego:

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

W swojej mainmetodzie możesz sprawdzić, czy masz pewność kompilacji:

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

wprowadź opis zdjęcia tutaj

Wyprzedzę jedną uwagę: nie, to nie jest naruszenie zasady SUSZENIA. Zasada DRY ma na celu zapobieganie powielaniu kodu w wielu miejscach, co utrudniłoby utrzymanie aplikacji.

W tym przypadku wcale tak nie jest: jeśli chcesz zmiany, możesz po prostu zmienić szablon (jedno źródło dla całego pokolenia!) I gotowe.

Aby użyć go z własnymi niestandardowymi definicjami, dodaj deklarację przestrzeni nazw (upewnij się, że jest taka sama jak ta, w której zdefiniujesz własną implementację) do wygenerowanego kodu i oznacz klasę jako partial. Następnie dodaj te wiersze do pliku szablonu, aby został włączony do ostatecznej kompilacji:

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

Bądźmy szczerzy: to całkiem fajne.

Oświadczenie: Kevin Hazzard i Jason Bock, Manning Publications, na tę próbkę mieli duży wpływ Metaprogramowanie w .NET .

Jeroen Vannevel
źródło
To całkiem fajne, ale czy byłoby możliwe zmodyfikowanie tego rozwiązania, aby metody akceptowały jakiś rodzaj ogólny, Tktóry jest lub dziedziczy z różnych IntXklas? Podoba mi się to rozwiązanie, ponieważ oszczędza czas, ale aby w 100% rozwiązać problem (mimo że nie jest tak przyjemny, jakby C # obsługiwał tego typu ograniczenia, wbudowane), każda z wygenerowanych metod powinna być ogólna, aby mogą zwrócić obiekt typu, który dziedziczy po jednej z IntXXklas.
Zachary Kniebel
1
@ZacharyKniebel: IntXXtypy są strukturami, co oznacza, że nie obsługują dziedziczenia . A nawet gdyby tak się stało wówczas zasadę substytucji Liskov (który może znasz ze stałego idiomu) obowiązuje: jeśli metoda jest zdefiniowana jako Xi Yjest dzieckiem Xwtedy z definicji każdy Ypowinien móc być przekazywane do tej metody jako substytut jego typ podstawowy.
Jeroen Vannevel
1
To obejście przy użyciu zasad stackoverflow.com/questions/32664/... używa T4 do generowania klas.
Sergey Shandar
2
+1 dla tego rozwiązania, ponieważ zachowuje efektywność działania wbudowanych typów integralnych, w przeciwieństwie do rozwiązań opartych na zasadach. Wywoływanie wbudowanych operatorów CLR (takich jak Add) za pomocą dodatkowej (prawdopodobnie wirtualnej) metody może poważnie wpłynąć na wydajność, jeśli jest używane wielokrotnie (jak w bibliotekach matematycznych). A ponieważ liczba typów całkowitych jest stała (i nie można się z niej dziedziczyć), wystarczy ponownie wygenerować kod w celu usunięcia błędów.
Attila Klenik
1
Bardzo fajnie i właśnie miałem zacząć go używać, a potem przypomniałem sobie, jak jestem zależny od Resharpera do refaktoryzacji i nie można zmienić nazwy refaktora za pomocą szablonu T4. To nie jest krytyczne, ale warte rozważenia.
bradgonesurfing 20.04.16
86

Nie ma na to żadnych ograniczeń. To prawdziwy problem dla każdego, kto chce używać ogólnych danych do obliczeń numerycznych.

Poszedłbym dalej i powiedział, że potrzebujemy

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

Lub nawet

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

Niestety masz tylko interfejsy, klasy podstawowe i słowa kluczowe struct(musi być typem wartości), class(musi być typem referencyjnym) i new()(musi mieć domyślny konstruktor)

Możesz zawinąć liczbę w coś innego (podobnego do INullable<T>) jak tutaj w projekcie kodowym .


Możesz zastosować ograniczenie w czasie wykonywania (przez zastanawianie się nad operatorami lub sprawdzanie typów), ale to nie ma przewagi, że generyczne jest na pierwszym miejscu.

Keith
źródło
2
Zastanawiam się, czy widziałeś wsparcie MiscUtil dla operatorów ogólnych ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
10
Tak - Jon Skeet wskazał mi jakiś czas temu (ale po tegorocznej odpowiedzi) - to sprytny pomysł, ale nadal chciałbym mieć odpowiednie wsparcie dla ograniczeń.
Keith,
1
Czekaj, czy where T : operators( +, -, /, * )jest legalny C #? Przepraszamy za pytanie dla początkujących.
kdbanman,
@kdbanman Nie sądzę. Keith mówi, że C # nie obsługuje tego, o co prosi OP, i sugeruje, że powinniśmy być w stanie to zrobić where T : operators( +, -, /, * ), ale nie możemy.
AMTerp
62

Obejście przy użyciu zasad:

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

Algorytmy:

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

}

Stosowanie:

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.

Rozwiązanie jest bezpieczne podczas kompilacji. CityLizard Framework zapewnia skompilowaną wersję .NET 4.0. Plik to lib / NETFramework4.0 / CityLizard.Policy.dll.

Jest również dostępny w Nuget: https://www.nuget.org/packages/CityLizard/ . Zobacz strukturę CityLizard.Policy.I .

Sergey Shandar
źródło
Miałem problemy z tym wzorcem, gdy argumentów funkcji jest mniej niż parametrów ogólnych. Otwarty stackoverflow.com/questions/36048248/…
xvan
jakiś powód, dlaczego używać struct? co jeśli użyję zamiast tego klasy singleton i zmienię instancję na, public static NumericPolicies Instance = new NumericPolicies();a następnie dodam ten konstruktor private NumericPolicies() { }.
M.kazem Akhgary
@ M.kazemAkhgary możesz korzystać z singletonu. Wolę struct. Teoretycznie można go zoptymalizować za pomocą kompilatora / CLR, ponieważ struktura nie zawiera żadnych informacji. W przypadku singletonu nadal będziesz przekazywać referencje, co może dodatkowo zwiększyć presję na GC. Kolejną zaletą jest to, że struct nie może mieć wartości zerowej :-).
Sergey Shandar,
Chciałem powiedzieć, że znalazłeś bardzo inteligentne rozwiązanie, ale rozwiązanie jest dla mnie zbyt ograniczone: zamierzałem go użyć T Add<T> (T t1, T t2), ale Sum()działa tylko wtedy, gdy może odzyskać swój własny typ T z jego parametrów, co nie jest możliwe gdy jest osadzony w innej funkcji ogólnej.
Tobias Knauss
16

To pytanie jest trochę FAQ, więc zamieszczam je jako wiki (ponieważ pisałem wcześniej podobne, ale jest to starsze); tak czy siak...

Jakiej wersji .NET używasz? Jeśli używasz .NET 3.5, to mam ogólną implementację operatorów w MiscUtil (darmowy itp.).

Ma to metody takie jak T Add<T>(T x, T y)i inne warianty arytmetyki na różnych typach (jak DateTime + TimeSpan).

Dodatkowo działa to dla wszystkich wbudowanych, podniesionych i wykonanych na zamówienie operatorów oraz buforuje delegata pod kątem wydajności.

Dodatkowe informacje na temat tego, dlaczego jest to trudne, znajdują się tutaj .

Możesz także wiedzieć, że dynamic(4.0) rozwiązuje ten problem również pośrednio - tj

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Marc Gravell
źródło
14

Niestety w tym przypadku możesz określić struct tylko w klauzuli where. Wydaje się dziwne, że nie można dokładnie określić Int16, Int32 itp., Ale jestem pewien, że istnieje głęboka przyczyna implementacji leżąca u podstaw decyzji o niedopuszczeniu typów wartości w klauzuli where.

Wydaje mi się, że jedynym rozwiązaniem jest sprawdzenie środowiska uruchomieniowego, co niestety zapobiega wykrywaniu problemu w czasie kompilacji. To pójdzie coś takiego:

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

Co jest trochę brzydkie, wiem, ale przynajmniej zapewnia wymagane ograniczenia.

Zastanowiłbym się również nad możliwymi implikacjami dotyczącymi wydajności dla tej implementacji, być może istnieje szybsze wyjście.

ljs
źródło
13
+1, jednak // Rest of code...może się nie kompilować, jeśli zależy to od operacji zdefiniowanych przez ograniczenia.
Nick
1
Convert.ToIntXX (wartość) może pomóc w kompilacji „// Rest of code” - przynajmniej do czasu, gdy zwracana funkcja IntegerFunction będzie również typu T, wtedy zostaniesz oszukany. :-p
yoyo
-1; to nie działa z powodu podanego przez @Nick. W momencie, gdy próbujesz wykonać dowolne operacje arytmetyczne w // Rest of code...podobny value + valuelub value * value, masz błąd kompilacji.
Mark Amery
13

Prawdopodobnie najbliższe jest to, co możesz zrobić

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

Nie jestem pewien, czy możesz wykonać następujące czynności

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

W przypadku czegoś tak specyficznego, dlaczego nie mieć przeciążeń dla każdego typu, lista jest tak krótka i prawdopodobnie zajmowałaby mniej miejsca w pamięci.

Haacked
źródło
6

Począwszy od wersji C # 7.3, można użyć dokładniejszego przybliżenia - ograniczenia niezarządzanego w celu określenia, że ​​parametr typu jest niezarządzalnym wskaźnikiem, nie dopuszczającym wartości zerowej .

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

Wiązanie niezarządzane implikuje ograniczenie struktury i nie może być łączone z ograniczeniami struct ani new ().

Typ jest typem niezarządzanym, jeśli jest jednym z następujących typów:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal lub bool
  • Dowolny typ wyliczenia
  • Dowolny typ wskaźnika
  • Dowolny typ struktury zdefiniowany przez użytkownika, który zawiera tylko pola typów niezarządzanych, aw wersjach C # 7.3 i wcześniejszych nie jest typem skonstruowanym (typ, który zawiera co najmniej jeden argument typu)

Aby dodatkowo ograniczyć i wyeliminować typy wskaźników i zdefiniowane przez użytkownika, które nie implementują IComparable, dodaj IComparable (ale wyliczanie nadal pochodzi z IComparable, więc ogranicz wyliczanie przez dodanie IEquatable <T>, możesz pójść dalej w zależności od okoliczności i dodać dodatkowe interfejsy. niezarządzany pozwala skrócić tę listę):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }
Vlad Novakovsky
źródło
Fajnie, ale niewystarczająco ... Na przykład DateTimepodlega unmanaged, IComparable, IEquatable<T>ograniczeniom ...
Adam Calvet Bohl
Wiem, ale możesz iść dalej, w zależności od okoliczności i dodać dodatkowe interfejsy. niezarządzany pozwala skrócić tę listę. właśnie pokazałem podejście, przybliżenie przy użyciu niezarządzanego. W większości przypadków to wystarczy
Vlad Novakovsky
4

Nie ma możliwości ograniczenia szablonów do typów, ale można zdefiniować różne działania w zależności od typu. W ramach ogólnego pakietu numerycznego potrzebowałem klasy ogólnej, aby dodać dwie wartości.

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

Zauważ, że typeofs są oceniane w czasie kompilacji, więc instrukcje if zostaną usunięte przez kompilator. Kompilator usuwa również fałszywe rzutowania. Coś więc rozwiązałoby się w kompilatorze

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Rob Deary
źródło
Dziękujemy za dostarczenie empirycznego rozwiązania!
zsf222,
Czy to nie to samo, co tworzenie tej samej metody dla każdego typu?
Luis
3

Stworzyłem małą funkcjonalność biblioteki, aby rozwiązać te problemy:

Zamiast:

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.

Możesz napisać:

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.

Możesz znaleźć kod źródłowy tutaj: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number

Martin Mulder
źródło
2

Zastanawiałem się tak samo jak samjudson, dlaczego tylko liczby całkowite? a jeśli tak jest, możesz utworzyć klasę pomocnika lub coś takiego, aby pomieścić wszystkie typy, które chcesz.

Jeśli wszystko, czego chcesz, to liczby całkowite, nie używaj ogólnego, który nie jest ogólny; lub jeszcze lepiej, odrzuć dowolny inny typ, sprawdzając jego typ.

Martin Marconcini
źródło
2

Nie ma na to „dobrego” rozwiązania. Jednak można znacznie zawęzić argument typu, aby wykluczyć wiele niedopasowań związanych z hipotetycznym ograniczeniem „INumeric”, jak pokazał Haacked powyżej.

static bool IntegerFunction <T> (wartość T) gdzie T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...

dmihailescu
źródło
2

Jeśli używasz .NET 4.0 i nowszych wersji, możesz po prostu użyć argumentu dynamicznego jako argumentu metody i sprawdzić w środowisku wykonawczym , czy przekazany typ argumentu dynamicznego jest typu liczbowego / liczb całkowitych.

Jeśli typ przekazywanej dynamiki nie jest typem liczbowym / całkowitym, wyrzuć wyjątek.

Przykładowy krótki kod, który implementuje ten pomysł, to coś takiego:

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

Oczywiście, że to rozwiązanie działa tylko w czasie wykonywania, ale nigdy w czasie kompilacji.

Jeśli chcesz rozwiązania, które zawsze działa w czasie kompilacji, a nigdy w czasie wykonywania, będziesz musiał zawinąć dynamikę w publiczną strukturę / klasę, której przeciążone publiczne konstruktory akceptują tylko argumenty pożądanych typów i nadają struktury / klasie odpowiednią nazwę.

Ma sens, że zawinięta dynamika jest zawsze prywatnym członkiem klasy / struct i jest jedynym członkiem struct / class, a nazwa jedynego członka struct / class to „wartość”.

Będziesz także musiał zdefiniować i wdrożyć publiczne metody i / lub operatory, które działają z pożądanymi typami dla prywatnego dynamicznego członka klasy / struktury, jeśli to konieczne.

Ma to również sens, że struct / klasa ma specjalny / unikalny konstruktor, który akceptuje dynamikę jako argument inicjujący tylko prywatny element dynamiczny o nazwie „wartość”, ale modyfikator tego konstruktora jest oczywiście prywatny .

Gdy klasa / struktura będzie gotowa, zdefiniuj typ argumentu IntegerFunction, aby była to klasa / struktura, która została zdefiniowana.

Przykładowy długi kod, który implementuje ten pomysł, to coś takiego:

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

Pamiętaj, że aby użyć dynamiki w kodzie, musisz dodać odniesienie do Microsoft.CSharp

Jeśli wersja .NET Framework jest niższa / niższa / mniejsza niż 4.0, a dynamika nie jest zdefiniowana w tej wersji, będziesz musiał zamiast tego użyć obiektu i rzutować na liczbę całkowitą, co jest problemem, więc polecam użyć w przynajmniej .NET 4.0 lub nowszy, jeśli możesz, dzięki czemu możesz używać dynamicznego zamiast obiektowego .


źródło
2

Niestety .NET nie zapewnia tego w sposób natywny.

Aby rozwiązać ten problem, stworzyłem bibliotekę OSS Genumerics, która zapewnia większość standardowych operacji numerycznych dla następujących wbudowanych typów liczbowych i ich zerowalnych odpowiedników z możliwością dodania obsługi innych typów liczbowych.

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

Wydajność jest równoważna rozwiązaniu numerycznemu, umożliwiającemu tworzenie wydajnych ogólnych algorytmów numerycznych.

Oto przykład użycia kodu.

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
źródło
1

Jaki jest sens tego ćwiczenia?

Jak już zauważyli ludzie, możesz mieć nietypową funkcję zajmującą największy element, a kompilator automatycznie skonwertuje dla ciebie mniejsze int.

static bool IntegerFunction(Int64 value) { }

Jeśli twoja funkcja znajduje się na ścieżce krytycznej pod względem wydajności (bardzo mało prawdopodobne, IMO), możesz spowodować przeciążenie wszystkich potrzebnych funkcji.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
dbkk
źródło
1
Dużo pracuję metodami numerycznymi. Czasami chcę liczby całkowite, a czasem zmiennoprzecinkowe. Oba mają wersje 64-bitowe, które są optymalne dla szybkości przetwarzania. Konwersja między nimi jest okropnym pomysłem, ponieważ w każdym przypadku występują straty. Chociaż mam tendencję do używania podwójnych, czasem lepiej jest używać liczb całkowitych, ponieważ są one używane w innym miejscu. Byłoby jednak bardzo miło, gdy piszę algorytm, aby zrobić to raz i pozostawić decyzję typu typowi wymaganiom instancji.
VoteCoffee
1

Chciałbym użyć ogólnego, który można obsługiwać zewnętrznie ...

/// <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
źródło
1

To ograniczenie dotyczyło mnie, gdy próbowałem przeciążać operatorów dla typów ogólnych; ponieważ nie istniało ograniczenie „INumeric”, a z wielu innych powodów dobrzy ludzie na przepływie stosu chętnie go udostępniają, operacji nie można definiować na typach ogólnych.

Chciałem coś takiego

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

Rozwiązałem ten problem, używając dynamicznego pisania w środowisku uruchomieniowym .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 };
    }
}

Dwie rzeczy o użyciu dynamic

  1. Wydajność. Wszystkie typy wartości zostaną zapakowane.
  2. Błędy czasu wykonania. „Pokonałeś” kompilator, ale straciłeś bezpieczeństwo typu. Jeśli typ ogólny nie ma zdefiniowanego operatora, wyjątek zostanie zgłoszony podczas wykonywania.
pomeroy
źródło
1

Prymitywne typy liczbowe .NET nie mają wspólnego interfejsu, który pozwalałby na ich użycie do obliczeń. Byłoby to możliwe, aby zdefiniować własne interfejsy (np ISignedWholeNumber), które wykonują takie operacje, definiujące struktury, które zawierają jeden Int16, Int32itp i wdrożenia tych interfejsów, a następnie mają metody, które przyjmują typy generyczne ograniczona do ISignedWholeNumber, ale konieczności konwertowania wartości liczbowych dla twoich typów konstrukcji prawdopodobnie byłby uciążliwy.

Alternatywnym rozwiązaniem byłoby zdefiniowanie statycznych klasy Int64Converter<T>z właściwości statycznych bool Available {get;};i statycznych delegatów Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). Konstruktor klas mógłby użyć zakodowanego na stałe ładowania ładunków delegowanych dla znanych typów i ewentualnie użyć Reflection w celu przetestowania, czy typ Timplementuje metody o odpowiednich nazwach i podpisach (w przypadku, gdy jest to coś w rodzaju struktury, która zawiera Int64i reprezentuje liczbę, ale ma ToString()metoda niestandardowa ). Takie podejście straciłoby zalety związane z sprawdzaniem typu podczas kompilacji, ale nadal udałoby mu się uniknąć operacji bokserskich, a każdy typ musiałby być „sprawdzony” tylko raz. Następnie operacje powiązane z tym typem zostaną zastąpione wysyłką delegowaną.

supercat
źródło
@KenKin: IConvertible zapewnia środek, za pomocą którego można dodać dowolną liczbę całkowitą do innego typu liczb całkowitych w celu uzyskania np. Int64Wyniku, ale nie zapewnia sposobu, w jaki można np. Zwiększyć liczbę całkowitą dowolnego typu, aby uzyskać inną liczbę całkowitą tego samego typu .
supercat
1

Miałem podobną sytuację, w której musiałem obsługiwać typy numeryczne i ciągi; wydaje się to trochę dziwne połączenie, ale proszę bardzo.

Ponownie, jak wiele osób, spojrzałem na ograniczenia i wymyśliłem kilka interfejsów, które musiał obsługiwać. Jednak: a) nie było w 100% wodoszczelne ib) każdy, kto spojrzy na tę długą listę ograniczeń, będzie od razu bardzo zdezorientowany.

Więc moje podejście polegało na umieszczeniu całej mojej logiki w ogólnej metodzie bez żadnych ograniczeń, ale uczynieniu tej ogólnej metody prywatną. Następnie odsłoniłem go metodami publicznymi, z których jeden wyraźnie obsługuje typ, który chciałem obsłużyć - według mnie kod jest czysty i wyraźny, np.

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
źródło
0

Jeśli wszystko, czego potrzebujesz, to użyć jednego typu liczbowego , możesz rozważyć utworzenie czegoś podobnego do aliasu w C ++ za pomocą using.

Więc zamiast mieć bardzo ogólny

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

mógłbyś mieć

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

To może pozwoli Ci łatwo przejść od doublecelu intlub inne, jeśli to konieczne, ale nie będzie w stanie korzystać ComputeSomethingz doublei intw tym samym programie.

Ale dlaczego nie zastąpić wszystkich doubledo inttego czasu? Ponieważ twoja metoda może chcieć użyć doubleparametru wejściowego doublelub int. Alias ​​pozwala dokładnie wiedzieć, która zmienna używa typu dynamicznego .

użytkownik276648
źródło
0

Temat jest stary, ale dla przyszłych czytelników:

Ta funkcja jest ściśle związana z tym, Discriminated Unionsco do tej pory nie zostało zaimplementowane w języku C #. Znalazłem jego problem tutaj:

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

Ten problem jest nadal otwarty, a funkcja została zaplanowana C# 10

Musimy jeszcze trochę poczekać, ale po wydaniu możesz to zrobić w ten sposób:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
Arman Ebrahimpour
źródło
-11

Myślę, że nie rozumiesz generycznych. Jeśli operacja, którą próbujesz wykonać, jest dobra tylko dla określonych typów danych, to nie robisz czegoś „ogólnego”.

Ponadto, ponieważ chcesz zezwolić tej funkcji tylko na int typy danych, nie powinieneś potrzebować osobnej funkcji dla każdego określonego rozmiaru. Po prostu przyjęcie parametru w największym konkretnym typie pozwoli programowi automatycznie przesłać do niego mniejsze typy danych. (tzn. przekazanie Int16 spowoduje automatyczną konwersję na Int64 podczas połączenia).

Jeśli wykonujesz różne operacje w oparciu o rzeczywisty rozmiar int przekazywany do funkcji, pomyślałem, że powinieneś poważnie przemyśleć nawet próbę zrobienia tego, co robisz. Jeśli musisz oszukać język, powinieneś pomyśleć nieco więcej o tym, co próbujesz osiągnąć, niż o tym, co chcesz zrobić.

W przeciwnym razie można użyć parametru typu Object, a następnie trzeba będzie sprawdzić typ parametru i podjąć odpowiednie działanie lub zgłosić wyjątek.

Tom Welch
źródło
10
Rozważ histogram klasy <T>. Rozsądne jest, aby pozwolić na przyjęcie parametru ogólnego, aby kompilator mógł go zoptymalizować pod kątem bajtów, liczb całkowitych, liczb podwójnych, dziesiętnych, BigInt, ... ale jednocześnie należy uniemożliwić utworzenie, powiedzmy, histogramu <Hashset >, ponieważ - rozmawiając z Tronem - nie oblicza. (dosłownie :))
sunside
15
To ty źle rozumiesz leki generyczne. Metaprogramowanie nie działa tylko na wartościach, które mogą być dowolnym możliwym typem , ale działa na typach, które pasują do różnych ograniczeń .
Jim Balter