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.

Saeed Neamati
sumber
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.

Tim S.
sumber
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.

Jon Skeet
sumber
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>
JaredPar
sumber
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
FYI: digabungkan dari stackoverflow.com/questions/775387/…
Shog9
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)
    {

    }
}
Oded
sumber
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);
Marc Gravell
sumber
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
1
FYI: digabungkan dari stackoverflow.com/questions/775387/…
Shog9
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;
}
Stanislav Basovník
sumber
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)
   {

   }
}
Dinesh Kumar P.
sumber
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 !

Bermanfaat
sumber
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)
    {

    }
}
Alex
sumber
0

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

Mario Vernari
sumber