Uji apakah properti tersedia pada variabel dinamis

225

Situasi saya sangat sederhana. Di suatu tempat dalam kode saya, saya punya ini:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Jadi, pada dasarnya pertanyaan saya adalah bagaimana memeriksa (tanpa mengeluarkan pengecualian) bahwa properti tertentu tersedia pada variabel dinamis saya. Saya bisa melakukannya GetType()tetapi saya lebih suka menghindari itu karena saya tidak benar-benar perlu tahu jenis objeknya. Yang saya benar-benar ingin tahu adalah apakah sebuah properti (atau metode, jika itu membuat hidup lebih mudah) tersedia. Ada petunjuk?

krisis bundar
sumber
1
Ada beberapa saran di sini: stackoverflow.com/questions/2985161/… - tetapi tidak ada jawaban yang diterima sejauh ini.
Andrew Anderson
terima kasih, saya bisa melihat bagaimana membuat salah satu solusinya, saya bertanya-tanya apakah ada sesuatu yang saya lewatkan
roundcrisis

Jawaban:

159

Saya pikir tidak ada cara untuk mengetahui apakah suatu dynamicvariabel memiliki anggota tertentu tanpa mencoba mengaksesnya, kecuali jika Anda menerapkan kembali cara pengikatan dinamis ditangani dalam kompiler C #. Yang mungkin akan mencakup banyak tebakan, karena itu adalah implementasi yang ditentukan, sesuai dengan spesifikasi C #.

Jadi, Anda harus benar-benar mencoba mengakses anggota dan menangkap pengecualian, jika gagal:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
svick
sumber
2
Saya akan menandai ini sebagai jawaban yang sudah begitu lama, sepertinya itu adalah jawaban terbaik
roundcrisis
8
Solusi yang lebih baik - stackoverflow.com/questions/2839598/…
ministrymason
20
@ministrymason Jika Anda bermaksud melakukan IDictionarydan bekerja dengan itu, itu hanya berfungsi ExpandoObject, itu tidak akan bekerja pada dynamicobjek lain .
svick
5
RuntimeBinderExceptionada di Microsoft.CSharp.RuntimeBindernamespace.
DavidRR
8
Saya masih merasa ingin menggunakan try / catch alih-alih jika / itu praktik yang buruk secara umum terlepas dari skenario ini secara spesifik.
Alexander Ryan Baggett
74

Saya pikir saya akan melakukan perbandingan jawaban Martijn dan jawaban svick ...

Program berikut mengembalikan hasil berikut:

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

Sebagai hasilnya saya sarankan menggunakan refleksi. Lihat di bawah.


Menanggapi komentar bland:

Rasio adalah reflection:exceptionkutu untuk 100000 iterasi:

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

... cukup adil - jika Anda mengharapkannya gagal dengan probabilitas kurang dari ~ 1/47, maka pilih pengecualian.


Di atas mengasumsikan bahwa Anda menjalankan GetProperties()setiap waktu. Anda mungkin dapat mempercepat proses dengan menyimpan hasil GetProperties()untuk setiap jenis dalam kamus atau sejenisnya. Ini dapat membantu jika Anda memeriksa serangkaian tipe yang sama berulang kali.

dav_i
sumber
7
Saya setuju dan suka refleksi dalam pekerjaan saya jika perlu. Keuntungan yang dimilikinya atas Try / Catch semata-mata ketika pengecualian dilemparkan. Jadi apa yang harus ditanyakan seseorang sebelum menggunakan perenungan di sini - apakah mungkin dengan cara tertentu? 90% atau bahkan 75% dari waktu, akankah kode Anda lulus? Maka Coba / Tangkap masih optimal. Jika itu di udara, atau terlalu banyak pilihan untuk satu kemungkinan besar, maka refleksi Anda tepat.
hambar
@ jawaban kasar diedit.
dav_i
1
Terima kasih, terlihat sangat lengkap sekarang.
lembut
@dav_i tidak adil membandingkan keduanya karena keduanya berperilaku berbeda. jawaban svick lebih lengkap.
nawfal
1
@ dav_i Tidak, mereka tidak melakukan fungsi yang sama. Jawaban Martijn memeriksa apakah sebuah properti ada pada tipe waktu kompilasi reguler dalam C #, yang dinyatakan dinamis (artinya ia mengabaikan kompilasi pemeriksaan keamanan waktu). Sedangkan jawaban svick memeriksa apakah suatu properti ada pada objek yang benar - benar dinamis , yaitu sesuatu yang diimplementasikan IIDynamicMetaObjectProvider. Saya mengerti motivasi di balik jawaban Anda, dan menghargainya. Cukup adil untuk menjawabnya.
nawfal
52

Mungkin menggunakan refleksi?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Martijn
sumber
2
Mengutip dari pertanyaan ". Saya bisa melakukan GetType () tetapi saya lebih suka menghindarinya"
roundcrisis
Bukankah ini memiliki kelemahan yang sama dengan saran saya? RouteValueDictionary menggunakan refleksi untuk mendapatkan properti .
Steve Wilkes
12
Anda bisa melakukannya tanpa Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i
Silakan lihat jawaban saya untuk perbandingan jawaban.
dav_i
3
Sebagai satu-kapal: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Pemain untuk mengetik diperlukan untuk membuat kompiler senang tentang lambda.
MushinNoShin
38

Untuk berjaga-jaga jika itu membantu seseorang:

Jika metode GetDataThatLooksVerySimilarButNotTheSame()mengembalikan suatu ExpandoObjectAnda juga dapat melemparkan ke IDictionarysebelum memeriksa.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
karask
sumber
3
Tidak yakin mengapa jawaban ini tidak memiliki lebih banyak suara, karena melakukan persis apa yang diminta (tidak terkecuali melempar atau merefleksikan).
Wolfshead
7
@ Wolfolf Jawaban ini bagus jika Anda tahu bahwa objek dinamis Anda adalah ExpandoObject atau sesuatu yang mengimplementasikan IDictionary <string, objek> tetapi jika itu terjadi pada sesuatu yang lain, maka ini akan gagal.
Damian Powell
9

Dua solusi umum untuk ini termasuk membuat panggilan dan menangkap RuntimeBinderException, menggunakan refleksi untuk memeriksa panggilan, atau membuat serial ke format teks dan parsing dari sana. Masalah dengan pengecualian adalah mereka sangat lambat, karena ketika seseorang dibangun, tumpukan panggilan saat ini adalah serial. Serialising ke JSON atau sesuatu yang analog menghasilkan penalti yang sama. Ini meninggalkan kita dengan refleksi tetapi hanya berfungsi jika objek yang mendasarinya sebenarnya adalah POCO dengan anggota nyata di atasnya. Jika itu pembungkus dinamis di sekitar kamus, objek COM, atau layanan web eksternal, maka refleksi tidak akan membantu.

Solusi lain adalah menggunakan DynamicMetaObjectuntuk mendapatkan nama anggota sebagaimana DLR melihatnya. Dalam contoh di bawah ini, saya menggunakan kelas statis ( Dynamic) untuk menguji Agebidang dan menampilkannya.

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;
    }
}
Damian Powell
sumber
Ternyata Dynamiteypaket nuget sudah melakukan ini. ( nuget.org/packages/Dynamitey )
Damian Powell
8

Jawaban Denis membuat saya berpikir untuk solusi lain menggunakan JsonObjects,

pemeriksa properti header:

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

atau mungkin lebih baik:

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

sebagai contoh:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Charles HETIER
sumber
1
Apakah ada peluang untuk mengetahui apa yang salah dengan jawaban ini?
Charles HETIER
Tidak tahu mengapa ini ditolak, bekerja bagus untuk saya. Saya memindahkan Predikat untuk setiap properti ke kelas pembantu dan memanggil metode Invoke untuk mengembalikan bool dari masing-masing.
markp3rry
7

Yah, saya menghadapi masalah yang sama tetapi pada unit test.

Menggunakan SharpTestsEx Anda dapat memeriksa apakah suatu properti ada. Saya menggunakan ini menguji pengontrol saya, karena karena objek JSON dinamis, seseorang dapat mengubah nama dan lupa untuk mengubahnya di javascript atau sesuatu, jadi pengujian untuk semua properti saat menulis controller harus meningkatkan keselamatan saya.

Contoh:

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

Sekarang, menggunakan SharTestsEx:

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

Dengan menggunakan ini, saya menguji semua properti yang ada menggunakan "Should (). NotThrow ()".

Mungkin di luar topik, tetapi dapat bermanfaat bagi seseorang.

Diego Santin
sumber
Terima kasih, sangat berguna. Menggunakan SharpTestsEx Saya menggunakan baris berikut untuk juga menguji nilai properti dinamis:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen
2

Mengikuti dari jawaban oleh @karask, Anda dapat membungkus fungsi sebagai penolong seperti:

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

Bagi saya ini bekerja:

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;
    }
}
Pelawak
sumber
nulltidak berarti properti tidak ada
quetzalcoatl
Saya tahu, tetapi jika itu nol, saya tidak perlu melakukan apa pun dengan nilai, oleh karena itu untuk usecase saya ok
Jester
0

Jika Anda mengontrol tipe yang digunakan sebagai dinamis, tidak bisakah Anda mengembalikan tuple alih-alih nilai untuk setiap akses properti? Sesuatu seperti...

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

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

Mungkin merupakan implementasi yang naif, tetapi jika Anda membangun salah satu dari ini secara internal setiap kali dan mengembalikannya sebagai ganti nilai aktual, Anda dapat memeriksa Existssetiap akses properti dan kemudian menekannya Valuejika itu benar dengan nilainya default(T)(dan tidak relevan) jika tidak.

Yang mengatakan, saya mungkin kehilangan beberapa pengetahuan tentang bagaimana dinamis bekerja dan ini mungkin bukan saran yang bisa diterapkan.

Shibumi
sumber
0

Dalam kasus saya, saya perlu memeriksa keberadaan metode dengan nama tertentu, jadi saya menggunakan antarmuka untuk itu

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

Selain itu, antarmuka dapat berisi lebih dari sekadar metode:

Antarmuka dapat berisi metode, properti, peristiwa, pengindeks, atau kombinasi keempat tipe anggota tersebut.

Dari: Antarmuka (Panduan Pemrograman C #)

Elegan dan tidak perlu menjebak pengecualian atau bermain dengan refleksi ...

Fred Mauroy
sumber
0

Saya tahu ini benar-benar posting lama tapi di sini ada solusi sederhana untuk bekerja dengan dynamicmengetik c#.

  1. dapat menggunakan refleksi sederhana untuk menyebutkan properti langsung
  2. atau dapat menggunakan objectmetode ekstensi
  3. atau gunakan GetAsOrDefault<int>metode untuk mendapatkan objek yang sangat diketik dengan nilai jika ada atau default jika tidak ada.
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 );
    }
}
Sederhana
sumber
0

Sebagai ExpandoObjectwarisan, IDictionary<string, object>Anda dapat menggunakan pemeriksaan berikut

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Anda dapat membuat metode utilitas untuk melakukan pemeriksaan ini, yang akan membuat kode lebih bersih dan dapat digunakan kembali

Mukesh Bhojwani
sumber
-1

Ini cara lain:

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;
            }
        }
    }
}
Denis
sumber
2
dari mana Anda mendapatkan ide pertanyaannya adalah tentang pengujian properti JObject? Jawaban Anda terbatas pada objek / kelas yang mengekspos IEnumerable atas propertinya. Tidak dijamin oleh dynamic. dynamickata kunci adalah subjek yang jauh lebih luas. Go cek jika Anda dapat menguji Countdi dynamic foo = new List<int>{ 1,2,3,4 }seperti itu
Quetzalcoatl