คำตอบที่ได้รับการยอมรับจะอธิบายได้อย่างถูกต้องว่าควรประกาศรายการอย่างไรและขอแนะนำอย่างยิ่งสำหรับสถานการณ์ส่วนใหญ่
แต่ฉันเจอสถานการณ์ที่แตกต่างออกไปซึ่งครอบคลุมคำถามที่ถามด้วย จะเกิดอะไรขึ้นถ้าคุณต้องใช้รายการวัตถุที่มีอยู่เช่น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 ดังนั้นจึงสามารถกำหนดพารามิเตอร์ได้