ทดสอบว่าคุณสมบัติมีอยู่ในตัวแปรแบบไดนามิกหรือไม่


225

สถานการณ์ของฉันง่ายมาก บางแห่งในรหัสของฉันฉันมีสิ่งนี้:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff

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


1
มีข้อเสนอแนะสองสามข้อที่นี่: stackoverflow.com/questions/2985161/… - แต่ยังไม่มีคำตอบที่ยอมรับ
Andrew Anderson

ขอบคุณฉันสามารถดูวิธีที่จะทำให้หนึ่งในวิธีการแก้ปัญหาที่ฉันสงสัยว่ามีสิ่งที่ฉันพลาด
roundcrisis

คำตอบ:


159

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

ดังนั้นคุณควรพยายามเข้าถึงสมาชิกและตรวจสอบข้อผิดพลาดหากล้มเหลว:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 

2
ฉันจะทำเครื่องหมายคำตอบนี้เป็นคำตอบที่ได้รับมานานดูเหมือนว่าจะเป็นคำตอบที่ดีที่สุด
roundcrisis

8
ทางออกที่ดีกว่า - stackoverflow.com/questions/2839598/…
ministrymason

20
@ministrymason ถ้าคุณหมายถึงการคัดเลือกIDictionaryและทำงานกับExpandoObjectมันมันใช้ได้กับมันเท่านั้นมันจะไม่ทำงานกับdynamicวัตถุอื่น ๆ
svick

5
RuntimeBinderExceptionอยู่ในMicrosoft.CSharp.RuntimeBinderเนมสเปซ
DavidRR

8
ฉันยังคงรู้สึกอยากใช้ลอง / จับแทนถ้า / อื่นเป็นการปฏิบัติที่ไม่ดีโดยทั่วไปโดยไม่คำนึงถึงรายละเอียดเฉพาะของสถานการณ์นี้
Alexander Ryan Baggett

74

ฉันคิดว่าฉันทำเปรียบเทียบของคำตอบ Martijn ของและคำตอบของ svick ...

โปรแกรมต่อไปนี้จะคืนค่าผลลัพธ์ดังต่อไปนี้:

Testing with exception: 2430985 ticks
Testing with reflection: 155570 ticks

void Main()
{
    var random = new Random(Environment.TickCount);

    dynamic test = new Test();

    var sw = new Stopwatch();

    sw.Start();

    for (int i = 0; i < 100000; i++)
    {
        TestWithException(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with exception: " + sw.ElapsedTicks.ToString() + " ticks");

    sw.Restart();

    for (int i = 0; i < 100000; i++)
    {
        TestWithReflection(test, FlipCoin(random));
    }

    sw.Stop();

    Console.WriteLine("Testing with reflection: " + sw.ElapsedTicks.ToString() + " ticks");
}

class Test
{
    public bool Exists { get { return true; } }
}

bool FlipCoin(Random random)
{
    return random.Next(2) == 0;
}

bool TestWithException(dynamic d, bool useExisting)
{
    try
    {
        bool result = useExisting ? d.Exists : d.DoesntExist;
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

bool TestWithReflection(dynamic d, bool useExisting)
{
    Type type = d.GetType();

    return type.GetProperties().Any(p => p.Name.Equals(useExisting ? "Exists" : "DoesntExist"));
}

ดังนั้นฉันขอแนะนำให้ใช้การสะท้อน ดูด้านล่าง


ตอบสนองต่อความคิดเห็นที่อ่อนโยน:

อัตราส่วนเป็นreflection:exceptionเห็บสำหรับการทำซ้ำ 100000:

Fails 1/1: - 1:43 ticks
Fails 1/2: - 1:22 ticks
Fails 1/3: - 1:14 ticks
Fails 1/5: - 1:9 ticks
Fails 1/7: - 1:7 ticks
Fails 1/13: - 1:4 ticks
Fails 1/17: - 1:3 ticks
Fails 1/23: - 1:2 ticks
...
Fails 1/43: - 1:2 ticks
Fails 1/47: - 1:1 ticks

... ยุติธรรมเพียงพอ - หากคุณคาดหวังว่ามันจะล้มเหลวด้วยความน่าจะเป็นที่น้อยกว่า ~ 1/47 ให้ไปยกเว้น


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


7
ฉันเห็นด้วยและชอบภาพสะท้อนในงานของฉันตามความเหมาะสม กำไรที่ได้จากการลอง / จับเป็นเพียงอย่างเดียวเมื่อมีการโยนข้อยกเว้น ดังนั้นสิ่งที่บางคนควรถามก่อนใช้การสะท้อนที่นี่ - เป็นไปได้ไหม 90% หรือ 75% ของเวลารหัสของคุณจะผ่านหรือไม่ จากนั้นลอง / จับยังดีที่สุด ถ้ามันลอยอยู่ในอากาศหรือมีตัวเลือกมากเกินไปที่จะเป็นไปได้มากที่สุด
รสชาติ

@bland แก้ไขคำตอบแล้ว
dav_i

1
ขอบคุณตอนนี้ดูเสร็จสมบูรณ์จริงๆ
รสชาติ

@dav_i ไม่ยุติธรรมที่จะเปรียบเทียบทั้งคู่เนื่องจากทั้งคู่ทำงานแตกต่างกัน คำตอบของ svick นั้นสมบูรณ์มากขึ้น
nawfal

1
@dav_i ไม่พวกเขาไม่ทำหน้าที่เดียวกัน คำตอบของ Martijn จะตรวจสอบว่ามีทรัพย์สินอยู่ในประเภทเวลาคอมไพล์ปกติใน C # หรือไม่นั่นคือการประกาศแบบไดนามิก (หมายถึงมันละเว้นการตรวจสอบความปลอดภัยเวลาคอมไพล์) ในขณะที่คำตอบของ svick จะตรวจสอบว่ามีทรัพย์สินอยู่ในวัตถุแบบไดนามิกอย่างแท้จริงIIDynamicMetaObjectProviderหรือไม่ ฉันเข้าใจแรงจูงใจเบื้องหลังคำตอบของคุณและขอบคุณ มันยุติธรรมที่จะตอบอย่างนั้น
nawfal

52

อาจใช้การสะท้อน?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 

2
อ้างอิงจากคำถาม "ฉันสามารถทำ GetType () แต่ฉันควรหลีกเลี่ยง"
roundcrisis

สิ่งนี้ไม่ได้มีข้อเสียเช่นเดียวกับข้อเสนอแนะของฉัน? RouteValueDictionary ใช้สะท้อนคุณสมบัติที่จะได้รับ
Steve Wilkes

12
คุณสามารถทำได้โดยไม่ต้องWhere:.Any(p => p.Name.Equals("PropertyName"))
dav_i

โปรดดูคำตอบของฉันสำหรับการเปรียบเทียบคำตอบ
dav_i

3
((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName"))ในฐานะที่เป็นหนึ่งซับ: จำเป็นต้องใช้ cast to type เพื่อทำให้คอมไพเลอร์มีความสุขเกี่ยวกับแลมบ์ดา
MushinNoShin

38

ในกรณีที่มันช่วยใครบางคน:

หากวิธีการGetDataThatLooksVerySimilarButNotTheSame()ส่งกลับExpandoObjectคุณสามารถส่งไปยังIDictionaryก่อนที่จะตรวจสอบ

dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}

3
ไม่แน่ใจว่าทำไมคำตอบนี้ไม่ได้รับการโหวตมากขึ้นเพราะมันทำในสิ่งที่ถูกถาม (ยกเว้นการโยนหรือการสะท้อน)
Wolfshead

7
@ Wolfshead คำตอบนี้ยอดเยี่ยมถ้าคุณรู้ว่าวัตถุแบบไดนามิกของคุณคือ ExpandoObject หรืออย่างอื่นที่ใช้ IDictionary <string, object> แต่ถ้ามันเป็นอย่างอื่นก็จะล้มเหลว
Damian Powell

9

โซลูชันทั่วไปสองรายการนี้รวมถึงการโทรออกและการจับRuntimeBinderExceptionโดยใช้การสะท้อนเพื่อตรวจสอบการโทรหรือการทำให้เป็นอนุกรมเป็นรูปแบบข้อความและการแยกวิเคราะห์จากที่นั่น ปัญหาเกี่ยวกับข้อยกเว้นคือพวกเขาช้ามากเพราะเมื่อมีการสร้างขึ้นสแต็กการโทรปัจจุบันจะถูกทำให้เป็นอนุกรม การทำให้เป็นอันดับต่อ JSON หรือบางอย่างคล้ายคลึงกันจะมีโทษเหมือนกัน สิ่งนี้ทำให้เราไตร่ตรอง แต่จะใช้งานได้ก็ต่อเมื่อวัตถุต้นแบบเป็นจริง POCO กับสมาชิกจริงในนั้น ถ้ามันเป็น wrapper แบบไดนามิกรอบ ๆ พจนานุกรมวัตถุ COM หรือบริการเว็บภายนอกการสะท้อนจะไม่ช่วย

อีกวิธีหนึ่งคือใช้DynamicMetaObjectเพื่อรับชื่อสมาชิกตามที่ DLR เห็น ในตัวอย่างด้านล่างฉันใช้คลาสคงที่ ( Dynamic) เพื่อทดสอบAgeฟิลด์และแสดง

class Program
{
    static void Main()
    {
        dynamic x = new ExpandoObject();

        x.Name = "Damian Powell";
        x.Age = "21 (probably)";

        if (Dynamic.HasMember(x, "Age"))
        {
            Console.WriteLine("Age={0}", x.Age);
        }
    }
}

public static class Dynamic
{
    public static bool HasMember(object dynObj, string memberName)
    {
        return GetMemberNames(dynObj).Contains(memberName);
    }

    public static IEnumerable<string> GetMemberNames(object dynObj)
    {
        var metaObjProvider = dynObj as IDynamicMetaObjectProvider;

        if (null == metaObjProvider) throw new InvalidOperationException(
            "The supplied object must be a dynamic object " +
            "(i.e. it must implement IDynamicMetaObjectProvider)"
        );

        var metaObj = metaObjProvider.GetMetaObject(
            Expression.Constant(metaObjProvider)
        );

        var memberNames = metaObj.GetDynamicMemberNames();

        return memberNames;
    }
}

ปรากฎว่าDynamiteyแพคเกจนักเก็ตทำเช่นนี้แล้ว ( nuget.org/packages/Dynamitey )
Damian Powell

8

คำตอบของเดนิสทำให้ฉันคิดถึงวิธีแก้ปัญหาอื่นโดยใช้ JsonObjects

ตัวตรวจสอบคุณสมบัติส่วนหัว:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).OfType<JProperty>()
                                     .Any(prop => prop.Name == "header");

หรืออาจจะดีกว่า:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).Property("header") != null;

ตัวอย่างเช่น:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;

1
มีโอกาสที่จะรู้ว่าคำตอบนี้ผิดหรือเปล่า?
Charles HETIER

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

7

ฉันประสบปัญหาคล้ายกัน แต่ในการทดสอบหน่วย

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

ตัวอย่าง:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";

ตอนนี้ใช้ SharTestsEx:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();

เมื่อใช้สิ่งนี้ฉันจะทดสอบคุณสมบัติที่มีอยู่ทั้งหมดโดยใช้ "Should (). NotThrow ()"

อาจไม่อยู่ในหัวข้อ แต่สามารถเป็นประโยชน์กับใครบางคนได้


ขอบคุณมีประโยชน์มาก ใช้ SharpTestsEx ฉันใช้บรรทัดต่อไปนี้เพื่อทดสอบค่าของคุณสมบัติไดนามิก:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen

2

จากคำตอบของ @karask คุณสามารถสรุปฟังก์ชั่นเป็นตัวช่วยได้ดังนี้

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}

2

สำหรับฉันงานนี้:

if (IsProperty(() => DynamicObject.MyProperty))
  ; // do stuff



delegate string GetValueDelegate();

private bool IsProperty(GetValueDelegate getValueMethod)
{
    try
    {
        //we're not interesting in the return value.
        //What we need to know is whether an exception occurred or not

        var v = getValueMethod();
        return v != null;
    }
    catch (RuntimeBinderException)
    {
        return false;
    }
    catch
    {
        return true;
    }
}

nullไม่ได้หมายความว่าไม่มีที่พัก
quetzalcoatl

ฉันรู้ แต่ถ้ามันเป็นโมฆะฉันไม่จำเป็นต้องทำอะไรกับค่าดังนั้นสำหรับ usecase ของฉันมันก็โอเค
Jester

0

หากคุณควบคุมประเภทที่ใช้เป็นแบบไดนามิกคุณจะไม่สามารถส่งคืนค่า tuple แทนค่าสำหรับการเข้าถึงคุณสมบัติทุกครั้งหรือไม่ สิ่งที่ต้องการ...

public class DynamicValue<T>
{
    internal DynamicValue(T value, bool exists)
    {
         Value = value;
         Exists = exists;
    }

    T Value { get; private set; }
    bool Exists { get; private set; }
}

อาจเป็นการใช้งานแบบไร้เดียงสา แต่ถ้าคุณสร้างสิ่งเหล่านี้ภายในแต่ละครั้งและคืนค่านั้นแทนที่จะเป็นค่าจริงคุณสามารถตรวจสอบExistsการเข้าถึงคุณสมบัติทั้งหมดและจากนั้นกดValueถ้ามันมีค่าdefault(T) (และไม่เกี่ยวข้อง) หากไม่ได้

ที่กล่าวว่าฉันอาจขาดความรู้เกี่ยวกับวิธีการทำงานแบบไดนามิกและนี่อาจไม่ใช่คำแนะนำที่ใช้การได้


0

ในกรณีของฉันฉันต้องตรวจสอบการมีอยู่ของวิธีที่มีชื่อเฉพาะดังนั้นฉันจึงใช้อินเทอร์เฟซสำหรับสิ่งนั้น

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
    plugin.CustomPluginAction(action);
}

นอกจากนี้อินเตอร์เฟสยังมีวิธีการมากกว่า:

การเชื่อมต่อสามารถมีวิธีการคุณสมบัติเหตุการณ์ดัชนีหรือการรวมกันของสมาชิกทั้งสี่ประเภท

จาก: ส่วนต่อประสาน (คู่มือการเขียนโปรแกรม C #)

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


0

ฉันรู้ว่านี้คือการโพสต์เก่าจริงๆ แต่นี่คือวิธีง่ายๆในการทำงานร่วมกับพิมพ์ dynamicc#

  1. สามารถใช้การสะท้อนแบบง่ายเพื่อระบุคุณสมบัติโดยตรง
  2. หรือสามารถใช้ objectวิธีการขยาย
  3. หรือใช้GetAsOrDefault<int>วิธีการรับวัตถุที่พิมพ์อย่างยิ่งใหม่ที่มีค่าถ้ามีอยู่หรือค่าเริ่มต้นหากไม่มีอยู่
public static class DynamicHelper
{
    private static void Test( )
    {
        dynamic myobj = new
                        {
                            myInt = 1,
                            myArray = new[ ]
                                      {
                                          1, 2.3
                                      },
                            myDict = new
                                     {
                                         myInt = 1
                                     }
                        };

        var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
        int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );

        if( default( int ) != myIntOrZero )
            Console.WriteLine( $"myInt: '{myIntOrZero}'" );

        if( default( int? ) != myNullableInt )
            Console.WriteLine( $"myInt: '{myNullableInt}'" );

        if( DoesPropertyExist( myobj, "myInt" ) )
            Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
    }

    public static bool DoesPropertyExist( dynamic dyn, string property )
    {
        var t = ( Type )dyn.GetType( );
        var props = t.GetProperties( );
        return props.Any( p => p.Name.Equals( property ) );
    }

    public static object GetAs< T >( dynamic obj, Func< T > lookup )
    {
        try
        {
            var val = lookup( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return null;
    }

    public static T GetAsOrDefault< T >( this object obj, Func< T > test )
    {
        try
        {
            var val = test( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return default( T );
    }
}

0

ในฐานะที่เป็นExpandoObjectสืบทอดIDictionary<string, object>คุณสามารถใช้การตรวจสอบต่อไปนี้

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))    
//Do stuff

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


-1

นี่คือวิธีอื่น:

using Newtonsoft.Json.Linq;

internal class DymanicTest
{
    public static string Json = @"{
            ""AED"": 3.672825,
            ""AFN"": 56.982875,
            ""ALL"": 110.252599,
            ""AMD"": 408.222002,
            ""ANG"": 1.78704,
            ""AOA"": 98.192249,
            ""ARS"": 8.44469
}";

    public static void Run()
    {
        dynamic dynamicObject = JObject.Parse(Json);

        foreach (JProperty variable in dynamicObject)
        {
            if (variable.Name == "AMD")
            {
                var value = variable.Value;
            }
        }
    }
}

2
คุณได้รับความคิดที่คำถามเกี่ยวกับการทดสอบคุณสมบัติของ JObject ที่ไหน คำตอบของคุณถูก จำกัด เฉพาะวัตถุ / คลาสที่เปิดเผย IEnumerable ผ่านคุณสมบัติของพวกเขา dynamicไม่รับประกันโดย dynamicคำหลักมากเรื่องที่กว้างขึ้น ตรวจสอบไปถ้าคุณสามารถทดสอบCountในdynamic foo = new List<int>{ 1,2,3,4 }แบบนั้น
Quetzalcoatl
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.