จะเข้าถึงทรัพย์สินประเภทนิรนามใน C # ได้อย่างไร?


125

ฉันมีสิ่งนี้:

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

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

... และฉันสงสัยว่าฉันสามารถคว้าคุณสมบัติ "ตรวจสอบแล้ว" ของวัตถุที่ไม่ระบุตัวตนได้หรือไม่ ฉันไม่แน่ใจว่าจะเป็นไปได้ไหม พยายามทำสิ่งนี้:

if (nodes.Any(n => n["Checked"] == false)) ... แต่มันไม่ได้ผล

ขอบคุณ

คำตอบ:


63

หากคุณต้องการรายการประเภทที่ไม่ระบุตัวตนที่พิมพ์อย่างชัดเจนคุณจะต้องทำให้รายการเป็นประเภทที่ไม่ระบุตัวตนด้วย วิธีที่ง่ายที่สุดคือการฉายลำดับเช่นอาร์เรย์ลงในรายการเช่น

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

จากนั้นคุณจะสามารถเข้าถึงได้เช่น:

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

เนื่องจากวิธีการทำงานของคอมไพลเลอร์สิ่งต่อไปนี้ควรใช้งานได้เมื่อคุณสร้างรายการเนื่องจากชนิดที่ไม่ระบุชื่อมีโครงสร้างเหมือนกันดังนั้นจึงเป็นประเภทเดียวกัน ฉันไม่มีคอมไพเลอร์ที่จะยืนยันสิ่งนี้

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

263

หากคุณจัดเก็บวัตถุเป็นประเภทobjectคุณต้องใช้การสะท้อน นี่เป็นความจริงสำหรับวัตถุทุกประเภทไม่ระบุตัวตนหรืออย่างอื่น ในวัตถุ o คุณจะได้รับประเภท:

Type t = o.GetType();

จากนั้นคุณค้นหาคุณสมบัติ:

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

จากนั้นคุณจะได้รับค่า:

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

คำตอบนี้ค้างชำระเป็นเวลานานสำหรับการอัปเดต C # 4:

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

และตอนนี้ทางเลือกอื่นใน C # 6:

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

โปรดทราบว่าการใช้?.เราทำให้ผลลัพธ์vอยู่nullในสามสถานการณ์ที่แตกต่างกัน!

  1. oเป็นnullจึงมีวัตถุที่ไม่ทั้งหมด
  2. oไม่ใช่ - nullแต่ไม่มีคุณสมบัติFoo
  3. oมีสถานที่ให้บริการแต่ค่าที่แท้จริงของมันที่จะเกิดขึ้นFoonull

ดังนั้นนี่จึงไม่เทียบเท่ากับตัวอย่างก่อนหน้านี้ แต่อาจสมเหตุสมผลหากคุณต้องการปฏิบัติต่อทั้งสามกรณีเหมือนกัน


4
ไม่เคยใช้ไดนามิกมาก่อนจนถึงขณะนี้การอัปเดตที่ดีสำหรับ. NET 4.0
Alan

ในโซลูชัน c # 4 คุณจะได้รับข้อยกเว้นรันไทม์หากไม่มีคุณสมบัติ ( object v = d.Foo) ในขณะที่GetValue(o, null)จะเป็นโมฆะหากไม่มีอยู่
YaakovHatam

1
ไม่GetPropertyจะกลับมาnullและGetValueจะโยนถ้าผ่านnullดังนั้นเอฟเฟกต์โดยรวมจึงเป็นข้อยกเว้น เวอร์ชัน C # 4.0 ให้ข้อยกเว้นที่อธิบายได้ชัดเจนกว่า
Daniel Earwicker

4
หากคุณใช้ไดนามิกในแอสเซมบลีที่แตกต่างจากแหล่งที่มาคุณต้องใช้ [InternalsVisibleTo]
Sarath

2
@DanielEarwicker ขอบคุณสำหรับความสำเร็จ นอกจากนี้ยังใช้กับประเภทที่ไม่ระบุตัวตน เนื่องจากคุณสมบัติทั้งหมดที่สร้างขึ้นสำหรับชนิดที่ไม่ระบุชื่อนั้นเป็นคุณสมบัติภายใน
Sarath

13

คุณสามารถทำซ้ำคุณสมบัติของชนิดที่ไม่ระบุตัวตนได้โดยใช้การสะท้อนกลับ ดูว่ามีคุณสมบัติ "ตรวจสอบ" หรือไม่และหากมีแล้วรับค่าของมัน

ดูบทความในบล็อกนี้: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

สิ่งที่ชอบ:

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!");
    }
}

6
หากคุณต้องการทรัพย์สินเพียงรายการเดียวและคุณรู้จักชื่อของมันอยู่แล้วก็ไม่มีเหตุผลที่จะต้องผ่านคุณสมบัติทั้งหมด เพียงใช้ GetProperty และ GetValue นอกจากนี้ System.out.println เป็น Java ไม่ใช่ C # ...
Chris Charabaruk

อ๊ะเป็นอย่างนั้นคริส! น่าอายนิดหน่อย ...
glennkentwell

6

คำตอบที่ได้รับการยอมรับจะอธิบายได้อย่างถูกต้องว่าควรประกาศรายการอย่างไรและขอแนะนำอย่างยิ่งสำหรับสถานการณ์ส่วนใหญ่

แต่ฉันเจอสถานการณ์ที่แตกต่างออกไปซึ่งครอบคลุมคำถามที่ถามด้วย จะเกิดอะไรขึ้นถ้าคุณต้องใช้รายการวัตถุที่มีอยู่เช่นViewData["htmlAttributes"]ในMVC ? คุณจะเข้าถึงคุณสมบัติของมันได้อย่างไร (โดยปกติจะสร้างผ่านnew { @style="width: 100px", ... })?

สำหรับสถานการณ์ที่แตกต่างกันเล็กน้อยนี้ฉันต้องการแบ่งปันสิ่งที่พบกับคุณ ในแนวทางแก้ไขด้านล่างฉันถือว่าการประกาศต่อไปนี้สำหรับnodes:

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

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

1. แก้ปัญหาด้วยไดนามิก

ในC # 4.0 และเวอร์ชันที่สูงกว่าคุณสามารถแคสต์เป็นไดนามิกและเขียน:

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

หมายเหตุ:กำลังใช้การโยงล่าช้าซึ่งหมายความว่าจะรับรู้เฉพาะที่รันไทม์หากวัตถุไม่มีCheckedคุณสมบัติและพ่น a RuntimeBinderExceptionในกรณีนี้ดังนั้นหากคุณพยายามใช้คุณสมบัติที่ไม่มีอยู่Checked2คุณจะได้รับข้อความต่อไปนี้ที่ รันไทม์: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. การแก้ปัญหาด้วยการสะท้อน

โซลูชันที่มีการสะท้อนกลับใช้ได้ทั้งกับคอมไพเลอร์ C #เวอร์ชันเก่าและเวอร์ชันใหม่ สำหรับ C # เวอร์ชันเก่าโปรดดูคำแนะนำในตอนท้ายของคำตอบนี้

พื้นหลัง

เป็นจุดเริ่มต้นที่ผมพบคำตอบที่ดีที่นี่ แนวคิดคือการแปลงชนิดข้อมูลที่ไม่ระบุชื่อลงในพจนานุกรมโดยใช้การสะท้อนกลับ พจนานุกรมช่วยให้เข้าถึงคุณสมบัติได้ง่ายเนื่องจากชื่อจะถูกจัดเก็บเป็นคีย์ (คุณสามารถเข้าถึงได้เช่นmyDict["myProperty"])

แรงบันดาลใจจากรหัสในการเชื่อมโยงดังกล่าวข้างต้นที่ผมสร้างระดับการขยายการให้บริการGetProp, UnanonymizePropertiesและUnanonymizeListItemsเป็นวิธีการขยายซึ่งเข้าถึงง่ายคุณสมบัติที่ไม่ระบุชื่อ ด้วยคลาสนี้คุณสามารถทำแบบสอบถามได้ดังนี้:

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

หรือคุณสามารถใช้นิพจน์nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()เป็นifเงื่อนไขซึ่งจะกรองโดยปริยายแล้วตรวจสอบว่ามีองค์ประกอบที่ส่งคืนหรือไม่

ในการรับวัตถุชิ้นแรกที่มีคุณสมบัติ "Checked" และส่งคืนคุณสมบัติ "ความลึก" คุณสามารถใช้:

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

หรือสั้นกว่า: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

หมายเหตุ:หากคุณมีรายการของวัตถุที่ไม่จำเป็นต้องมีคุณสมบัติทั้งหมด (เช่นบางรายการไม่มีคุณสมบัติ "ที่ถูกตรวจสอบ") และคุณยังต้องการสร้างแบบสอบถามตามค่า "ที่ถูกตรวจสอบ" คุณสามารถ ทำเช่นนี้:

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

สิ่งนี้ป้องกันไม่ให้KeyNotFoundExceptionเกิดขึ้นหากไม่มีคุณสมบัติ "ถูกตรวจสอบ"


ชั้นเรียนด้านล่างประกอบด้วยวิธีการขยายต่อไปนี้:

  • UnanonymizeProperties: ใช้เพื่อยกเลิกการเปิดเผยคุณสมบัติที่มีอยู่ในวัตถุ วิธีนี้ใช้การสะท้อน จะแปลงวัตถุเป็นพจนานุกรมที่มีคุณสมบัติและค่าของมัน
  • UnanonymizeListItems: ใช้เพื่อแปลงรายการวัตถุเป็นรายการพจนานุกรมที่มีคุณสมบัติ มันอาจมีนิพจน์แลมบ์ดาเพื่อกรองล่วงหน้า
  • GetProp: ใช้เพื่อส่งคืนค่าเดียวที่ตรงกับชื่อคุณสมบัติที่กำหนด อนุญาตให้ถือว่าคุณสมบัติที่ไม่มีอยู่เป็นค่าว่าง (จริง) แทนที่จะเป็น KeyNotFoundException (เท็จ)

สำหรับตัวอย่างข้างต้นสิ่งที่จำเป็นคือคุณเพิ่มคลาสส่วนขยายด้านล่าง:

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;
        }
    }
}

คำแนะนำ:รหัสข้างต้นคือการใช้null เงื่อนไขผู้ประกอบการที่มีอยู่ตั้งแต่ C # รุ่น 6.0 - ถ้าคุณทำงานอยู่กับที่มีอายุมากกว่า C คอมไพเลอร์ # (เช่น C # 3.0),เพียงแทนที่?.โดย.และ?[จาก[ทุกที่เช่น

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

หากคุณไม่ได้บังคับให้ใช้คอมไพเลอร์ C # รุ่นเก่าให้ใช้คอมไพเลอร์ตามที่เป็นอยู่เพราะการใช้ null-conditionals ทำให้การจัดการ null ง่ายขึ้นมาก

หมายเหตุ:เช่นเดียวกับโซลูชันอื่น ๆ ที่มีไดนามิกโซลูชันนี้ยังใช้การผูกล่าช้า แต่ในกรณีนี้คุณจะไม่ได้รับข้อยกเว้น - มันจะไม่พบองค์ประกอบหากคุณกำลังอ้างถึงคุณสมบัติที่ไม่มีอยู่ตราบเท่าที่ ในขณะที่คุณเก็บตัวดำเนินการเงื่อนไขว่างไว้

สิ่งที่อาจเป็นประโยชน์สำหรับบางแอปพลิเคชันคือคุณสมบัติถูกอ้างถึงผ่านสตริงในโซลูชัน 2 ดังนั้นจึงสามารถกำหนดพารามิเตอร์ได้


1

เมื่อเร็ว ๆ นี้ฉันมีปัญหาเดียวกันภายใน. NET 3.5 (ไม่มีไดนามิก) นี่คือวิธีที่ฉันแก้ไข:

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

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

ดัดแปลงจากที่ใดที่หนึ่งใน stackoverflow:

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

ตอนนี้รับคืนวัตถุผ่านการโยน:

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.