Verifica se una proprietà è disponibile su una variabile dinamica


225

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, sostanzialmente la mia domanda è come verificare (senza generare un'eccezione) che una determinata proprietà sia disponibile sulla mia variabile dinamica. Potrei farlo, GetType()ma preferirei evitarlo poiché non ho davvero bisogno di conoscere il tipo di oggetto. Tutto quello che voglio davvero sapere è se è disponibile una proprietà (o metodo, se ciò semplifica la vita). Qualche puntatore?


1
Ci sono un paio di suggerimenti qui: stackoverflow.com/questions/2985161/… - ma finora nessuna risposta accettata.
Andrew Anderson,

grazie, posso vedere come rendere l'abete una delle soluzioni, anche se mi chiedevo se c'è qualcosa che mi sto perdendo
roundcrisis

Risposte:


159

Penso che non ci sia modo di scoprire se una dynamicvariabile ha un determinato membro senza tentare di accedervi, a meno che non sia stata implementata nuovamente la modalità di gestione dell'associazione dinamica nel compilatore C #. Il che probabilmente includerebbe molte ipotesi, perché è definito dall'implementazione, secondo la specifica C #.

Quindi dovresti effettivamente provare ad accedere al membro e prendere un'eccezione, se fallisce:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

2
Lo segnerò come la risposta da tanto tempo, sembra la risposta migliore
roundcrisis,


20
@ministrymason Se vuoi dire casting IDictionarye lavorare con quello, funziona solo su ExpandoObject, non funzionerà su nessun altro dynamicoggetto.
svick,

5
RuntimeBinderExceptionè nello Microsoft.CSharp.RuntimeBinderspazio dei nomi.
DavidRR

8
Ho ancora voglia di usare try / catch anziché if / else è una cattiva pratica in generale, indipendentemente da queste specifiche di questo scenario.
Alexander Ryan Baggett,

74

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.


Risposta al commento insipido:

I rapporti sono reflection:exceptiontick 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à con meno di ~ 1/47, allora vai per eccezione.


Quanto sopra presuppone che tu stia funzionando GetProperties()ogni volta. È possibile accelerare il processo memorizzando nella cache il risultato di GetProperties()ciascun tipo in un dizionario o simile. Ciò può essere utile se stai verificando più volte lo stesso set di tipi.


7
Concordo e mi piace la riflessione nel mio lavoro, se del caso. I guadagni che ha su Try / Catch sono solo quando viene generata l'eccezione. Quindi cosa dovrebbe chiedere qualcuno prima di usare la riflessione qui - è probabile che sia un certo modo? Il 90% o anche il 75% delle volte passerà il tuo codice? Quindi Try / Catch è ancora ottimale. Se è in aria, o troppe scelte per essere più probabile, allora il tuo riflesso è perfetto.
insipido

@bland Risposta modificata.
dav_i,

1
Grazie, sembra davvero completo ora.
insipido

@dav_i non è corretto confrontarli entrambi poiché entrambi si comportano diversamente. la risposta di svick è più completa.
nawfal,

1
@dav_i No, non svolgono la stessa funzione. La risposta di Martijn verifica se esiste una proprietà in un normale tipo di tempo di compilazione in C #, che viene dichiarata dinamica (nel senso che ignora i controlli di sicurezza in fase di compilazione). Considerando che la risposta di svick verifica se esiste una proprietà su un oggetto veramente dinamico , cioè qualcosa che implementa IIDynamicMetaObjectProvider. Capisco la motivazione alla base della tua risposta e la apprezzo. È giusto rispondere così.
nawfal,

52

Forse usi la riflessione?

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

2
Citazione dalla domanda ". Potrei fare GetType () ma preferirei evitarlo"
Roundcrisis,

Questo non ha gli stessi inconvenienti del mio suggerimento? RouteValueDictionary utilizza la riflessione per ottenere le proprietà .
Steve Wilkes,

12
Puoi semplicemente fare a meno di Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i

Si prega di consultare la mia risposta per il confronto delle risposte.
dav_i,

3
Come una battuta: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Il cast da digitare è necessario per rendere felice il compilatore per la lambda.
MushinNoShin

38

Nel caso in cui aiuti qualcuno:

Se il metodo GetDataThatLooksVerySimilarButNotTheSame()restituisce un ExpandoObject, puoi anche eseguire il cast su un IDictionaryprima di controllare.

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

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

3
Non sono sicuro del perché questa risposta non abbia più voti, perché fa esattamente ciò che è stato richiesto (nessuna eccezione o riflessione).
Wolfshead,

7
@Wolfshead Questa risposta è ottima se sai che il tuo oggetto dinamico è un ExpandoObject o qualcos'altro che implementa IDictionary <stringa, oggetto> ma se succede che è qualcos'altro, allora fallirà.
Damian Powell,

9

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

Un'altra soluzione consiste nell'utilizzare il DynamicMetaObjectper ottenere i nomi dei membri come li vede il DLR. Nell'esempio seguente, utilizzo una classe statica ( Dynamic) per verificare il Agecampo 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<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;
    }
}

Si scopre che il Dynamiteypacchetto nuget lo fa già. ( nuget.org/packages/Dynamitey )
Damian Powell,

8

La risposta di Denis mi ha fatto pensare a un'altra soluzione usando JsonObjects,

un controllo proprietà dell'intestazione:

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

o forse meglio:

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

per esempio:

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

1
C'è una possibilità di sapere cosa c'è che non va in questa risposta, per favore?
Charles HETIER,

Non so perché questo sia stato votato, ha funzionato alla grande per me. Ho spostato il Predicato per ogni proprietà in una classe di supporto e ho chiamato il metodo Invoke per restituire un bool da ciascuno.
markp3rry

7

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

Utilizzando SharpTestsEx è possibile verificare l'esistenza di una proprietà. Uso questo test sui miei controller, perché poiché l'oggetto JSON è dinamico, qualcuno può cambiare il nome e dimenticare di cambiarlo nel javascript o qualcosa del genere, quindi testare tutte le proprietà quando si scrive il controller dovrebbe aumentare la mia 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, collaudo tutte le proprietà esistenti usando "Should (). NotThrow ()".

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


Grazie molto utile Uso di SharpTestsEx Uso la seguente riga per testare anche il valore della proprietà dinamica:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen,

2

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

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

2

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

nullnon significa che la proprietà non esiste
quetzalcoatl

Lo so ma se è nullo non ho bisogno di fare nulla con il valore quindi per il mio caso è ok
Jester

0

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

public class DynamicValue<T>
{
    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 costruisci uno di questi internamente ogni volta e restituisci quello invece del valore effettivo, puoi controllare Existsogni accesso alla proprietà e poi premere Valuese lo fa con valore essendo default(T)(e irrilevante) se non lo fa.

Detto questo, potrei mancare alcune conoscenze su come funziona la dinamica e questo potrebbe non essere un suggerimento praticabile.


0

Nel mio caso, dovevo verificare l'esistenza di un metodo con un nome specifico, quindi ho usato un'interfaccia per quello

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(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 per programmatori C #)

Elegante e non c'è bisogno di intrappolare le eccezioni o giocare con la riflessione ...


0

So che questo è un post molto vecchio, ma qui è una soluzione semplice per lavorare con il dynamictipo c#.

  1. può usare una semplice riflessione per enumerare le proprietà dirette
  2. o può usare il objectmetodo di estensione
  3. oppure usa il GetAsOrDefault<int>metodo per ottenere un nuovo oggetto fortemente tipizzato con valore se esiste o predefinito se non esiste.
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

Come ExpandoObjecteredita IDictionary<string, object>è possibile utilizzare il seguente controllo

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

È possibile creare un metodo di utilità per eseguire questo controllo, che renderà il codice molto più pulito e riutilizzabile


-1

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

2
da dove ti è venuta l'idea della domanda sulle proprietà di JObject? La tua risposta è limitata a oggetti / classi che espongono IEnumerable sulle loro proprietà. Non garantito da dynamic. dynamicla parola chiave è un argomento molto più ampio. Vai a vedere se riesci a provare Countin dynamic foo = new List<int>{ 1,2,3,4 }questo modo
quetzalcoatl
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.