Keterbatasan tipe metode beberapa metode generik

135

Membaca ini , saya belajar bahwa memungkinkan metode untuk menerima parameter dari berbagai jenis dengan menjadikannya metode generik. Dalam contoh, kode berikut digunakan dengan batasan tipe untuk memastikan "U" adalah IEnumerable<T>.

public T DoSomething<U, T>(U arg) where U : IEnumerable<T>
{
    return arg.First();
}

Saya menemukan beberapa kode lagi yang memungkinkan menambahkan beberapa batasan tipe, seperti:

public void test<T>(string a, T arg) where T: ParentClass, ChildClass 
{
    //do something
}

Namun, kode ini muncul untuk menegakkan yang argharus berupa tipe ParentClass dan ChildClass . Yang ingin saya lakukan adalah mengatakan bahwa arg bisa berupa ParentClass atau ChildClass dengan cara berikut:

public void test<T>(string a, T arg) where T: string OR Exception
{
//do something
}

Bantuan Anda dihargai seperti biasa!

Mansfield
sumber
4
Apa yang bisa Anda lakukan dengan bermanfaat, secara umum di dalam tubuh metode semacam itu (kecuali semua tipe multipel berasal dari kelas dasar tertentu, dalam hal ini mengapa tidak menyatakannya sebagai batasan tipe)?
Damien_The_Unbeliever
@Damien_The_Unbeliever Tidak yakin apa yang Anda maksud dengan di dalam tubuh? Kecuali jika Anda mengizinkan jenis apa pun dan memeriksa secara manual di tubuh ... dan dalam kode aktual yang ingin saya tulis (cuplikan kode terakhir), saya ingin dapat meneruskan string ATAU pengecualian, sehingga tidak ada kelas hubungan di sana (kecuali system.object yang saya bayangkan).
Mansfield
1
Juga mencatat bahwa tidak ada gunanya dalam menulis where T : string, sebagai stringadalah disegel kelas. Satu-satunya hal berguna yang dapat Anda lakukan adalah mendefinisikan kelebihan stringdan T : Exception, seperti yang dijelaskan oleh @ Botz3000 dalam jawabannya di bawah ini.
Mattias Buelens
Tetapi ketika tidak ada hubungan, satu-satunya metode yang dapat Anda panggil argadalah yang ditentukan oleh object- jadi mengapa tidak hanya menghapus obat generik dari gambar dan membuat jenisnya arg object? Apa yang kamu dapat?
Damien_The_Unbeliever
1
@Mansfield Anda bisa membuat metode pribadi yang menerima parameter objek. Kedua kelebihan akan memanggil yang itu. Tidak diperlukan obat generik di sini.
Botz3000

Jawaban:

68

Itu tidak mungkin. Namun, Anda dapat menentukan kelebihan beban untuk jenis tertentu:

public void test(string a, string arg);
public void test(string a, Exception arg);

Jika itu adalah bagian dari kelas generik, mereka akan lebih disukai daripada versi generik dari metode ini.

Botz3000
sumber
1
Menarik. Ini terjadi pada saya, tapi saya pikir mungkin membuat kode lebih bersih untuk menggunakan satu fungsi. Ah, terima kasih banyak! Hanya karena penasaran, apakah Anda tahu jika ada alasan khusus ini tidak mungkin? (Apakah itu sengaja dikeluarkan dari bahasa itu?)
Mansfield
3
@Mansfield Saya tidak tahu alasan pastinya, tetapi saya pikir Anda tidak akan dapat menggunakan parameter generik dengan cara yang berarti lagi. Di dalam kelas, mereka harus diperlakukan seperti objek jika mereka diizinkan dari tipe yang sama sekali berbeda. Itu berarti Anda bisa mengabaikan parameter generik dan memberikan kelebihan.
Botz3000
3
@ Mansfield, itu karena orhubungan membuat hal-hal yang jauh dari umum menjadi berguna. Anda harus melakukan refleksi untuk mencari tahu apa yang harus dilakukan dan semua itu. (YUCK!).
Chris Pfohl
29

Jawaban botz 100% benar, berikut penjelasan singkatnya:

Saat Anda menulis sebuah metode (generik atau tidak) dan mendeklarasikan tipe-tipe parameter yang digunakan metode tersebut, Anda mendefinisikan sebuah kontrak:

Jika Anda memberi saya objek yang tahu bagaimana melakukan set hal-hal yang Tipe T tahu bagaimana melakukannya, saya bisa memberikan 'a': nilai kembali dari tipe yang saya nyatakan, atau 'b': semacam perilaku yang menggunakan tipe itu.

Jika Anda mencoba dan memberikannya lebih dari satu jenis pada satu waktu (dengan memiliki atau) atau mencoba membuatnya mengembalikan nilai yang mungkin lebih dari satu jenis yang membuat kontrak menjadi kabur:

Jika Anda memberi saya objek yang tahu cara melompat tali atau tahu cara menghitung pi ke digit ke-15, saya akan mengembalikan objek yang bisa memancing atau mungkin mencampur beton.

Masalahnya adalah bahwa ketika Anda masuk ke metode Anda tidak tahu apakah mereka telah memberi Anda IJumpRopeatau PiFactory. Selain itu, ketika Anda melanjutkan dan menggunakan metode ini (dengan asumsi bahwa Anda telah mengkompilasi secara ajaib), Anda tidak benar-benar yakin jika Anda memiliki Fisheratau AbstractConcreteMixer. Pada dasarnya itu membuat semuanya jauh lebih membingungkan.

Solusi untuk masalah Anda adalah salah satu dari dua kemungkinan:

  1. Tetapkan lebih dari satu metode yang menentukan setiap kemungkinan transformasi, perilaku, atau apa pun. Itu jawaban Botz. Dalam dunia pemrograman ini disebut sebagai Overloading the method.

  2. Tentukan kelas dasar atau antarmuka yang tahu bagaimana melakukan semua hal yang Anda butuhkan untuk metode ini dan minta satu metode mengambil tipe itu saja . Ini mungkin melibatkan membungkus a stringdan Exceptiondi kelas kecil untuk menentukan bagaimana Anda merencanakan pemetaan mereka untuk implementasi, tetapi kemudian semuanya sangat jelas dan mudah dibaca. Saya bisa datang, empat tahun dari sekarang dan membaca kode Anda dan dengan mudah memahami apa yang terjadi.

Yang Anda pilih tergantung pada seberapa rumit pilihan 1 dan 2 akan menjadi dan seberapa luas itu perlu.

Jadi untuk situasi spesifik Anda, saya akan membayangkan Anda hanya mengeluarkan pesan atau sesuatu dari pengecualian:

public interface IHasMessage
{
    string GetMessage();
}

public void test(string a, IHasMessage arg)
{
    //Use message
}

Sekarang yang Anda butuhkan adalah metode yang mengubah stringdan Exceptionmenjadi IHasMessage. Sangat mudah.

Chris Pfohl
sumber
maaf @ Botz3000, baru perhatikan saya salah mengeja nama Anda.
Chris Pfohl
Atau kompiler dapat mengancam tipe tersebut sebagai tipe gabungan dalam fungsi dan memiliki nilai balik sama dengan yang dimilikinya. TypeScript melakukan ini.
Alex
@Alex tapi bukan itu yang dilakukan C #.
Chris Pfohl
Itu benar, tetapi bisa. Saya membaca jawaban ini karena tidak bisa, apakah saya salah menafsirkan?
Alex
1
Masalahnya adalah bahwa batasan parameter generik C dan generik itu sendiri cukup primitif dibandingkan dengan, katakanlah, template C ++. C # mengharuskan Anda untuk memberi tahu kompilator terlebih dahulu operasi apa yang diizinkan pada tipe generik. Cara untuk memberikan info itu adalah dengan menambahkan batasan antarmuka alat (di mana T: IDisposable). Tetapi Anda mungkin tidak ingin tipe Anda mengimplementasikan beberapa antarmuka untuk menggunakan metode generik atau Anda mungkin ingin mengizinkan beberapa tipe dalam kode generik Anda yang tidak memiliki antarmuka umum. Ex. memungkinkan struct atau string apa pun sehingga Anda bisa memanggil Equals (v1, v2) untuk perbandingan berbasis nilai.
Vakhtang
8

Jika ChildClass berarti berasal dari ParentClass, Anda dapat menulis yang berikut ini untuk menerima ParentClass dan ChildClass;

public void test<T>(string a, T arg) where T: ParentClass 
{
    //do something
}

Di sisi lain, jika Anda ingin menggunakan dua jenis yang berbeda tanpa hubungan pewarisan di antara mereka, Anda harus mempertimbangkan jenis yang mengimplementasikan antarmuka yang sama;

public interface ICommonInterface
{
    string SomeCommonProperty { get; set; }
}

public class AA : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;set;
    }
}

public class BB : ICommonInterface
{
    public string SomeCommonProperty
    {
        get;
        set;
    }
}

maka Anda dapat menulis fungsi generik Anda sebagai;

public void Test<T>(string a, T arg) where T : ICommonInterface
{
    //do something
}
daryal
sumber
Ide bagus, kecuali saya tidak berpikir saya akan dapat melakukan ini karena saya ingin menggunakan string yang merupakan kelas tertutup sesuai komentar di atas ...
Mansfield
ini sebenarnya masalah desain. fungsi generik digunakan untuk melakukan operasi serupa dengan penggunaan kembali kode. baik jika Anda berencana untuk melakukan operasi yang berbeda dalam tubuh metode, memisahkan metode adalah cara yang lebih baik (IMHO).
daryal
Apa yang saya lakukan sebenarnya menulis fungsi logging kesalahan sederhana. Saya ingin parameter terakhir menjadi string info tentang kesalahan, atau pengecualian, dalam hal ini saya menyimpan e.message + e.stacktrace sebagai string.
Mansfield
Anda dapat menulis kelas baru, memiliki isSuccesful, menyimpan pesan dan pengecualian sebagai properti. maka Anda dapat memeriksa apakah benar benar dan melakukan sisanya.
daryal
1

Setua pertanyaan ini saya masih mendapatkan upvotes acak pada penjelasan saya di atas. Penjelasannya masih sangat bagus, tetapi saya akan menjawab untuk yang kedua kalinya dengan tipe yang sangat membantu saya sebagai pengganti untuk tipe-tipe serikat (jawaban yang sangat diketik untuk pertanyaan yang tidak secara langsung didukung oleh C # sebagaimana adanya ).

using System;
using System.Diagnostics;

namespace Union {
    [DebuggerDisplay("{currType}: {ToString()}")]
    public struct Either<TP, TA> {
        enum CurrType {
            Neither = 0,
            Primary,
            Alternate,
        }
        private readonly CurrType currType;
        private readonly TP primary;
        private readonly TA alternate;

        public bool IsNeither => currType == CurrType.Primary;
        public bool IsPrimary => currType == CurrType.Primary;
        public bool IsAlternate => currType == CurrType.Alternate;

        public static implicit operator Either<TP, TA>(TP val) => new Either<TP, TA>(val);

        public static implicit operator Either<TP, TA>(TA val) => new Either<TP, TA>(val);

        public static implicit operator TP(Either<TP, TA> @this) => @this.Primary;

        public static implicit operator TA(Either<TP, TA> @this) => @this.Alternate;

        public override string ToString() {
            string description = IsNeither ? "" :
                $": {(IsPrimary ? typeof(TP).Name : typeof(TA).Name)}";
            return $"{currType.ToString("")}{description}";
        }

        public Either(TP val) {
            currType = CurrType.Primary;
            primary = val;
            alternate = default(TA);
        }

        public Either(TA val) {
            currType = CurrType.Alternate;
            alternate = val;
            primary = default(TP);
        }

        public TP Primary {
            get {
                Validate(CurrType.Primary);
                return primary;
            }
        }

        public TA Alternate {
            get {
                Validate(CurrType.Alternate);
                return alternate;
            }
        }

        private void Validate(CurrType desiredType) {
            if (desiredType != currType) {
                throw new InvalidOperationException($"Attempting to get {desiredType} when {currType} is set");
            }
        }
    }
}

Kelas di atas mewakili tipe yang bisa berupa TP atau TA. Anda dapat menggunakannya seperti itu (tipe merujuk kembali ke jawaban asli saya):

// ...
public static Either<FishingBot, ConcreteMixer> DemoFunc(Either<JumpRope, PiCalculator> arg) {
  if (arg.IsPrimary) {
    return new FishingBot(arg.Primary);
  }
  return new ConcreteMixer(arg.Secondary);
}

// elsewhere:

var fishBotOrConcreteMixer = DemoFunc(new JumpRope());
var fishBotOrConcreteMixer = DemoFunc(new PiCalculator());

Catatan penting:

  • Anda akan mendapatkan kesalahan runtime jika Anda tidak memeriksa IsPrimary terlebih dahulu.
  • Anda dapat memeriksa salah satu IsNeither IsPrimaryatau IsAlternate.
  • Anda dapat mengakses nilai melalui PrimarydanAlternate
  • Ada konverter implisit antara TP / TA dan Either untuk memungkinkan Anda untuk melewati nilai-nilai atau Eitherdi mana saja di mana orang diharapkan. Jika Anda melakukan lulus Eitherdi mana TAatau TPdiharapkan, tetapi Eithermengandung salah jenis nilai Anda akan mendapatkan error runtime.

Saya biasanya menggunakan ini di mana saya ingin metode mengembalikan hasil atau kesalahan. Itu benar-benar membersihkan kode gaya itu. Saya juga sangat jarang ( jarang ) menggunakan ini sebagai pengganti metode kelebihan beban. Secara realistis ini adalah pengganti yang sangat buruk untuk kelebihan seperti itu.

Chris Pfohl
sumber