Apakah ada solusi untuk menggunakan metode statis oleh kelas generik?

Saya memiliki masalah yang agak sederhana, tetapi sepertinya tidak ada solusi dalam C#.

Saya memiliki sekitar 100 kelas Foo yang masing-masing mengimplementasikan metode static FromBytes(). Ada juga beberapa kelas generik yang akan menggunakan metode ini untuk FromBytes()-nya sendiri. TAPI kelas generik tidak dapat menggunakan metode static FromBytes(), karena T.FromBytes(...) ilegal.

Apakah saya melewatkan sesuatu atau tidak ada cara untuk mengimplementasikan fungsi ini?

public class Foo1
{
    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // build Foo1 instance
        return new Foo1()
        {
            Property1 = bytes[index++],
            Property2 = bytes[index++],
            // [...]
            Property10 = bytes[index++]
        };
    }

    public int Property1 { get; set; }
    public int Property2 { get; set; }
    // [...]
    public int Property10 { get; set; }
}

//public class Foo2 { ... }
// [...]
//public class Foo100 { ... }

// Generic class which needs the static method of T to work
public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(T.FromBytes(bytes, ref index)); // T.FromBytes(...) is illegal
        }

        return listOfFoo;
    }
}

Menurut saya tidak adil jika memilih jawaban sebagai jawaban yang diterima, lagipula jawaban dan komentar memberikan kontribusi yang berbeda-beda dengan pandangan mereka yang berbeda. Alangkah baiknya jika seseorang menulis gambaran yang bagus tentang berbagai pendekatan beserta kelebihan dan kekurangannya. Itu harus diterima setelah ini membantu pengembang masa depan dengan baik.


person Karsten Gutjahr    schedule 11.11.2013    source sumber
comment
Coba metode ekstensi.   -  person Hossain Muctadir    schedule 11.11.2013
comment
Hai @Muctadir, sayangnya saya memiliki masalah yang sama pada metode ekstensi generik seperti pada kelas generik.   -  person Karsten Gutjahr    schedule 11.11.2013
comment
Bagaimana Anda memanggil metode ListOfFoo<T>.FromBytes(...) statis? Anda tetap harus menentukan argumen tipe, bukan? Misalnya: ListOfFoo<Foo1>.FromBytes(...). Kemudian Anda dapat meneruskan delegasi sebagai parameter tambahan (seperti Servy diusulkan). Atau Anda menyebutnya melalui refleksi?   -  person gehho    schedule 11.11.2013


Jawaban (8)


Pilihan terbaik adalah dengan menerima fungsi FromBytes tertentu sebagai delegasi ke fungsi FromBytes generik Anda. Hal ini menghindari biaya kinerja dan kurangnya verifikasi waktu kompilasi yang menyertai penggunaan refleksi.

public delegate T FromBytesFunc<T>(byte[] bytes, ref int index);
public static List<T> FromBytes<T>(byte[] bytes, ref int index,
    FromBytesFunc<T> function)
{
    var count = bytes[index++];
    var listOfFoo = new List<T>();
    for (var i = 0; i < count; i++)
    {
        listOfFoo.Add(function(bytes, ref index));
    }

    return listOfFoo;
}

Perhatikan bahwa jika Anda membuat metodenya, bukan kelas yang ada di dalamnya, menjadi generik, Anda bisa membuat kompiler menyimpulkan argumen generiknya. Itu bisa disebut seperti ini:

var list = SomeClass.FromBytes(bytes, ref index, Foo1.FromBytes);
person Servy    schedule 11.11.2013

Masalahnya adalah Anda mencoba menggunakan FromBytes sebagai metode ekstensi padahal sebenarnya tidak. Dari apa yang saya kumpulkan, Anda mencoba memanggil FromBytes yang sesuai untuk <T> apa pun, sebagaimana didefinisikan di kelas apa pun yang telah Anda buat untuk T.

Yang perlu Anda lakukan adalah memanggil metode melalui refleksi.

Coba lihat beberapa rangkaian pesan ini untuk mendapatkan bantuan tentang cara mencapai hal ini.

Gunakan Refleksi untuk memanggil metode generik pada objek instance dengan tanda tangan: SomeObject.SomeGenericInstanceMethod‹T›(T argumen)

Bagaimana cara menggunakan refleksi untuk memanggil metode generik?

Memanggil metode generik menggunakan refleksi di .NET

Bagaimana cara memanggil metode generik dengan objek Tipe tertentu?

person Smeegs    schedule 11.11.2013
comment
Jika saya menggunakan refleksi untuk mengetahui tipenya, saya dapat memanggil ´Foo42.FromBytes()´. Tetapi saya tidak dapat menambahkan instance yang dihasilkan ke daftar: ´listOf.Add(Foo42.FromBytes(Bytes, ref index));´ memberi saya tipe Argumen 'Foo42' tidak dapat ditetapkan ke tipe Parameter T.. - person Karsten Gutjahr; 11.11.2013
comment
Hmmm, sudah coba casting hasilnya? (T)Foo42.FromBytes(Bytes, ref index)? - person Smeegs; 11.11.2013
comment
Hmm, maaf saya tidak bisa membantu lebih lanjut. Tapi saya tidak yakin. Mungkin coba posting ulang pertanyaan ini tetapi dengan refleksi pada judulnya. Anda mungkin menarik perhatian para ahli refleksi. - person Smeegs; 11.11.2013
comment
Coba transmisikan ke object lalu ke T: (T)((object)Foo42.FromBytes(Bytes, ref index)) - person gehho; 11.11.2013

Bisakah Anda mengimplementasikan metode FromBytes statis secara umum di kelas utilitas? Lalu lakukan hal seperti ini?

listOf.Add(Utility.FromBytes<T>(bytes, ref index));

Metode utilitas itu mungkin agak jelek jika setiap metode FromBytes sangat berbeda, tetapi tidak akan terlalu buruk, itu semua adalah kode yang dulu ada di setiap kelas.

Sesuatu seperti ini:

public static class Utility
{
    public static T FromBytes<T>(byte[] bytes, ref int index)
    {
          if (typeof(T) is Foo1)
          {
               return Foo1.GetBytes(bytes, ref index);
          }
          //etc....
    }
}

Anda juga dapat menyembunyikan kode refleksi di sini seperti yang ditunjukkan oleh jawaban lain. Sayangnya tampaknya tidak ada cara yang bersih untuk melakukan hal ini, namun menyembunyikan hal-hal yang berantakan dalam metode utilitas mungkin merupakan pilihan terbaik.

person Kevin DiTraglia    schedule 11.11.2013
comment
Terima kasih atas minat Anda, tetapi antarmuka tidak boleh memiliki metode statis. Kelas abstrak juga tidak bisa memiliki sesuatu yang statis. - person Karsten Gutjahr; 11.11.2013
comment
Saya pikir Anda harus mengandalkan refleksi. - person Wagner DosAnjos; 11.11.2013
comment
Menariknya saya sebenarnya tidak mengetahuinya. Saya kira refleksi adalah cara untuk melakukan ini. - person Kevin DiTraglia; 11.11.2013
comment
Ini hanya mengubah pertanyaan menjadi: Bagaimana cara mengimplementasikan Utility.FromBytes? - person svick; 11.11.2013
comment
@svick Saya membayangkan pernyataan kasus raksasa yang mencerminkan definisi masing-masing kelas saat ini (atau sekadar menyebut bahwa kelas sudah membuat definisi). - person Kevin DiTraglia; 11.11.2013
comment
Sayangnya saya tidak dapat mengembalikan Foo42 jika saya harus mengembalikan T. Casting tampaknya mustahil. - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr Hmm sepertinya ini lebih sulit dari yang seharusnya. Saya kira Anda bisa mengembalikan objek alih-alih T sebagai solusi sementara. Sepertinya sesuatu yang lebih elegan harus ada. - person Kevin DiTraglia; 11.11.2013
comment
@KarstenGutjahr Pemeran dimungkinkan. Perlu sedikit kerja keras. Mahal untuk tipe nilai. dan begini caranya.[return (T)(object)someType] - person Sriram Sakthivel; 11.11.2013

Anda dapat menggunakan refleksi untuk menemukan metode pada tipe tersebut dan kemudian membangun delegasi untuknya. Sesuatu seperti:

delegate T FromBytesFunc<T>(byte[] bytes, ref int index);

public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
{
    FromBytesFunc<T> fromBytes =
        (FromBytesFunc<T>)Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>), typeof(T).GetMethod("FromBytes")

    var count = bytes[index++];
    var listOf = new ListOfFoo<T>();
    for (var i = 0; i < count; i++)
    {
        listOf.Add(fromBytes(bytes, ref index));
    }

    return listOf;
}
person svick    schedule 11.11.2013
comment
Ini terlihat menarik, tetapi ´MethodInfo´ tidak memiliki Metode ´CreateDelegate()´. ;-) - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr Memang benar, tetapi hanya di .Net 4.5, saya tidak menyadarinya. - person svick; 11.11.2013

Anda dapat mencoba sesuatu seperti ini:

sealed class RetType
{
    public object Value
    {
        get;
        private set;
    }

    public int Index
    {
        get;
        private set;
    }

    public RetType(object value, int index)
    {
        Value = value;
        Index = index;
    }
}

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    static readonly Dictionary<Type, Func<byte[], int, RetType>> dic = new Dictionary<Type, Func<byte[], int, RetType>>
    {
        {
            typeof(Foo1),
            new Func<byte[], int, RetType>((bytes, index) =>
            {
                var value = Foo1.FromBytes(bytes, ref index);

                return new RetType(value, index);
            })
        }
        // add here others Foo
    };

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOf = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            var o = dic[typeof(T)](bytes, index);

            listOf.Add((T)o.Value);

            index = o.Index;
        }

        return listOf;
    }
}

Anda membuat pencarian untuk menemukan metode yang ingin Anda panggil untuk membangun instance.

person Alessandro D'Andria    schedule 11.11.2013
comment
Jadi maksud Anda kamus harus diisi secara manual untuk setiap Foo? Kedengarannya itu bukan solusi yang bagus. - person svick; 11.11.2013
comment
Jelas sekali, dan saya setuju bahwa ini bukanlah solusi yang baik tetapi ini adalah solusi. Pilihan lain yang jelas adalah refleksi seperti yang diusulkan di atas. - person Alessandro D'Andria; 11.11.2013
comment
Saya juga tertarik dengan solusi yang tidak terlalu bagus, tetapi untuk kompiler, ´T´ dari ´RetType‹T›´ tidak sama dengan ´T´ seperti pada ´ListOfFoo‹T›´, cukup gunakan nama yang sama. Oleh karena itu kompiler tidak dapat mentransmisikan yang satu ke yang lain... :-( - person Karsten Gutjahr; 11.11.2013
comment
@KarstenGutjahr oke saya mengerjakan ulang kodenya dan mengujinya, coba lihat. - person Alessandro D'Andria; 11.11.2013
comment
Bagus, sepertinya masuk akal. Saya hanya khawatir kode ini dapat diringkas menjadi (T)((object)Foo42.FromBytes(Bytes, ref index)) yang dikomentari di sini 20 menit setelah komentar Anda. - person Karsten Gutjahr; 11.11.2013
comment
Saya yakin pengisian Kamus dapat diotomatisasi: foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(t => t.Namespace == "Foos")) { Dict.Add(type, (FromBytesFunc<T>)Delegate.CreateDelegate(typeof(FromBytesFunc<T>), type.GetMethod("FromBytes"))); } dengan private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index); Namun Anda tidak memiliki pemeriksaan kompiler. - person Karsten Gutjahr; 12.11.2013

Tidak yakin apakah ini berhasil untuk Anda, tetapi ini adalah sebuah pilihan:

  1. Tentukan antarmuka IHaveFromBytesMethod<T> yang mendefinisikan metode instance FromBytes(...).
  2. Jadikan semua tipe Foo# mengimplementasikan antarmuka ini. (Jika Anda tidak ingin metode instance ini segera terlihat, Anda dapat mengimplementasikan antarmuka secara eksplisit.)
  3. Semua implementasi metode instan hanya meneruskan panggilan ke metode statis jenis tersebut.
  4. Batasi tipe parameter T di kelas ListOfFoo<T> Anda untuk mengimplementasikan antarmuka ini dan untuk menyediakan konstruktor tanpa parameter.
  5. Saat Anda memerlukan metode statis, buat objek dummy baru bertipe T menggunakan konstruktor tanpa parameter, lalu panggil metode instance pada instance dummy tersebut.

Antarmuka:

public interface IHaveFromBytesMethod<T>
{
    T FromBytes(byte[] bytes, ref int index);
}

Salah satu kelas Foo#:

public class Foo1 : IHaveFromBytesMethod<Foo1>
{
    public Foo1()
    {
        // ...
    }

    public static Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // ...
    }

    public Foo1 FromBytes(byte[] bytes, ref int index)
    {
        // within the instance method simply call the static method
        return Foo1.FromBytes(bytes, ref index);
    }
}

Kelas ListOfFoo<T> yang dimodifikasi:

// requires T to implement the interface and provide a parameterless ctor!
public class ListOfFoo<T> : System.Collections.Generic.List<T>
    where T : IHaveFromBytesMethod<T>, new()
{
    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        // create dummy instance for accessing static method via instance method
        T dummy = new T();
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            // instead of calling the static method,
            // call the instance method on the dummy instance
            listOfFoo.Add(dummy.FromBytes(bytes, ref index));
        }

        return listOfFoo;
    }
}
person gehho    schedule 11.11.2013
comment
Itu sangat jelek. Logikanya, tidak masuk akal untuk membuat sebuah instance dari Foo untuk membuat instance lainnya. - person svick; 11.11.2013
comment
Itu benar. Namun bukankah semua solusi di halaman ini jelek dalam beberapa hal? Saya hanya ingin mengusulkan alternatif yang berhasil tanpa refleksi. - person gehho; 11.11.2013

Saya ingin mengucapkan terima kasih kepada kalian semua, Anda memberi saya beberapa wawasan untuk dipikirkan. Saya telah mempertimbangkan setiap pendekatan dan memikirkan pro dan kontranya dan menulis kode berikut. Apa yang Anda pikirkan? Menurut saya ini adalah kompromi yang baik untuk kegunaan, keterbacaan, dan kinerja. Itu hanya tidak memiliki pemeriksaan kompiler.

public class ListOfFoo<T> : System.Collections.Generic.List<T>
{
    private static readonly FromBytesFunc<T> BytesFromFunc = 
        (FromBytesFunc<T>)System.Delegate.CreateDelegate(
            typeof(FromBytesFunc<T>),
            typeof(T).GetMethod("FromBytes"));

    private delegate T2 FromBytesFunc<out T2>(byte[] bytes, ref int index);

    public static ListOfFoo<T> FromBytes(byte[] bytes, ref int index)
    {
        var count = bytes[index++];
        var listOfFoo = new ListOfFoo<T>();
        for (var i = 0; i < count; i++)
        {
            listOfFoo.Add(BytesFromFunc(bytes, ref index));
        }

        return listOfFoo;
    }
}
person Karsten Gutjahr    schedule 12.11.2013

Ini akan menjadi ringkasan untuk semua pemirsa di masa depan.

Karena Anda tidak bisa memanggil metode tersebut begitu saja, masalah utamanya adalah mendapatkan delegasi. Ada dua pendekatan:

  • melalui refleksi
  • dari penelepon

Anda mungkin berpikir refleksinya lambat, namun kinerja tidak menjadi masalah. Jika delegasi disimpan dalam bidang statis, hal ini hanya perlu dilakukan satu kali per kelas, karena tipe generik tidak berbagi anggota statis.

Verifikasi waktu kompilasi adalah sebuah masalah. Jika Anda sangat peduli dengan verifikasi waktu kompilasi, Anda harus meneruskan delegasi dari pemanggil. Jika Anda lebih peduli dengan pemanggilan metode yang bersih, Anda harus mengorbankan kemampuan verifikasi waktu kompilasi.

PS: Beberapa orang menyarankan untuk memiliki kamus atau switch/case atau if/else tempat delegasi disimpan. Ini adalah sesuatu yang tidak boleh Anda lakukan. Ini tidak memiliki keuntungan dibandingkan menyimpan delegasi dalam bidang statis kelas generik (tipe generik tidak berbagi anggota statis).

person Karsten Gutjahr    schedule 28.03.2014