Bagaimana cara melewatkan tipe anonim sebagai parameter?


147

Bagaimana cara mengirimkan tipe anonim sebagai parameter ke fungsi lain? Pertimbangkan contoh ini:

var query = from employee in employees select new { Name = employee.Name, Id = employee.Id };
LogEmployees(query);

Variabel di querysini tidak memiliki tipe yang kuat. Bagaimana saya harus mendefinisikan LogEmployeesfungsi saya untuk menerimanya?

public void LogEmployees (? list)
{
    foreach (? item in list)
    {

    }
}

Dengan kata lain, apa yang harus saya gunakan sebagai pengganti ?tanda.


1
Pertanyaan duplikat berbeda yang lebih baik yang berhubungan dengan parameter yang lewat daripada mengembalikan data: stackoverflow.com/questions/16823658/…
Rob Church

Jawaban:


188

Saya pikir Anda harus membuat kelas untuk tipe anonim ini. Itu akan menjadi hal yang paling masuk akal untuk dilakukan menurut saya. Tetapi jika Anda benar-benar tidak menginginkannya, Anda dapat menggunakan dinamika:

public void LogEmployees (IEnumerable<dynamic> list)
{
    foreach (dynamic item in list)
    {
        string name = item.Name;
        int id = item.Id;
    }
}

Perhatikan bahwa ini tidak diketik dengan kuat, jadi jika, misalnya, Name berubah menjadi EmployeeName, Anda tidak akan tahu ada masalah hingga runtime.


Saya memeriksa ini sebagai jawaban yang benar, karena dynamicpenggunaan. Saya benar-benar berguna bagi saya. Terima kasih :)
Saeed Neamati

1
Saya setuju bahwa begitu data mulai diedarkan, cara yang lebih terstruktur mungkin / seharusnya lebih disukai agar tidak menimbulkan bug yang sulit ditemukan (Anda menghindari sistem tipe). Namun, jika Anda ingin menemukan kompromi, cara lain adalah dengan menggunakan Dictionary generik. Penginisialisasi kamus C # cukup nyaman digunakan saat ini.
Jonas

Ada beberapa kasus di mana Anda menginginkan implementasi generik, dan meneruskan tipe keras berarti kemungkinan beralih atau implementasi pabrik yang mulai membengkak kode. Jika Anda memiliki situasi yang benar-benar dinamis dan tidak keberatan sedikit refleksi untuk menangani data yang Anda terima, maka ini sempurna. Terima kasih atas jawabannya @Tim S.
Larry Smith

43

Anda bisa melakukannya seperti ini:

public void LogEmployees<T>(List<T> list) // Or IEnumerable<T> list
{
    foreach (T item in list)
    {

    }
}

... tetapi Anda tidak akan bisa berbuat banyak dengan setiap item. Anda dapat memanggil ToString, tetapi Anda tidak akan dapat menggunakan (katakanlah) Namedan Idsecara langsung.


2
Kecuali Anda dapat menggunakan where T : some typedi akhir baris pertama untuk mempersempit jenisnya. Namun, pada titik itu, mengharapkan jenis antarmuka umum tertentu akan lebih masuk akal untuk mengharapkan antarmuka. :)
CassOnMars

9
@d_r_w: Anda tidak dapat menggunakan where T : some typedengan jenis anonim, karena mereka tidak menerapkan jenis antarmuka apa pun ...
Jon Skeet

@dlev: Anda tidak dapat melakukannya, foreach mengharuskan variabel yang diiterasi pada implementasi GetEnumerator, dan jenis anonim tidak menjaminnya.
CassOnMars

1
@ Jon Skeet: poin bagus, otak saya kurang kuat pagi ini.
CassOnMars

1
@Tokopedia Saya kira Anda dapat menggunakan refleksi untuk tetap mengakses / mengatur properti jika T adalah tipe anonim, bukan? Saya memikirkan kasus di mana seseorang menulis pernyataan "Pilih * dari" dan menggunakan kelas anonim (atau ditentukan) untuk menentukan kolom mana dari peta hasil kueri ke properti bernama yang sama pada objek anonim Anda.
C. Tewalt

19

Sayangnya, apa yang Anda coba lakukan tidak mungkin. Di balik terpal, variabel kueri diketik menjadi IEnumerabletipe anonim. Nama tipe anonim tidak dapat direpresentasikan dalam kode pengguna oleh karena itu tidak ada cara untuk menjadikannya parameter input ke suatu fungsi.

Taruhan terbaik Anda adalah membuat jenis dan menggunakannya sebagai hasil dari kueri dan kemudian meneruskannya ke dalam fungsi. Sebagai contoh,

struct Data {
  public string ColumnName; 
}

var query = (from name in some.Table
            select new Data { ColumnName = name });
MethodOp(query);
...
MethodOp(IEnumerable<Data> enumerable);

Namun dalam kasus ini, Anda hanya memilih satu bidang, jadi mungkin lebih mudah untuk langsung memilih bidang tersebut. Ini akan menyebabkan kueri diketik sebagai IEnumerablejenis bidang. Dalam hal ini, nama kolom.

var query = (from name in some.Table select name);  // IEnumerable<string>

Contoh saya adalah satu, tetapi seringkali lebih. Jawaban Anda melalui karya (dan cukup jelas sekarang). Saya hanya perlu istirahat untuk makan siang untuk memikirkannya ;-)
Tony Trembath-Drake


Peringatan adalah bahwa ketika Anda membuat kelas yang tepat, Equalsperilaku perubahan. Yaitu Anda harus menerapkannya. (Saya tahu tentang perbedaan ini tetapi masih berhasil melupakannya selama refactoring.)
LosManos

12

Anda tidak dapat meneruskan tipe anonim ke fungsi non generik, kecuali tipe parameternya adalah object.

public void LogEmployees (object obj)
{
    var list = obj as IEnumerable(); 
    if (list == null)
       return;

    foreach (var item in list)
    {

    }
}

Jenis anonim dimaksudkan untuk penggunaan jangka pendek dalam suatu metode.

Dari MSDN - Jenis Anonim :

Anda tidak dapat mendeklarasikan bidang, properti, peristiwa, atau tipe kembalian dari suatu metode sebagai memiliki tipe anonim. Demikian pula, Anda tidak dapat mendeklarasikan parameter formal dari sebuah metode, properti, konstruktor, atau pengindeks memiliki tipe anonim. Untuk meneruskan tipe anonim, atau kumpulan yang berisi tipe anonim, sebagai argumen untuk metode, Anda bisa mendeklarasikan parameter sebagai objek tipe . Namun, melakukan ini menggagalkan tujuan mengetik yang kuat.

(penekanan saya)


Memperbarui

Anda dapat menggunakan obat generik untuk mencapai apa yang Anda inginkan:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

4
Jika Anda tidak bisa meneruskan tipe anonim (atau kumpulan tipe anonim) ke metode, seluruh LINQ akan gagal. Anda bisa, hanya saja metode tersebut harus sepenuhnya generik, tidak menggunakan properti tipe anonim.
Jon Skeet

2
re object- or dynamic; p
Marc Gravell

Jika casting dengan "as" Anda harus memeriksa apakah daftar adalah null
Alex

"bisa"! = "harus". Menggunakan objecttidak sama dengan membuat metode generik dalam tipe anonim, sesuai jawaban saya.
Jon Skeet

8

Biasanya, Anda melakukan ini dengan obat generik, misalnya:

MapEntToObj<T>(IQueryable<T> query) {...}

Kompilator kemudian harus menyimpulkan Tkapan Anda memanggil MapEntToObj(query). Tidak begitu yakin apa yang ingin Anda lakukan di dalam metode ini, jadi saya tidak tahu apakah ini berguna ... masalahnya adalah di dalam MapEntToObjAnda masih tidak dapat memberi nama T- Anda juga dapat:

  • panggil metode umum lainnya dengan T
  • gunakan refleksi Tuntuk melakukan sesuatu

tetapi selain itu, cukup sulit untuk memanipulasi tipe anonim - paling tidak karena mereka tidak dapat diubah ;-p

Trik lain (saat mengekstrak data) adalah juga meneruskan selektor - yaitu sesuatu seperti:

Foo<TSource, TValue>(IEnumerable<TSource> source,
        Func<TSource,string> name) {
    foreach(TSource item in source) Console.WriteLine(name(item));
}
...
Foo(query, x=>x.Title);

1
Mempelajari sesuatu yang baru, tidak tahu bahwa tipe anonim tidak dapat diubah! ;)
Annie Lagang

1
@AnneLagang yang benar-benar bergantung pada kompiler, karena ia menghasilkannya. Di VB.NET, tipe anon bisa berubah.
Marc Gravell


7

Anda dapat menggunakan obat generik dengan trik berikut (mentransmisikan ke tipe anonim):

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {
        var typedItem = Cast(item, new { Name = "", Id = 0 });
        // now you can use typedItem.Name, etc.
    }
}

static T Cast<T>(object obj, T type)
{
    return (T)obj;
}

6

"dinamis" juga dapat digunakan untuk tujuan ini.

var anonymousType = new { Id = 1, Name = "A" };

var anonymousTypes = new[] { new { Id = 1, Name = "A" }, new { Id = 2, Name = "B" };

private void DisplayAnonymousType(dynamic anonymousType)
{
}

private void DisplayAnonymousTypes(IEnumerable<dynamic> anonymousTypes)
{
   foreach (var info in anonymousTypes)
   {

   }
}

1
Ini adalah jawaban yang benar! Itu hanya membutuhkan lebih banyak cinta :)
Korayem

2

Alih-alih meneruskan tipe anonim, teruskan Daftar tipe dinamis:

  1. var dynamicResult = anonymousQueryResult.ToList<dynamic>();
  2. Tanda tangan metode: DoSomething(List<dynamic> _dynamicResult)
  3. Metode panggilan: DoSomething(dynamicResult);
  4. selesai.

Terima kasih kepada Petar Ivanov !


0

Jika Anda tahu, bahwa hasil Anda mengimplementasikan antarmuka tertentu, Anda dapat menggunakan antarmuka sebagai tipe data:

public void LogEmployees<T>(IEnumerable<T> list)
{
    foreach (T item in list)
    {

    }
}

0

Saya akan gunakan IEnumerable<object>sebagai tipe untuk argumen. Namun bukan keuntungan besar untuk pemeran eksplisit yang tidak dapat dihindari. Bersulang

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.