Verifica se una proprietà è disponibile su una variabile dynamic

La mia situazione è molto semplice. Da qualche parte nel mio codice ho questo:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); //How to do this? if (myVariable.MyProperty.Exists) //Do stuff 

Quindi, in pratica, la mia domanda è come verificare (senza generare un’eccezione) che una determinata proprietà sia disponibile sulla mia variabile dynamic. Potrei fare GetType() ma preferirei evitarlo visto che non ho davvero bisogno di sapere il tipo di object. Tutto quello che voglio veramente sapere è se una proprietà (o un metodo, se questo rende la vita più facile) è disponibile. Qualche indicazione?

Penso che non ci sia modo di scoprire se una variabile dynamic ha un determinato membro senza tentare di accedervi, a meno che non si riesca a implementare il modo in cui il binding dinamico viene gestito nel compilatore C #. Il che probabilmente include molte supposizioni, perché è definito dall’implementazione, secondo la specifica C #.

Quindi dovresti provare ad accedere al membro e rilevare un’eccezione, se fallisce:

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

Ho pensato di fare un confronto tra la risposta di Martijn e la risposta di svick …

Il seguente programma restituisce i seguenti risultati:

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

Di conseguenza, suggerirei di usare la riflessione. Vedi sotto.


Rispondendo al commento blando:

I rapporti sono reflection:exception tick di reflection:exception per 100000 iterazioni:

 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 

… abbastanza giusto – se ti aspetti che fallisca con una probabilità inferiore a ~ 1/47, vai all’eccezione.


Quanto sopra presuppone che tu stia eseguendo GetProperties() ogni volta. È ansible velocizzare il processo memorizzando nella cache il risultato di GetProperties() per ciascun tipo in un dizionario o simile. Questo può essere d’aiuto se controlli lo stesso insieme di tipi più e più volte.

Forse usare la riflessione?

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

Nel caso in cui aiuti qualcuno:

Se il metodo GetDataThatLooksVerySimilarButNotTheSame() restituisce un ExpandoObject , puoi anche eseguire il cast su un IDictionary prima di controllarlo.

 dynamic test = new System.Dynamic.ExpandoObject(); test.foo = "bar"; if (((IDictionary)test).ContainsKey("foo")) { Console.WriteLine(test.foo); } 

Bene, ho affrontato un problema simile ma sui test unitari.

Usando SharpTestsEx puoi verificare se esiste una proprietà. Uso questo test sui miei controller, perché poiché l’object JSON è dinamico, qualcuno può cambiare il nome e dimenticare di cambiarlo in javascript o qualcosa del genere, quindi testare tutte le proprietà quando si scrive il controller dovrebbe aumentare la sicurezza.

Esempio:

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

Ora, usando SharTestsEx:

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

Usando questo, provo tutte le proprietà esistenti usando “Should (). NotThrow ()”.

Probabilmente è fuori discussione, ma può essere utile per qualcuno.

Le due soluzioni comuni includono la chiamata e l’intercettazione di RuntimeBinderException , l’uso della reflection per verificare la chiamata o la serializzazione in un formato di testo e l’analisi da lì. Il problema con le eccezioni è che sono molto lenti, perché quando uno viene costruito, lo stack di chiamate corrente viene serializzato. La serializzazione a JSON o qualcosa di analogo comporta una penalità simile. Questo ci lascia con riflessi, ma funziona solo se l’object sottostante è in realtà un POCO con membri reali su di esso. Se si tratta di un wrapper dinamico attorno a un dizionario, a un object COM o a un servizio Web esterno, la riflessione non sarà di aiuto.

Un’altra soluzione è utilizzare DynamicMetaObject per ottenere i nomi dei membri come li vede il DLR. Nell’esempio seguente, utilizzo una class statica ( Dynamic ) per testare il campo Age e visualizzarlo.

 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 GetMemberNames(object dynObj) { var metaObjProvider = dynObj as IDynamicMetaObjectProvider; if (null == metaObjProvider) throw new InvalidOperationException( "The supplied object must be a dynamic object " + "(ie it must implement IDynamicMetaObjectProvider)" ); var metaObj = metaObjProvider.GetMetaObject( Expression.Constant(metaObjProvider) ); var memberNames = metaObj.GetDynamicMemberNames(); return memberNames; } } 

La risposta di Denis mi ha fatto riflettere su un’altra soluzione usando JsonObjects,

un correttore di proprietà dell’intestazione:

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

o forse meglio:

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

per esempio:

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

Per me questo funziona:

 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) ? false : true; } catch (RuntimeBinderException) { return false; } catch { return true; } } 

Seguendo la risposta di @karask, potresti avvolgere la funzione come un aiutante in questo modo:

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

Se controlli il tipo utilizzato come dinamico, non potresti restituire una tupla invece di un valore per ogni accesso di proprietà? Qualcosa di simile a…

 public class DynamicValue { internal DynamicValue(T value, bool exists) { Value = value; Exists = exists; } T Value { get; private set; } bool Exists { get; private set; } } 

Forse un’implementazione ingenua, ma se si costruisce uno di questi internamente ogni volta e si restituisce quello invece del valore effettivo, è ansible selezionare Exists su ogni accesso di proprietà e quindi premere Value se lo fa con il valore default(T) (e irrilevante) se non lo fa.

Detto questo, potrei mancare qualche conoscenza su come funziona la dynamic e questo potrebbe non essere un suggerimento praticabile.

Nel mio caso, avevo bisogno di verificare l’esistenza di un metodo con un nome specifico, quindi ho usato un’interfaccia per quello

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

Inoltre, le interfacce possono contenere più di semplici metodi:

Le interfacce possono contenere metodi, proprietà, eventi, indicizzatori o qualsiasi combinazione di questi quattro tipi di membri.

Da: Interfacce (Guida alla programmazione C #)

Elegante e non c’è bisogno di intrappolare le eccezioni o giocare con la riflessione …

Ecco l’altro modo:

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