Come rilevare se esiste una proprietà su un ExpandoObject?


188

In JavaScript puoi rilevare se una proprietà è definita usando la parola chiave non definita:

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

Come faresti in C # usando la parola chiave dinamica con ExpandoObjecte senza generare un'eccezione?


5
@CodeInChaos: si noti che il codice visualizzato non controlla il valore di data.myProperty; controlla ciò che typeof data.myPropertyritorna. È corretto che data.myPropertypossa esistere ed essere impostato su undefined, ma in tal caso, typeofrestituirà qualcosa di diverso "undefined". Quindi questo codice funziona.
Aasmund Eldhuset,

Risposte:


181

Secondo MSDN la dichiarazione mostra che sta implementando IDictionary:

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

Puoi usarlo per vedere se un membro è definito:

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

3
Per semplificare questo controllo, ho sovraccaricato TryGetValue e lo restituisco sempre true, impostando il valore restituito su "indefinito" se la proprietà non è stata definita. if (someObject.someParam! = "undefined") ... E funziona :)
Softlion

Anche possibile :), ma penso che volevi dire "annullato" anziché sovraccarico.
Dykam,

Sì. Ho nuovamente cambiato "indefinito" in un valore const di un oggetto speciale che ho creato altrove. Previene i problemi di lancio: p
Softlion il

1
Credo che questa soluzione sia ancora attuale; non prendere la parola di nessuno per il prezzo della riflessione
provalo

1
@ BlueRaja-DannyPflughoeft Sì, lo è. Senza il cast sarà solo una chiamata dinamica, con il caso che si ottiene negli interni. Più specificamente, è esplicitamente implementato: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/…
Dykam

28

Una distinzione importante deve essere fatta qui.

La maggior parte delle risposte qui sono specifiche per ExpandoObject menzionato nella domanda. Ma un uso comune (e un motivo per atterrare su questa domanda durante la ricerca) è quando si utilizza ASP.Net MVC ViewBag. Questa è un'implementazione / sottoclasse personalizzata di DynamicObject, che non genererà un'eccezione quando si controlla null su qualsiasi nome di proprietà arbitrario. Supponiamo che tu possa dichiarare una proprietà come:

@{
    ViewBag.EnableThinger = true;
}

Supponiamo quindi che tu voglia verificarne il valore e se sia addirittura impostato, se esiste. Quanto segue è valido, verrà compilato, non genererà eccezioni e ti darà la risposta giusta:

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

Ora sbarazzati della dichiarazione di EnableThinger. Lo stesso codice viene compilato ed eseguito correttamente. Non c'è bisogno di riflessione.

A differenza di ViewBag, ExpandoObject verrà lanciato se si verifica la presenza di null su una proprietà che non esiste. Per ottenere la funzionalità più delicata di MVC ViewBag dai tuoi dynamicoggetti, dovrai utilizzare un'implementazione di dinamica che non getti.

È possibile semplicemente utilizzare l'implementazione esatta in 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

Puoi vederlo collegato a MVC Views qui, in MVC ViewPage:

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

La chiave del comportamento elegante di DynamicViewDataDictionary è l'implementazione del dizionario su ViewDataDictionary, qui:

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

In altre parole, restituisce sempre un valore per tutte le chiavi, indipendentemente da ciò che contiene, restituisce semplicemente null quando non c'è nulla. Tuttavia, ViewDataDictionary ha l'onere di essere legato al Modello MVC, quindi è meglio eliminare solo le parti del dizionario graziose da utilizzare all'esterno di MVC Views.

È troppo lungo per pubblicare davvero tutte le budella qui - la maggior parte solo implementando IDictionary - ma ecco un oggetto dinamico (classe DDict) che non genera controlli null su proprietà che non sono state dichiarate, su Github:

https://github.com/b9chris/GracefulDynamicDictionary

Se vuoi solo aggiungerlo al tuo progetto tramite NuGet, il suo nome è GracefulDynamicDictionary .


Perché hai votato verso il basso su DynamicDictionary perché non utilizza la riflessione allora?
Softlion,

allora puoi votare in quanto questa è la stessa soluzione :)
Softlion,

3
Certamente non è la stessa soluzione.
Chris Moschini,

"dovrai creare una sottoclasse simile che non venga generata quando non viene trovata una proprietà." => lo è! Oh no non lo è. La mia soluzione è migliore Lancia - perché lo vogliamo, E non può nemmeno lanciare se si usa TryXX;
Softlion,

1
QUESTO, QUESTO è esattamente il motivo per cui sono qui, non sono riuscito a capire perché un codice (viewbag) NON si stava rompendo. Grazie.
Adam Tolley,

11

Di recente ho risposto a una domanda molto simile: come posso riflettere sui membri dell'oggetto dinamico?

In breve, ExpandoObject non è l'unico oggetto dinamico che potresti ottenere. Reflection funzionerebbe con tipi statici (tipi che non implementano IDynamicMetaObjectProvider). Per i tipi che implementano questa interfaccia, la riflessione è sostanzialmente inutile. Per ExpandoObject, puoi semplicemente verificare se la proprietà è definita come chiave nel dizionario sottostante. Per altre implementazioni, potrebbe essere impegnativo e talvolta l'unico modo è lavorare con le eccezioni. Per i dettagli, seguire il link sopra.


11

AGGIORNATO: è possibile utilizzare i delegati e provare a ottenere un valore dalla proprietà dell'oggetto dinamico se esiste. Se non è presente alcuna proprietà, è sufficiente prendere l'eccezione e restituire false.

Dai un'occhiata, funziona bene per me:

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

L'output del codice è il seguente:

True
True
False

2
@marklam è un male catturare tutte le eccezioni quando non sai cosa causa l'eccezione. Nel nostro caso va bene dato che prevediamo la possibile assenza di un campo.
Alexander G,

3
se sai quali sono le cause dell'eccezione, devi anche conoscerne il tipo, quindi prendi (Qualunque sia l'eccezione) Altrimenti il ​​tuo codice continuerà silenziosamente anche se hai un'eccezione imprevista, come ad esempio OutOfMemoryException.
marklam,

4
Puoi passare qualsiasi getter a IsPropertyExist. In questo esempio, sai che uno può lanciare un InvalidOperationException. In pratica, non hai idea di quale eccezione possa essere generata. +1 per contrastare il culto del carico.
Piedar

2
Questa soluzione è inaccettabile se le prestazioni sono importanti, ad esempio se utilizzata in un ciclo con oltre 500 iterazioni, si somma e può causare molti secondi di ritardo. Ogni volta che viene rilevata un'eccezione, lo stack deve essere copiato nell'oggetto eccezione
Jim109,

1
Ri: Prestazioni: il debugger collegato e Console.WriteLine sono i bit lenti. 10.000 iterazioni qui richiedono meno di 200ms (con 2 eccezioni per iterazione). Lo stesso test senza eccezioni richiede una manciata di millisecondi. Ciò significa che se ti aspetti che il tuo utilizzo di questo codice manchi solo raramente una proprietà, o se lo chiami un numero limitato di volte, o puoi memorizzare i risultati nella cache, ti preghiamo di rendersi conto che tutto ha il suo posto e nessuno dei eccessivi -avvertiti rigurgitati qui contano.
Caos il

10

Volevo creare un metodo di estensione in modo da poter fare qualcosa del tipo:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

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

... ma non è possibile creare estensioni in ExpandoObjectbase alla documentazione di C # 5 (maggiori informazioni qui ).

Così ho finito per creare un aiutante di classe:

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

Per usarlo:

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

4
vota per commenti utili e link alle estensioni per ExpandoObject.
Roberto,

1

Perché non si desidera utilizzare Reflection per ottenere una serie di caratteri propri? Come questo

 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
 } 

Non sono sicuro che restituirà le proprietà aggiunte in modo dinamico, la mia ipotesi è che restituisca i metodi dell'oggetto Dynamic.
Dykam,

1

Questo metodo di estensione verifica l'esistenza di una proprietà e quindi restituisce il valore o null. Questo è utile se non vuoi che le tue applicazioni generino eccezioni non necessarie, almeno quelle che puoi aiutare.

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

Se può essere usato come tale:

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

o se la tua variabile locale è 'dinamica' dovrai prima lanciarla su ExpandoObject.

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

1

A seconda del caso d'uso, se null può essere considerato uguale a indefinito, è possibile trasformare ExpandoObject in 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"));

Produzione:

a is 1
b is 2.5
c is undefined

-2
(authorDynamic as ExpandoObject).Any(pair => pair.Key == "YourProp");

-3

Ehi ragazzi smettete di usare Reflection per tutto ciò che costa molti cicli della CPU.

Ecco la soluzione:

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
Questo mostra come implementare un oggetto dinamico, non come vederlo uscire da una proprietà su un oggetto dinamico.
Matt Warren,

È possibile verificare se un'istanza dinamica ha una proprietà eseguendo un controllo null rispetto alla proprietà in questione.
ctorx,

2
"Questo mostra come implementare un oggetto dinamico": sì, in effetti lo è. La soluzione a questa domanda è: non esiste una soluzione generica poiché dipende dall'implementazione.
Softlion,

@Softlion No, la soluzione è quella cosa che noi dobbiamo smettere di usare
nik.shornikov

@Softlion Qual è lo scopo dei metodi Tryxxx? TryGet non restituirà mai false quando non trova la proprietà, quindi devi comunque controllare il risultato. Il ritorno è inutile. In TrySet, se la chiave non esiste, genererà un'eccezione invece di restituire false. Non capisco perché dovresti anche usarlo come risposta, se tu stesso scrivessi qui sui commenti "La soluzione a questa domanda è: non esiste una soluzione generica in quanto dipende dall'implementazione", anche questo non è vero. Guarda la risposta di Dykam per la vera soluzione.
pqsk,

-5

Prova questo

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

3
Ciò verificherebbe l'esistenza di una proprietà dell'oggetto nascosta sotto il nome dinamico, che è un dettaglio di implementazione. Hai verificato la tua soluzione in codice reale prima di pubblicare? Non dovrebbe funzionare affatto.
Softlion,

Ho usato questo pezzo di codice in uno scenario in tempo reale. Funziona bene
Venkat,

6
Mi dà null tutto il tempo, anche se la proprietà esiste.
atlantis,

Funzionerebbe con oggetti di base, ma non con ExpandoObjects. Dinamica, non sono sicuro.
Joe,

1
Per confermare, questo non funziona neanche con gli dynamicoggetti (restituisce sempre null).
Codice finito il
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.