Apakah ada batasan yang membatasi metode generik saya ke tipe numerik?

364

Adakah yang bisa memberi tahu saya jika ada cara dengan obat generik untuk membatasi argumen tipe generik Thanya:

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

Saya mengetahui wherekata kunci, tetapi tidak dapat menemukan antarmuka hanya untuk jenis ini,

Sesuatu seperti:

static bool IntegerFunction<T>(T value) where T : INumeric 
Corin Blaikie
sumber
2
Sekarang ada berbagai proposal C # yang memungkinkan penyelesaian hal ini, tetapi AFAIK tidak satupun dari mereka yang lebih dari eksplorasi / diskusi awal. Lihat Eksplorasi: Bentuk dan Ekstensi , Eksplorasi: Peran, antarmuka ekstensi dan anggota antarmuka statis , Champion "Tipe Kelas (alias Konsep, Kendala Generik Struktural)" , dan Proposal: Jenis generik harus mendukung operator
Chris Yungmann

Jawaban:

140

C # tidak mendukung ini. Hejlsberg telah menjelaskan alasan untuk tidak menerapkan fitur ini dalam sebuah wawancara dengan Bruce Eckel :

Dan tidak jelas bahwa kompleksitas yang ditambahkan sepadan dengan hasil kecil yang Anda dapatkan. Jika sesuatu yang ingin Anda lakukan tidak secara langsung didukung dalam sistem kendala, Anda dapat melakukannya dengan pola pabrik. Anda dapat memiliki Matrix<T>, misalnya, dan MatrixAnda ingin mendefinisikan metode titik produk. Itu tentu saja itu berarti Anda akhirnya perlu memahami bagaimana untuk kalikan dua Ts, tetapi Anda tidak bisa mengatakan bahwa sebagai kendala, setidaknya tidak jika Tadalah int, double, atau float. Tapi yang bisa Anda lakukan adalah Matrixmengambil argumen Anda sebagai a Calculator<T>, dan Calculator<T>, memiliki metode yang disebut multiply. Anda pergi menerapkannya dan Anda meneruskannya ke Matrix.

Namun, ini mengarah pada kode yang cukup berbelit-belit, di mana pengguna harus menyediakan Calculator<T>implementasi mereka sendiri , untuk masing-masing Tyang ingin mereka gunakan. Selama itu tidak harus diperluas, yaitu jika Anda hanya ingin mendukung sejumlah jenis tetap, seperti intdan double, Anda dapat pergi dengan antarmuka yang relatif sederhana:

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

( Implementasi minimal dalam GitHub Gist. )

Namun, segera setelah Anda ingin pengguna dapat menyediakan jenis kustom mereka sendiri, Anda perlu membuka implementasi ini sehingga pengguna dapat menyediakan Calculatorcontoh mereka sendiri . Misalnya, untuk membuat instance matriks yang menggunakan implementasi floating point desimal khusus DFP, Anda harus menulis kode ini:

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

... dan laksanakan semua anggota untuk DfpCalculator : ICalculator<DFP>.

Alternatif, yang sayangnya memiliki batasan yang sama, adalah bekerja dengan kelas kebijakan, seperti yang dibahas dalam jawaban Sergey Shandar .

Konrad Rudolph
sumber
25
btw, MiscUtil menyediakan kelas generik yang melakukan ini; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
1
@ Mark: komentar yang bagus. Namun, hanya untuk memperjelas, saya tidak berpikir bahwa Hejlsberg merujuk pada pembuatan kode sebagai solusi untuk masalah seperti yang Anda lakukan dalam Operator<T>kode (karena wawancara telah diberikan jauh sebelum adanya Expressionskerangka kerja, meskipun orang dapat penggunaan saja Reflection.Emit) - dan saya akan sangat tertarik dengan solusinya.
Konrad Rudolph
@Konrad Rudolph: Saya pikir jawaban ini untuk pertanyaan serupa menjelaskan solusi Hejlsberg. Kelas generik lainnya dibuat abstrak. Karena mengharuskan Anda untuk mengimplementasikan kelas generik lain untuk setiap jenis yang ingin Anda dukung, ini akan menghasilkan kode duplikat, tetapi berarti Anda hanya dapat membuat instance kelas generik asli dengan jenis yang didukung.
Ergwun
14
Saya tidak setuju dengan frasa Heijsberg "Jadi dalam arti, template C ++ sebenarnya tidak diketik, atau diketik secara longgar. Sedangkan generik C # sangat diketik.". Itu benar-benar Pemasaran BS untuk mempromosikan C #. Pengetikan Kuat / Lemah tidak berkaitan dengan kualitas diagnostik. Sebaliknya: Temuan menarik.
Sebastian Mach
100

Mengingat popularitas pertanyaan ini dan minat di balik fungsi seperti itu, saya terkejut melihat bahwa belum ada jawaban yang melibatkan T4.

Dalam kode contoh ini saya akan menunjukkan contoh yang sangat sederhana tentang bagaimana Anda dapat menggunakan mesin templating yang kuat untuk melakukan apa yang dilakukan oleh kompiler di belakang layar dengan obat generik.

Alih-alih melewati rintangan dan mengorbankan kepastian waktu kompilasi, Anda cukup membuat fungsi yang Anda inginkan untuk setiap tipe yang Anda sukai dan menggunakannya sesuai itu (pada waktu kompilasi!).

Untuk melakukan ini:

  • Buat file Template Teks baru yang disebut GenericNumberMethodTemplate.tt .
  • Hapus kode yang dibuat secara otomatis (Anda akan menyimpan sebagian besar, tetapi beberapa tidak diperlukan).
  • Tambahkan cuplikan berikut:
<#@ 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;
        }
    <#
    } #>
}

Itu dia. Anda selesai sekarang.

Menyimpan file ini akan secara otomatis mengompilasinya ke file sumber ini:

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

Dalam mainmetode Anda, Anda dapat memverifikasi bahwa Anda memiliki kepastian waktu kompilasi:

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

masukkan deskripsi gambar di sini

Saya akan mendahului satu komentar: tidak, ini bukan pelanggaran prinsip KERING. Prinsip KERING ada untuk mencegah orang dari menggandakan kode di banyak tempat yang akan menyebabkan aplikasi menjadi sulit untuk dipelihara.

Ini sama sekali tidak terjadi di sini: jika Anda ingin perubahan maka Anda bisa mengubah template (satu sumber untuk semua generasi Anda!) Dan selesai.

Untuk menggunakannya dengan definisi kustom Anda sendiri, tambahkan deklarasi namespace (pastikan itu adalah yang sama dengan yang di mana Anda akan mendefinisikan implementasi Anda sendiri) ke kode yang Anda hasilkan dan tandai kelas sebagai partial. Setelah itu, tambahkan baris ini ke file template Anda sehingga akan dimasukkan dalam kompilasi akhirnya:

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

Jujur saja: Ini keren sekali.

Penafian: sampel ini sangat dipengaruhi oleh Metaprogramming dalam .NET oleh Kevin Hazzard dan Jason Bock, Manning Publications .

Jeroen Vannevel
sumber
Ini sangat keren, tetapi apakah mungkin untuk memodifikasi solusi ini untuk membuat metode menerima beberapa tipe generik Tyang ada atau mewarisi dari berbagai IntXkelas? Saya suka solusi ini karena menghemat waktu, tetapi untuk itu untuk menyelesaikan masalah 100% (meskipun tidak sebaik jika # C memiliki dukungan untuk jenis kendala ini, built-in) masing-masing metode yang dihasilkan masih harus generik sehingga mereka dapat mengembalikan objek tipe yang mewarisi dari salah satu IntXXkelas.
Zachary Kniebel
1
@ZacharyKniebel: IntXXjenisnya adalah struct yang berarti mereka tidak mendukung warisan sejak awal . Dan bahkan jika itu terjadi maka prinsip substitusi Liskov (yang mungkin Anda ketahui dari idiom SOLID) berlaku: jika metode ini didefinisikan sebagai Xdan Ymerupakan anak dari Xsetiap definisi maka setiap Yharus dapat diteruskan ke metode itu sebagai pengganti jenis dasarnya.
Jeroen Vannevel
1
Pemecahan masalah ini menggunakan kebijakan stackoverflow.com/questions/32664/… tidak menggunakan T4 untuk menghasilkan kelas.
Sergey Shandar
2
+1 untuk solusi ini karena menjaga efisiensi operasi dari tipe integral bawaan, tidak seperti solusi berbasis kebijakan. Memanggil operator CLR bawaan (seperti Tambah) melalui metode tambahan (mungkin virtual) dapat sangat memengaruhi kinerja jika digunakan berkali-kali (seperti di perpustakaan matematika). Dan karena jumlah tipe integral adalah konstan (dan tidak dapat diwarisi dari) Anda hanya perlu membuat ulang kode untuk perbaikan bug.
Attila Klenik
1
Sangat keren dan saya baru saja akan mulai menggunakannya kemudian saya ingat betapa tergantung pada Resharper saya untuk refactoring dan Anda tidak dapat melakukan rename refactor melalui template T4. Itu tidak penting tetapi layak dipertimbangkan.
bradgonesurfing
86

Tidak ada kendala untuk ini. Ini masalah nyata bagi siapa pun yang ingin menggunakan obat generik untuk perhitungan numerik.

Saya akan melangkah lebih jauh dan mengatakan kita perlu

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

Atau bahkan

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

Sayangnya Anda hanya memiliki antarmuka, kelas dasar, dan kata kunci struct(harus tipe nilai), class(harus tipe referensi) dan new()(harus memiliki konstruktor default)

Anda bisa membungkus nomor itu dengan sesuatu yang lain (mirip dengan INullable<T>) seperti di sini di proyek codep .


Anda bisa menerapkan pembatasan pada saat runtime (dengan merefleksikan operator atau memeriksa jenis) tetapi itu memang kehilangan keuntungan memiliki generik di tempat pertama.

Keith
sumber
2
Saya ingin tahu apakah Anda telah melihat dukungan MiscUtil untuk operator generik ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
10
Ya - Jon Skeet menunjuk saya pada mereka untuk sesuatu yang lain beberapa waktu yang lalu (tapi setelah tanggapan tahun ini) - mereka adalah ide yang cerdas, tapi saya masih ingin dukungan kendala yang tepat.
Keith
1
Tunggu, where T : operators( +, -, /, * )apakah legal C #? Maaf untuk pertanyaan pemula.
kdbanman
@kdbanman kurasa tidak. Keith mengatakan bahwa C # tidak mendukung apa yang diminta OP, dan menyarankan agar kita bisa melakukannya where T : operators( +, -, /, * ), tetapi tidak bisa.
AMTerp
62

Penanganan masalah menggunakan kebijakan:

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

Algoritma:

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

}

Pemakaian:

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.

Solusinya adalah waktu kompilasi yang aman. CityLizard Framework menyediakan versi kompilasi untuk .NET 4.0. File tersebut adalah lib / NETFramework4.0 / CityLizard.Policy.dll.

Ini juga tersedia di Nuget: https://www.nuget.org/packages/CityLizard/ . Lihat struktur CityLizard.Policy.I .

Sergey Shandar
sumber
Saya punya masalah dengan pola ini ketika ada argumen fungsi kurang dari parameter generik. Membuka stackoverflow.com/questions/36048248/…
xvan
alasan mengapa menggunakan struct? bagaimana jika saya menggunakan kelas tunggal sebagai gantinya dan mengubah instance ke public static NumericPolicies Instance = new NumericPolicies();dan kemudian menambahkan konstruktor ini private NumericPolicies() { }.
M.kazem Akhgary
@ M.kazemAkhgary Anda dapat menggunakan singleton. Saya lebih suka struct. Secara teori, ini dapat dioptimalkan oleh kompiler / CLR karena struct tidak mengandung informasi. Dalam kasus singleton, Anda masih akan melewati referensi, yang dapat menambah tekanan tambahan pada GC. Keuntungan lain adalah bahwa struct tidak boleh nol :-).
Sergey Shandar
Saya akan mengatakan bahwa Anda menemukan solusi yang sangat cerdas, tetapi solusinya terlalu terbatas bagi saya: Saya akan menggunakannya T Add<T> (T t1, T t2), tetapi Sum()hanya berfungsi ketika dapat mengambil sendiri tipe T dari parameternya, yang tidak mungkin ketika itu tertanam dalam fungsi generik lain.
Tobias Knauss
16

Pertanyaan ini sedikit seperti FAQ, jadi saya memposting ini sebagai wiki (karena saya pernah memposting yang serupa sebelumnya, tapi ini yang lebih tua); bagaimanapun...

Versi NET. Apa yang Anda gunakan? Jika Anda menggunakan .NET 3.5, maka saya memiliki implementasi operator generik di MiscUtil (gratis dll).

Ini memiliki metode seperti T Add<T>(T x, T y), dan varian lain untuk aritmatika pada berbagai jenis (seperti DateTime + TimeSpan).

Selain itu, ini berfungsi untuk semua operator inbuilt, mengangkat dan dipesan lebih dahulu, dan cache delegasi untuk kinerja.

Beberapa latar belakang tambahan tentang mengapa ini rumit ada di sini .

Anda mungkin juga ingin tahu bahwa dynamic(4.0) semacam memecahkan masalah ini secara tidak langsung juga - yaitu

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

Sayangnya Anda hanya dapat menentukan struct di klausa where dalam hal ini. Tampaknya aneh Anda tidak dapat menentukan Int16, Int32, dll secara khusus, tetapi saya yakin ada beberapa alasan implementasi mendalam yang mendasari keputusan untuk tidak mengizinkan tipe nilai dalam klausa mana.

Saya kira satu-satunya solusi adalah dengan melakukan pemeriksaan runtime yang sayangnya mencegah masalah diambil pada waktu kompilasi. Itu akan seperti: -

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

Yang agak jelek, saya tahu, tapi setidaknya memberikan kendala yang diperlukan.

Saya juga melihat kemungkinan implikasi kinerja untuk implementasi ini, mungkin ada cara yang lebih cepat di luar sana.

ls
sumber
13
+1, Namun, // Rest of code...mungkin tidak dapat dikompilasi jika tergantung pada operasi yang ditentukan oleh kendala.
Nick
1
Convert.ToIntXX (nilai) dapat membantu membuat kompilasi "// Istirahat kode" - setidaknya sampai tipe pengembalian IntegerFunction juga bertipe T, maka Anda di-hooping. :-p
yoyo
-1; ini tidak berfungsi karena alasan yang diberikan oleh @Nick. Saat Anda mencoba melakukan operasi aritmatika dalam // Rest of code...suka value + valueatau value * value, Anda punya kesalahan kompilasi.
Mark Amery
13

Mungkin yang terdekat yang bisa Anda lakukan adalah

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

Tidak yakin apakah Anda bisa melakukan yang berikut ini

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

Untuk sesuatu yang sangat spesifik, mengapa tidak hanya memiliki kelebihan untuk masing-masing jenis, daftarnya sangat singkat dan mungkin memiliki lebih sedikit jejak memori.

Haacked
sumber
6

Dimulai dengan C # 7.3, Anda dapat menggunakan pendekatan yang lebih dekat - batasan yang tidak dikelola untuk menentukan bahwa parameter tipe adalah tipe tidak dikelola yang bukan penunjuk, tidak dapat dibatalkan .

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

Kendala yang tidak dikelola menyiratkan kendala struct dan tidak dapat digabungkan dengan kendala struct atau baru ().

Tipe adalah tipe yang tidak dikelola jika salah satu dari tipe berikut:

  • sbyte, byte, pendek, ushort, int, uint, panjang, ulong, char, float, double, desimal, atau bool
  • Semua tipe enum
  • Jenis penunjuk apa pun
  • Setiap tipe struct yang ditentukan pengguna yang berisi bidang tipe yang tidak dikelola saja dan, dalam C # 7.3 dan sebelumnya, bukan tipe yang dikonstruksi (tipe yang menyertakan setidaknya satu argumen tipe)

Untuk membatasi lebih jauh dan menghilangkan pointer dan tipe yang ditentukan pengguna yang tidak mengimplementasikan IComparable, tambahkan IComparable (tetapi enum masih berasal dari IComparable, jadi batasi enum dengan menambahkan IEquatable <T>, Anda dapat melangkah lebih jauh tergantung pada keadaan Anda dan menambahkan antarmuka tambahan. tidak dikelola memungkinkan daftar ini lebih pendek):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }
Vlad Novakovsky
sumber
Bagus, tetapi tidak cukup ... Misalnya, DateTimeberada di bawah unmanaged, IComparable, IEquatable<T>batasan ..
Adam Calvet Bohl
Saya tahu tetapi Anda bisa melangkah lebih jauh tergantung pada keadaan Anda dan menambahkan antarmuka tambahan. tidak terkelola memungkinkan untuk membuat daftar ini lebih pendek. saya baru saja menunjukkan pendekatan, pendekatan menggunakan tidak terkelola. Untuk sebagian besar kasus ini sudah cukup
Vlad Novakovsky
4

Tidak ada cara untuk membatasi templat ke tipe, tetapi Anda dapat menentukan tindakan yang berbeda berdasarkan jenisnya. Sebagai bagian dari paket numerik umum, saya membutuhkan kelas generik untuk menambahkan dua nilai.

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

Perhatikan bahwa typeofs dievaluasi pada waktu kompilasi, jadi pernyataan if akan dihapus oleh kompiler. Kompiler juga menghapus gips palsu. Jadi Sesuatu akan menyelesaikan di kompiler untuk

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Rob Deary
sumber
Terima kasih telah memberikan solusi empiris!
zsf222
Apakah tidak sama dengan membuat metode yang sama untuk setiap jenis?
Luis
3

Saya membuat sedikit fungsi perpustakaan untuk mengatasi masalah ini:

Dari pada:

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.

Anda bisa menulis:

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.

Anda dapat menemukan kode sumber di sini: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number

Martin Mulder
sumber
2

Saya bertanya-tanya sama dengan samjudson, mengapa hanya untuk bilangan bulat? dan jika itu masalahnya, Anda mungkin ingin membuat kelas pembantu atau sesuatu seperti itu untuk menampung semua jenis yang Anda inginkan.

Jika semua yang Anda inginkan adalah bilangan bulat, jangan gunakan generik, itu bukan generik; atau lebih baik lagi, tolak tipe lain dengan memeriksa tipenya.

Martin Marconcini
sumber
2

Belum ada solusi 'baik' untuk ini. Namun Anda dapat mempersempit argumen tipe secara signifikan untuk mengesampingkan banyak kesalahan untuk kendala 'Inumerik' hipotetis Anda seperti yang ditunjukkan oleh Haacked di atas.

static bool IntegerFunction <T> (nilai T) di mana T: ICmembandingkan, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...

dmihailescu
sumber
2

Jika Anda menggunakan .NET 4.0 dan yang lebih baru maka Anda bisa menggunakan argumen metode dinamis dan memeriksa runtime bahwa tipe argumen dinamis yang diteruskan adalah tipe numerik / integer.

Jika jenis melewati dinamis adalah tidak numerik jenis / bilangan bulat kemudian melemparkan pengecualian.

Contoh kode pendek yang mengimplementasikan ide adalah sesuatu seperti:

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

Tentu saja solusi ini hanya bekerja dalam jangka waktu tetapi tidak pernah dalam waktu kompilasi.

Jika Anda menginginkan solusi yang selalu berfungsi dalam waktu kompilasi dan tidak pernah dalam waktu berjalan maka Anda harus membungkus dinamis dengan struct / kelas publik yang konstruktor publiknya yang kelebihan beban hanya menerima argumen dari tipe yang diinginkan saja dan memberikan nama yang sesuai dengan struct / kelas.

Masuk akal bahwa dynamic yang dibungkus selalu anggota privat dari kelas / struct dan itu adalah satu-satunya anggota struct / kelas dan nama satu-satunya anggota struct / kelas adalah "nilai".

Anda juga harus mendefinisikan dan menerapkan metode publik dan / atau operator yang bekerja dengan tipe yang diinginkan untuk anggota dinamis pribadi kelas / struct jika perlu.

Juga masuk akal bahwa struct / kelas memiliki konstruktor khusus / unik yang menerima dinamis sebagai argumen yang menginisialisasi itu hanya anggota dinamis pribadi yang disebut "nilai" tetapi pengubah konstruktor ini bersifat pribadi tentu saja.

Setelah kelas / struct siap mendefinisikan tipe argumen IntegerFunction menjadi kelas / struct yang telah didefinisikan.

Contoh kode panjang yang mengimplementasikan ide adalah sesuatu seperti:

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

Perhatikan bahwa untuk menggunakan dinamis dalam kode Anda, Anda harus Menambahkan Referensi ke Microsoft.CSharp

Jika versi .NET framework di bawah / di bawah / kurang dari 4.0 dan dinamis tidak terdefinisi dalam versi itu maka Anda harus menggunakan objek dan melakukan casting ke tipe integer, yang merupakan masalah, jadi saya sarankan Anda gunakan di Setidaknya. NET 4.0 atau yang lebih baru jika Anda bisa sehingga Anda dapat menggunakan dinamis, bukan objek .


sumber
2

Sayangnya. NET tidak menyediakan cara untuk melakukannya secara asli.

Untuk mengatasi masalah ini, saya membuat pustaka OSS Genumerics yang menyediakan sebagian besar operasi numerik standar untuk tipe numerik bawaan berikut dan setara yang dapat dibatalkan dengan kemampuan untuk menambahkan dukungan untuk tipe numerik lainnya.

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

Kinerja ini setara dengan solusi spesifik tipe numerik yang memungkinkan Anda membuat algoritma numerik generik yang efisien.

Berikut adalah contoh penggunaan kode.

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
sumber
1

Apa gunanya latihan ini?

Seperti yang sudah ditunjukkan orang, Anda bisa memiliki fungsi non-generik mengambil item terbesar, dan kompiler akan secara otomatis mengonversi int lebih kecil untuk Anda.

static bool IntegerFunction(Int64 value) { }

Jika fungsi Anda berada di jalur kritis kinerja (sangat tidak mungkin, IMO), Anda dapat memberikan kelebihan untuk semua fungsi yang diperlukan.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
dbkk
sumber
1
Saya banyak bekerja dengan metode numerik. Terkadang saya ingin bilangan bulat dan terkadang saya ingin floating point. Keduanya memiliki versi 64 bit yang optimal untuk kecepatan pemrosesan. Konversi antara ini adalah ide yang buruk karena ada kerugian setiap jalan. Sementara saya cenderung menggunakan ganda, kadang-kadang saya merasa lebih baik menggunakan bilangan bulat karena cara mereka digunakan di tempat lain. Tetapi akan sangat bagus ketika saya menulis sebuah algoritma untuk melakukannya sekali dan membiarkan keputusan jenis hingga persyaratan instance.
VoteCoffee
1

Saya akan menggunakan yang generik yang Anda dapat menangani eksternal ...

/// <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
sumber
1

Keterbatasan ini memengaruhi saya ketika saya mencoba membebani operator untuk tipe generik; karena tidak ada batasan "Inumerik", dan karena alasan lain orang-orang baik di stackoverflow senang untuk menyediakan, operasi tidak dapat didefinisikan pada tipe generik.

Saya menginginkan sesuatu seperti

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

Saya telah mengatasi masalah ini menggunakan .net4 dynamic runtime typing.

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

Dua hal tentang penggunaan dynamicadalah

  1. Performa. Semua tipe nilai dikotakkan.
  2. Kesalahan runtime. Anda "mengalahkan" kompiler, tetapi kehilangan keamanan jenis. Jika tipe generik tidak memiliki operator yang ditentukan, pengecualian akan dilemparkan selama eksekusi.
Pomeroy
sumber
1

Tipe .NET numeric primitive tidak berbagi antarmuka umum apa pun yang memungkinkan mereka digunakan untuk perhitungan. Dimungkinkan untuk mendefinisikan antarmuka Anda sendiri (misISignedWholeNumber ) yang akan melakukan operasi tersebut, menentukan struktur yang mengandung satu Int16, Int32, dll dan mengimplementasikan interface tersebut, dan kemudian memiliki metode yang menerima jenis generik dibatasi ISignedWholeNumber, tetapi harus mengkonversi nilai numerik untuk jenis struktur Anda mungkin akan menjadi gangguan.

Sebuah pendekatan alternatif akan mendefinisikan kelas statis Int64Converter<T>dengan properti statis bool Available {get;};dan delegasi statis untuk Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). Konstruktor kelas dapat menggunakan kode-keras untuk memuat delegasi untuk tipe yang diketahui, dan mungkin menggunakan Refleksi untuk menguji apakah tipe Tmengimplementasikan metode dengan nama dan tanda tangan yang sesuai (dalam kasus itu sesuatu seperti struct yang berisi Int64dan mewakili angka, tetapi memiliki ToString()metode kustom ). Pendekatan ini akan kehilangan keuntungan yang terkait dengan pengecekan tipe waktu kompilasi, tetapi akan tetap berhasil menghindari operasi tinju dan masing-masing jenis hanya perlu "diperiksa" sekali. Setelah itu, operasi yang terkait dengan tipe itu akan diganti dengan pengiriman delegasi.

supercat
sumber
@ KenKin: IConvertible menyediakan sarana yang mana bilangan bulat dapat ditambahkan ke jenis bilangan bulat lain untuk menghasilkan misalnya Int64hasil, tetapi tidak memberikan sarana yang misalnya bilangan bulat dari jenis sewenang-wenang dapat ditingkatkan untuk menghasilkan bilangan bulat lain dari jenis yang sama .
supercat
1

Saya memiliki situasi serupa di mana saya perlu menangani jenis dan string numerik; tampaknya sedikit campuran aneh tapi begitulah.

Sekali lagi, seperti banyak orang, saya melihat kendala dan menghasilkan banyak antarmuka yang harus didukung. Namun, a) bukan 100% kedap air dan b), siapa pun yang baru melihat daftar panjang kendala ini akan segera sangat bingung.

Jadi, pendekatan saya adalah menempatkan semua logika saya ke dalam metode generik tanpa kendala, tetapi menjadikan metode generik itu pribadi. Saya kemudian mengeksposnya dengan metode publik, yang secara eksplisit menangani tipe yang ingin saya tangani - menurut saya, kodenya bersih dan eksplisit, misalnya

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
sumber
0

Jika yang Anda inginkan adalah menggunakan satu tipe numerik , Anda dapat mempertimbangkan untuk membuat sesuatu yang mirip dengan alias di C ++ with using.

Jadi alih-alih memiliki yang sangat generik

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

kamu bisa saja

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

Itu mungkin memungkinkan Anda untuk dengan mudah beralih dari doubleke intatau orang lain jika diperlukan, tetapi Anda tidak akan dapat menggunakannya ComputeSomethingdengan doubledan intdalam program yang sama.

Tapi mengapa tidak mengganti semua doubleuntuk intkemudian? Karena metode Anda mungkin ingin menggunakan doubleapakah inputnya doubleatau int. Alias ​​memungkinkan Anda untuk mengetahui dengan tepat variabel mana yang menggunakan tipe dinamis .

pengguna276648
sumber
0

Topik sudah tua tetapi untuk pembaca masa depan:

Fitur ini sangat terkait dengan Discriminated Unionsyang tidak diimplementasikan dalam C # sejauh ini. Saya menemukan masalahnya di sini:

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

Masalah ini masih terbuka dan fitur sudah direncanakan C# 10

Jadi kita masih harus menunggu lebih lama, tetapi setelah melepaskan Anda bisa melakukannya dengan cara ini:

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

Saya pikir Anda salah paham obat generik. Jika operasi yang Anda coba lakukan hanya baik untuk tipe data tertentu maka Anda tidak melakukan sesuatu yang "generik".

Juga, karena Anda hanya ingin memungkinkan fungsi untuk bekerja pada tipe data int maka Anda seharusnya tidak memerlukan fungsi terpisah untuk setiap ukuran tertentu. Dengan hanya mengambil parameter dalam tipe spesifik terbesar akan memungkinkan program untuk secara otomatis mengirimkan tipe data yang lebih kecil ke dalamnya. (Yaitu dengan mengirimkan Int16 akan secara otomatis dikonversi ke Int64 saat memanggil).

Jika Anda melakukan operasi yang berbeda berdasarkan ukuran int aktual yang dilewatkan ke dalam fungsi maka saya akan berpikir Anda harus mempertimbangkan kembali dengan serius bahkan mencoba melakukan apa yang Anda lakukan. Jika Anda harus membodohi bahasa itu, Anda harus berpikir lebih banyak tentang apa yang ingin Anda capai daripada bagaimana melakukan apa yang Anda inginkan.

Gagal semua yang lain, parameter tipe objek dapat digunakan dan kemudian Anda harus memeriksa tipe parameter dan mengambil tindakan yang sesuai atau melempar pengecualian.

Tom Welch
sumber
10
Pertimbangkan Histogram kelas <T>. Masuk akal untuk mengambil parameter generik, sehingga kompiler dapat mengoptimalkannya untuk byte, int, dobel, desimal, BigInt, ... tetapi pada saat yang sama Anda perlu mencegah agar Anda dapat membuat, katakanlah, Histogram <Hashset >, karena - berbicara dengan Tron - itu tidak menghitung. (secara harfiah :))
sunside
15
Kaulah yang salah paham obat generik. Metaprogramming tidak hanya beroperasi pada nilai-nilai yang bisa menjadi tipe apa pun yang mungkin , itu untuk beroperasi pada tipe yang sesuai dengan berbagai kendala .
Jim Balter