วิธีการตรวจสอบว่าทรัพย์สินมีอยู่ใน ExpandoObject?


188

ในจาวาสคริปต์คุณสามารถตรวจพบว่ามีการกำหนดคุณสมบัติโดยใช้คำสำคัญที่ไม่ได้กำหนด:

if( typeof data.myProperty == "undefined" ) ...

คุณจะทำเช่นนี้ใน C # โดยใช้คำหลักแบบไดนามิกที่มีExpandoObjectและไม่มีข้อยกเว้นได้อย่างไร


5
@CodeInChaos: โปรดทราบว่ารหัสที่แสดงไม่ได้ตรวจสอบค่าของdata.myProperty; มันตรวจสอบสิ่งที่typeof data.myPropertyส่งคืน มันเป็นที่ถูกต้องที่data.myPropertyอาจมีอยู่และจะกำหนดให้undefinedแต่ในกรณีที่จะกลับสิ่งอื่นที่ไม่ใช่typeof "undefined"ดังนั้นรหัสนี้ใช้งานได้
Aasmund Eldhuset

คำตอบ:


181

ตามMSDNการประกาศแสดงให้เห็นว่ามีการใช้ IDictionary:

public sealed class ExpandoObject : IDynamicMetaObjectProvider, 
    IDictionary<string, Object>, ICollection<KeyValuePair<string, Object>>, 
    IEnumerable<KeyValuePair<string, Object>>, IEnumerable, INotifyPropertyChanged

คุณสามารถใช้สิ่งนี้เพื่อดูว่ามีการกำหนดสมาชิก:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}

3
เพื่อให้การตรวจสอบนี้ง่ายขึ้นฉันได้ลองใช้ TryGetValue มากเกินไปและทำให้มันกลับมาเป็นจริงเสมอโดยตั้งค่าส่งคืนเป็น "ไม่ได้กำหนด" หากคุณสมบัติไม่ได้ถูกกำหนด ถ้า (someObject.someParam = "ไม่ได้กำหนด"!) ... และมันทำงาน :)
Softlion

อาจเป็นไปได้ :) แต่ฉันคิดว่าคุณหมายถึง "แทนที่" แทนที่จะโอเวอร์โหลด
Dykam

อ๋อ ฉันได้เปลี่ยน "ไม่ได้กำหนด" เป็นค่า const ของวัตถุพิเศษที่ฉันสร้างขึ้นที่อื่น มันช่วยป้องกันปัญหาการหล่อ: p
Softlion

1
ฉันเชื่อว่าวิธีนี้ยังคงเป็นปัจจุบันอยู่ อย่าใช้คำพูดของใครก็ได้ในเรื่องของการไตร่ตรองลองทดสอบด้วยตัวคุณเองและดูว่าคุณสามารถซื้อได้หรือไม่
nik.shornikov

1
@ BlueRaja-DannyPflughoeft ใช่มันเป็น มันจะเป็นการเรียกแบบไดนามิกด้วยกรณีที่คุณได้รับภายใน โดยเฉพาะอย่างยิ่งจะมีการใช้งานอย่างชัดเจน: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/
......

28

ความแตกต่างที่สำคัญจะต้องทำที่นี่

คำตอบส่วนใหญ่ที่นี่เฉพาะสำหรับ ExpandoObject ที่กล่าวถึงในคำถาม แต่การใช้งานทั่วไป (และเหตุผลในการถามคำถามนี้เมื่อทำการค้นหา) คือเมื่อใช้ ASP.Net MVC ViewBag นั่นเป็นการนำไปใช้งาน / คลาสย่อยแบบกำหนดเองของ DynamicObject ซึ่งจะไม่แสดงข้อยกเว้นเมื่อคุณตรวจสอบชื่อคุณสมบัติใด ๆ ที่เป็นค่าว่าง สมมติว่าคุณอาจประกาศคุณสมบัติเช่น:

@{
    ViewBag.EnableThinger = true;
}

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

if (ViewBag.EnableThinger != null && ViewBag.EnableThinger)
{
    // Do some stuff when EnableThinger is true
}

ตอนนี้กำจัดการประกาศของ EnableThinger รหัสเดียวกันรวบรวมและทำงานอย่างถูกต้อง ไม่จำเป็นต้องไตร่ตรอง

ซึ่งแตกต่างจาก ViewBag นั้น ExpandoObject จะส่งผลต่อหากคุณตรวจสอบค่าว่างของคุณสมบัติที่ไม่มีอยู่ ในการที่จะทำให้ฟังก์ชั่นของ MVC ViewBag ออกมาจากdynamicวัตถุของคุณได้อย่างราบรื่นคุณจะต้องใช้การนำไปใช้ของไดนามิกที่ไม่ได้เกิดขึ้น

คุณสามารถใช้การดำเนินการที่แน่นอนใน MVC ViewBag:

. . .
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    result = ViewData[binder.Name];
    // since ViewDataDictionary always returns a result even if the key does not exist, always return true
    return true;
}
. . .

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/DynamicViewDataDictionary.cs

คุณสามารถดูว่ามีการเชื่อมโยงกับมุมมอง MVC ที่นี่ใน MVC ViewPage:

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/ViewPage.cs

กุญแจสำคัญในการทำงานที่สง่างามของ DynamicViewDataDictionary คือการใช้พจนานุกรมใน ViewDataDictionary ที่นี่:

public object this[string key]
{
    get
    {
        object value;
        _innerDictionary.TryGetValue(key, out value);
        return value;
    }
    set { _innerDictionary[key] = value; }
}

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/ViewDataDictionary.cs

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

มันยาวเกินไปที่จะโพสต์ความกล้าทั้งหมดที่นี่ - ส่วนใหญ่เป็นเพียงการใช้ IDictionary - แต่นี่เป็นวัตถุแบบไดนามิก (คลาสDDict) ที่ไม่ส่งผลต่อการตรวจสอบคุณสมบัติที่ไม่ได้ประกาศบน Github:

https://github.com/b9chris/GracefulDynamicDictionary

ถ้าคุณเพียงต้องการที่จะเพิ่มให้กับโครงการของคุณผ่านทาง NuGet ชื่อของมันคือGracefulDynamicDictionary


ทำไมคุณถึงลงคะแนนใน DynamicDictionary เพราะมันไม่ได้ใช้การสะท้อนกลับ?
Softlion

จากนั้นคุณสามารถลงคะแนนมันขึ้นมาเช่นนี้เป็นวิธีการแก้ปัญหาเดียวกัน :)
Softlion

3
แน่นอนที่สุดมันไม่ใช่ทางออกเดียวกัน
Chris Moschini

"คุณจะต้องสร้างคลาสย่อยที่คล้ายกันซึ่งไม่ได้โยนเมื่อไม่พบพร็อพเพอร์ตี้" => มันคือ! ไม่หรอก ทางออกของฉันดีกว่า มันพ่น - เพราะเราต้องการและมันก็ไม่สามารถโยนได้ถ้าใช้ TryXX;
Softlion

1
นี่คือสาเหตุที่ฉันมาที่นี่ฉันไม่สามารถเข้าใจได้ว่าทำไมรหัส (viewbag) ถึงไม่เสียหาย ขอบคุณ.
Adam Tolley

11

ฉันตอบคำถามที่คล้ายกันเมื่อเร็ว ๆ นี้: ฉันจะสะท้อนถึงสมาชิกของวัตถุแบบไดนามิกได้อย่างไร

ในไม่ช้า ExpandoObject ไม่ได้เป็นเพียงวัตถุไดนามิกที่คุณอาจได้รับ การสะท้อนจะทำงานกับประเภทคงที่ (ชนิดที่ไม่ได้ใช้ IDynamicMetaObjectProvider) สำหรับประเภทที่ใช้อินเตอร์เฟสนี้การสะท้อนกลับนั้นไร้ประโยชน์ สำหรับ ExpandoObject คุณสามารถตรวจสอบได้ว่าคุณสมบัติถูกกำหนดเป็นคีย์ในพจนานุกรมพื้นฐานหรือไม่ สำหรับการใช้งานอื่น ๆ มันอาจจะท้าทายและบางครั้งวิธีเดียวที่จะทำงานกับข้อยกเว้น สำหรับรายละเอียดโปรดไปที่ลิงก์ด้านบน


11

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

ลองดูมันใช้ได้ดีสำหรับฉัน:

class Program
{
    static void Main(string[] args)
    {
        dynamic userDynamic = new JsonUser();

        Console.WriteLine(IsPropertyExist(() => userDynamic.first_name));
        Console.WriteLine(IsPropertyExist(() => userDynamic.address));
        Console.WriteLine(IsPropertyExist(() => userDynamic.last_name));
    }

    class JsonUser
    {
        public string first_name { get; set; }
        public string address
        {
            get
            {
                throw new InvalidOperationException("Cannot read property value");
            }
        }
    }

    static bool IsPropertyExist(GetValueDelegate getValueMethod)
    {
        try
        {
            //we're not interesting in the return value. What we need to know is whether an exception occurred or not
            getValueMethod();
            return true;
        }
        catch (RuntimeBinderException)
        {
            // RuntimeBinderException occurred during accessing the property
            // and it means there is no such property         
            return false;
        }
        catch
        {
            //property exists, but an exception occurred during getting of a value
            return true;
        }
    }

    delegate string GetValueDelegate();
}

ผลลัพธ์ของรหัสต่อไปนี้:

True
True
False

2
@marklam การตรวจสอบข้อยกเว้นทั้งหมดไม่ดีเมื่อคุณไม่รู้ว่าอะไรเป็นสาเหตุของข้อยกเว้น ในกรณีของเรามันก็โอเคเพราะเราคาดว่าอาจจะไม่มีสนาม
Alexander G

3
ถ้าคุณรู้ว่าอะไรเป็นสาเหตุของข้อยกเว้นคุณจะต้องรู้ชนิดของมันด้วยดังนั้นจับ (AnythingException) มิฉะนั้นโค้ดของคุณจะยังคงดำเนินการต่อไปแม้ว่าคุณจะได้รับข้อยกเว้นที่ไม่คาดคิด - เช่น OutOfMemoryException
marklam

4
คุณสามารถส่งผ่านในทะเยอทะยานใด ๆ IsPropertyExistที่จะ InvalidOperationExceptionในตัวอย่างนี้คุณรู้ว่าใครสามารถโยน ในทางปฏิบัติคุณไม่ทราบว่าอาจมีข้อยกเว้นเกิดขึ้น +1 เพื่อต่อต้านลัทธิคาร์โก้
piedar

2
โซลูชันนี้ไม่สามารถยอมรับได้หากประสิทธิภาพมีความสำคัญตัวอย่างเช่นหากใช้ในลูปที่มีการวนซ้ำมากกว่า 500+ การรวมจะเพิ่มขึ้นและอาจทำให้เกิดความล่าช้าหลายวินาที ทุกครั้งที่มีการยกเว้นเกิดขึ้นสแต็กต้องถูกคัดลอกไปยังวัตถุยกเว้น
Jim109

1
Re: ประสิทธิภาพการทำงาน: ดีบักเกอร์ที่แนบมาและ Console.WriteLine เป็นบิตช้า 10,000 รอบที่นี่ใช้เวลาน้อยกว่า 200 มิลลิวินาที (มี 2 ข้อยกเว้นต่อการทำซ้ำ) การทดสอบเดียวกันโดยไม่มีข้อยกเว้นใช้เวลาไม่กี่มิลลิวินาที ซึ่งหมายความว่าหากคุณคาดหวังว่าการใช้รหัสนี้จะหายไปเพียงน้อยครั้งหรือไม่ได้รับการ จำกัด จำนวนครั้งหรือสามารถแคชผลลัพธ์ได้โปรดทราบว่าทุกอย่างมีที่และไม่มีสิ่งใดมากเกินไป - เตือนคำเตือนที่นี่
ความโกลาหล

10

ฉันต้องการสร้างวิธีการขยายดังนั้นฉันสามารถทำสิ่งที่ชอบ:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

if (myDynamicObject.HasProperty("propertyName"))
{
    //...
}

... แต่คุณไม่สามารถสร้างส่วนขยายExpandoObjectตามเอกสาร C # 5 (ข้อมูลเพิ่มเติมที่นี่ )

ดังนั้นฉันสิ้นสุดการสร้างผู้ช่วยในชั้นเรียน:

public static class ExpandoObjectHelper
{
    public static bool HasProperty(ExpandoObject obj, string propertyName)
    {
        return ((IDictionary<String, object>)obj).ContainsKey(propertyName);
    }
}

วิธีใช้:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}

4
โหวตให้ความเห็นที่เป็นประโยชน์และลิงก์ในส่วนขยายสำหรับ ExpandoObject
Roberto

1

ทำไมคุณไม่ต้องการใช้ Reflection เพื่อรับชุดคุณสมบัติประเภท? แบบนี้

 dynamic v = new Foo();
 Type t = v.GetType();
 System.Reflection.PropertyInfo[] pInfo =  t.GetProperties();
 if (Array.Find<System.Reflection.PropertyInfo>(pInfo, p => { return p.Name == "PropName"; }).    GetValue(v,  null) != null))
 {
     //PropName initialized
 } 

ฉันไม่แน่ใจว่าจะส่งคืนคุณสมบัติที่เพิ่มแบบไดนามิกหรือไม่ฉันเดาว่าจะส่งคืนวิธีการของวัตถุแบบไดนามิก
Dykam

1

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

    public static object Value(this ExpandoObject expando, string name)
    {
        var expandoDic = (IDictionary<string, object>)expando;
        return expandoDic.ContainsKey(name) ? expandoDic[name] : null;
    }

หากสามารถใช้งานได้เช่น:

  // lookup is type 'ExpandoObject'
  object value = lookup.Value("MyProperty");

หรือถ้าตัวแปรท้องถิ่นของคุณเป็น 'ไดนามิก' คุณจะต้องส่งมันไปยัง ExpandoObject ก่อน

  // lookup is type 'dynamic'
  object value = ((ExpandoObject)lookup).Value("PropertyBeingTested");

1

ทั้งนี้ขึ้นอยู่กับกรณีการใช้งานของคุณหากถือได้ว่าเป็นโมฆะเหมือนกันไม่ได้กำหนดคุณสามารถเปลี่ยน ExpandoObject ของคุณให้เป็น DynamicJsonObject

    dynamic x = new System.Web.Helpers.DynamicJsonObject(new ExpandoObject());
    x.a = 1;
    x.b = 2.50;
    Console.WriteLine("a is " + (x.a ?? "undefined"));
    Console.WriteLine("b is " + (x.b ?? "undefined"));
    Console.WriteLine("c is " + (x.c ?? "undefined"));

เอาท์พุท:

a is 1
b is 2.5
c is undefined


-3

สวัสดีพวกเขาหยุดใช้ Reflection กับทุกสิ่งที่มีค่าใช้จ่ายมากรอบ CPU

นี่คือทางออก:

public class DynamicDictionary : DynamicObject
{
    Dictionary<string, object> dictionary = new Dictionary<string, object>();

    public int Count
    {
        get
        {
            return dictionary.Count;
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string name = binder.Name;

        if (!dictionary.TryGetValue(binder.Name, out result))
            result = "undefined";

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dictionary[binder.Name] = value;
        return true;
    }
}

4
สิ่งนี้แสดงให้เห็นถึงวิธีการใช้งานวัตถุแบบไดนามิกไม่ใช่วิธีการดูว่าคุณสมบัติมีอยู่บนวัตถุแบบไดนามิก
Matt Warren

คุณสามารถตรวจสอบว่าอินสแตนซ์แบบไดนามิกมีคุณสมบัติโดยทำการตรวจสอบ null กับคุณสมบัติที่เป็นปัญหา
ctorx

2
"นี่แสดงให้เห็นถึงวิธีการใช้วัตถุแบบไดนามิก": ใช่แล้วในความเป็นจริง ทางออกสำหรับคำถามนี้คือ: ไม่มีวิธีแก้ปัญหาทั่วไปเนื่องจากขึ้นอยู่กับการใช้งาน
Softlion

@ Softlion ไม่การแก้ปัญหาคือสิ่งที่เราต้องหยุดใช้
nik.shornikov

@Softlion จุดประสงค์ของวิธี Tryxxx คืออะไร? TryGet จะไม่ส่งคืนค่าเท็จเมื่อไม่พบคุณสมบัติดังนั้นคุณยังต้องตรวจสอบผลลัพธ์ ผลตอบแทนนั้นไม่มีประโยชน์ ใน TrySet หากไม่มีคีย์นั้นจะมีข้อผิดพลาดแทนที่จะส่งคืนค่าเท็จ ฉันไม่เข้าใจว่าทำไมคุณถึงใช้สิ่งนี้เป็นคำตอบถ้าคุณเขียนความคิดเห็นไว้ที่นี่ "การแก้ปัญหาสำหรับคำถามนี้คือ: ไม่มีวิธีแก้ปัญหาทั่วไปตามการใช้งาน" นั่นก็ไม่จริง ดูคำตอบของ Dykam สำหรับวิธีแก้ปัญหาที่แท้จริง
pqsk

-5

ลองอันนี้

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}

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

ฉันใช้โค้ดนี้ในสถานการณ์จริง มันใช้งานได้ดี
Venkat

6
ให้ฉันเป็นโมฆะตลอดเวลาแม้ว่าทรัพย์สินจะมีอยู่
atlantis

มันจะทำงานกับวัตถุพื้นฐาน แต่ไม่ใช่กับ ExpandoObjects Dynamics ไม่แน่ใจ
Joe

1
เพื่อยืนยันสิ่งนี้จะไม่ทำงานกับdynamicวัตถุใด ๆ (กลับมาเสมอnull)
Gone Coding
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.