Bagaimana cara mengakses properti tipe anonim di C #?

125

Aku punya ini:

List<object> nodes = new List<object>(); 

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});

... dan saya ingin tahu apakah saya dapat mengambil properti "Dicentang" dari objek anonim. Saya tidak yakin apakah ini mungkin. Mencoba melakukan ini:

if (nodes.Any(n => n["Checked"] == false)) ... tapi tidak berhasil.

Terima kasih

wgpubs
sumber

Jawaban:

63

Jika Anda menginginkan daftar jenis anonim yang diketik dengan kuat, Anda juga perlu membuat daftar jenis anonim. Cara termudah untuk melakukannya adalah dengan memproyeksikan urutan seperti array ke dalam daftar, mis

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

Kemudian Anda akan dapat mengaksesnya seperti:

nodes.Any(n => n.Checked);

Karena cara kerja kompilator, berikut ini seharusnya juga berfungsi setelah Anda membuat daftar, karena tipe anonim memiliki struktur yang sama sehingga mereka juga memiliki tipe yang sama. Saya tidak memiliki kompiler untuk memverifikasi ini.

nodes.Add(new { Checked = false, /* etc */ });
Greg Beech
sumber
263

Jika Anda menyimpan objek sebagai tipe object, Anda perlu menggunakan refleksi. Ini berlaku untuk semua jenis objek, anonim atau lainnya. Pada objek o, Anda bisa mendapatkan tipenya:

Type t = o.GetType();

Kemudian dari situ Anda mencari properti:

PropertyInfo p = t.GetProperty("Foo");

Kemudian dari situ Anda bisa mendapatkan nilai:

object v = p.GetValue(o, null);

Jawaban ini sudah lama menunggu pembaruan untuk C # 4:

dynamic d = o;
object v = d.Foo;

Dan sekarang alternatif lain di C # 6:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

Perhatikan bahwa dengan menggunakan ?.kita menyebabkan yang dihasilkan vmenjadi nulltiga situasi yang berbeda!

  1. oadalah null, jadi tidak ada objek sama sekali
  2. oadalah non- nulltetapi tidak memiliki propertiFoo
  3. omemiliki properti Footetapi nilai riilnya kebetulan null.

Jadi ini tidak sama dengan contoh sebelumnya, tetapi mungkin masuk akal jika Anda ingin memperlakukan ketiga kasus dengan sama.

Daniel Earwicker
sumber
4
Belum pernah menggunakan dinamika sebelumnya sampai sekarang, pembaruan yang bagus untuk .NET 4.0
Alan
dalam solusi c # 4 Anda akan mendapatkan pengecualian runtime jika properti tidak ada ( object v = d.Foo), sedangkan GetValue(o, null)akan menjadi null jika tidak ada.
YaakovHatam
1
Tidak, GetPropertyakan kembali null, dan GetValueakan melempar jika diteruskan null, jadi efek keseluruhan adalah pengecualian. Versi C # 4.0 memberikan pengecualian yang lebih deskriptif.
Daniel Earwicker
4
Jika Anda menggunakan dinamika dalam perakitan yang berbeda dari sumbernya maka Anda perlu menggunakan [InternalsVisibleTo]
Sarath
2
@DanielEarwicker terima kasih sudah selesai. Ini juga berlaku untuk tipe anonim. Karena semua properti yang dihasilkan untuk tipe anonim bersifat internal.
Sarath
13

Anda bisa mengulangi properti tipe anonim menggunakan Refleksi; lihat apakah ada properti "Dicentang" dan jika ada, dapatkan nilainya.

Lihat entri blog ini: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Jadi sesuatu seperti:

foreach(object o in nodes)
{
    Type t = o.GetType();

    PropertyInfo[] pi = t.GetProperties(); 

    foreach (PropertyInfo p in pi)
    {
        if (p.Name=="Checked" && !(bool)p.GetValue(o))
            Console.WriteLine("awesome!");
    }
}
glennkentwell.dll
sumber
6
Jika Anda hanya membutuhkan satu properti dan Anda sudah tahu namanya, tidak ada gunanya memeriksa semuanya; cukup gunakan GetProperty dan GetValue. Juga, System.out.println adalah Java, bukan C # ...
Chris Charabaruk
Ups, ini benar, Chris! Agak memalukan ... diperbaiki sekarang.
glennkentwell
6

Jawaban yang diterima dengan benar menjelaskan bagaimana daftar harus dideklarasikan dan sangat direkomendasikan untuk sebagian besar skenario.

Tetapi saya menemukan skenario yang berbeda, yang juga mencakup pertanyaan yang diajukan. Bagaimana jika Anda harus menggunakan daftar objek yang ada, seperti ViewData["htmlAttributes"]di MVC ? Bagaimana Anda dapat mengakses propertinya (biasanya dibuat melalui new { @style="width: 100px", ... })?

Untuk skenario yang sedikit berbeda ini, saya ingin berbagi dengan Anda apa yang saya temukan. Dalam solusi di bawah ini, saya mengasumsikan deklarasi berikut untuk nodes:

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

1. Solusi dengan dinamis

Di C # 4.0 dan versi yang lebih tinggi , Anda cukup mentransmisikan ke dinamis dan menulis:

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found not checked element!");

Catatan: Ini menggunakan pengikatan akhir, yang berarti ia akan mengenali hanya pada waktu proses jika objek tidak memiliki Checkedproperti dan RuntimeBinderExceptiondalam kasus ini melontarkan a - jadi jika Anda mencoba menggunakan properti yang tidak ada Checked2, Anda akan mendapatkan pesan berikut di runtime: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Solusi dengan refleksi

Solusi dengan refleksi bekerja baik dengan kompiler C # lama dan baru versi . Untuk versi C # lama harap perhatikan petunjuk di akhir jawaban ini.

Latar Belakang

Sebagai titik awal, saya menemukan jawaban yang bagus di sini . Idenya adalah untuk mengubah tipe data anonim menjadi kamus dengan menggunakan refleksi. Kamus memudahkan untuk mengakses properti, karena namanya disimpan sebagai kunci (Anda dapat mengaksesnya sepertimyDict["myProperty"] ).

Terinspirasi oleh kode pada tautan di atas, saya membuat kelas ekstensi yang disediakan GetProp, UnanonymizePropertiesdan UnanonymizeListItemssebagai metode ekstensi, yang menyederhanakan akses ke properti anonim. Dengan kelas ini Anda cukup melakukan kueri sebagai berikut:

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

atau Anda dapat menggunakan ekspresi nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()sebagaiif kondisi, yang memfilter secara implisit lalu memeriksa apakah ada elemen yang dikembalikan.

Untuk mendapatkan objek pertama yang berisi properti "Dicentang" dan mengembalikan properti "kedalaman", Anda dapat menggunakan:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

atau lebih pendek: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Catatan: Jika Anda memiliki daftar objek yang tidak selalu berisi semua properti (misalnya, beberapa tidak berisi properti "Dicentang"), dan Anda masih ingin membuat kueri berdasarkan nilai "Dicentang", Anda dapat melakukan hal ini:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

Ini mencegah, yang KeyNotFoundExceptionterjadi jika properti "Dicentang" tidak ada.


Kelas di bawah ini berisi metode ekstensi berikut:

  • UnanonymizeProperties: Digunakan untuk membatalkan anonimitas properti yang terdapat dalam suatu objek. Metode ini menggunakan refleksi. Ini mengubah objek menjadi kamus yang berisi properti dan nilainya.
  • UnanonymizeListItems: Digunakan untuk mengubah daftar objek menjadi daftar kamus yang berisi properti. Ini mungkin secara opsional berisi ekspresi lambda untuk difilter sebelumnya.
  • GetProp: Digunakan untuk mengembalikan satu nilai yang cocok dengan nama properti yang diberikan. Mengizinkan untuk memperlakukan properti yang tidak ada sebagai nilai null (benar) daripada sebagai KeyNotFoundException (salah)

Untuk contoh di atas, yang diperlukan hanyalah Anda menambahkan kelas ekstensi di bawah ini:

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object obj)
    {
        Type type = obj?.GetType();
        var properties = type?.GetProperties()
               ?.Select(n => n.Name)
               ?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj, null));
        return properties;
    }

    // converts object list into list of properties that meet the filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<IDictionary>();
        foreach (object obj in objectList)
        {
            var props = obj.UnanonymizeProperties();
            if (filterCriteria == default
               || filterCriteria((IDictionary<string, object>)props) == true)
            { accessibleList.Add(props); }
        }
        return accessibleList;
    }

    // returns specific property, i.e. obj.GetProp(propertyName)
    // requires prior usage of AccessListItems and selection of one element, because
    // object needs to be a IDictionary<string, object>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

Petunjuk: Kode di atas adalah dengan menggunakan null-bersyarat operator, tersedia sejak C # versi 6.0 - Jika Anda bekerja dengan lebih tua C # compiler (misalnya C # 3.0), cukup mengganti ?.dengan .dan ?[oleh [mana-mana, misalnya

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

Jika Anda tidak dipaksa untuk menggunakan kompiler C # yang lebih lama, pertahankan apa adanya, karena menggunakan null-conditionals membuat penanganan null menjadi lebih mudah.

Catatan: Seperti solusi lain dengan dinamis, solusi ini juga menggunakan pengikatan akhir, tetapi dalam kasus ini Anda tidak mendapatkan pengecualian - solusi ini tidak akan menemukan elemen jika Anda merujuk ke properti yang tidak ada, selama saat Anda mempertahankan operator bersyarat null .

Apa yang mungkin berguna untuk beberapa aplikasi adalah bahwa properti tersebut dirujuk melalui string dalam solusi 2, sehingga dapat dijadikan parameter.

Matt
sumber
1

Baru-baru ini, saya mengalami masalah yang sama dalam .NET 3.5 (tidak ada dinamika yang tersedia). Inilah cara saya memecahkan:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Diadaptasi dari suatu tempat di stackoverflow:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

Sekarang kembalikan objek melalui cast:

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

        ...
    }
    ...
}
orfruit
sumber