Como detectar se existe uma propriedade em um ExpandoObject?

188

Em javascript, você pode detectar se uma propriedade é definida usando a palavra-chave indefinida:

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

Como você faria isso em C # usando a palavra-chave dinâmica com ExpandoObjecte sem gerar uma exceção?

Softlion
fonte
5
@CodeInChaos: Observe que o código exibido não verifica o valor de data.myProperty; verifica o que typeof data.myPropertyretorna. É correto que data.myPropertypossa existir e estar definido como undefined, mas nesse caso, typeofretornará algo diferente "undefined". Portanto, esse código funciona.
Aasmund Eldhuset

Respostas:

181

De acordo com o MSDN, a declaração mostra que está implementando o IDictionary:

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

Você pode usar isso para ver se um membro está definido:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}
Dykam
fonte
3
Para tornar essa verificação mais simples, sobrecarreguei o TryGetValue e o faço sempre retornar verdadeiro, configurando o valor de retorno como "indefinido" se a propriedade não tiver sido definida. if (someObject.someParam = "indefinido"!) ... E funciona :)
Softlion
Também é possível :), mas acho que você quis dizer "substituído" em vez de sobrecarregado.
Dykam
Sim. Novamente alterei "indefinido" para o valor const de um objeto especial que criei em outro lugar. Evita problemas de fundição: p
Softlion
1
Eu acredito que esta solução ainda é atual; não aceite a palavra de ninguém pelo preço da reflexão - teste você mesmo e veja se você pode pagar
nik.shornikov 10/13/13
1
@ BlueRaja-DannyPflughoeft Sim, é. Sem o elenco, será apenas uma invocação dinâmica, com o caso que você recebe nos internos. Mais especificamente, ele é implementado explicitamente: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/…
Dykam 04/02/2015
28

Uma distinção importante precisa ser feita aqui.

A maioria das respostas aqui são específicas para o ExpandoObject, mencionado na pergunta. Mas um uso comum (e um motivo para abordar essa questão ao pesquisar) é ao usar o ASP.Net MVC ViewBag. Essa é uma implementação / subclasse personalizada do DynamicObject, que não gera uma exceção quando você verifica nulo em qualquer nome de propriedade arbitrária. Suponha que você possa declarar uma propriedade como:

@{
    ViewBag.EnableThinger = true;
}

Suponha que você queira verificar seu valor e se está definido - se ele existe. O seguinte é válido, será compilado, não emitirá nenhuma exceção e fornece a resposta certa:

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

Agora livre-se da declaração de EnableThinger. O mesmo código compila e executa corretamente. Não há necessidade de reflexão.

Ao contrário do ViewBag, o ExpandoObject será lançado se você verificar nulo em uma propriedade que não existe. Para tirar a funcionalidade mais suave do MVC ViewBag de seus dynamicobjetos, você precisará usar uma implementação de dinâmica que não seja lançada.

Você pode simplesmente usar a implementação exata no 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

Você pode vê-lo vinculado às visualizações do MVC aqui, no MVC ViewPage:

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

A chave do comportamento gracioso do DynamicViewDataDictionary é a implementação do Dictionary no ViewDataDictionary, aqui:

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

Em outras palavras, ele sempre retorna um valor para todas as chaves, independentemente do que está nele - ele simplesmente retorna nulo quando não existe nada. Porém, o ViewDataDictionary tem o ônus de estar vinculado ao modelo do MVC, portanto, é melhor remover apenas as partes graciosas do dicionário para uso fora das visualizações do MVC.

É muito longo para realmente postar todos os detalhes aqui - a maioria implementando apenas o IDictionary - mas aqui está um objeto dinâmico (classe DDict) que não gera verificações nulas nas propriedades que não foram declaradas no Github:

https://github.com/b9chris/GracefulDynamicDictionary

Se você quiser apenas adicioná-lo ao seu projeto via NuGet, o nome dele é GracefulDynamicDictionary .

Chris Moschini
fonte
Por que você votou contra o DynamicDictionary, pois ele não usa reflexão então?
Softlion
então você pode votá-lo como este é a mesma solução :)
Softlion
3
Certamente não é a mesma solução.
22614 Chris Moschini
"você precisará criar uma subclasse semelhante que não seja lançada quando uma propriedade não for encontrada." => é! Oh não, não é. Minha solução é melhor. Joga - porque queremos, e também não pode jogar se o TryXX for usado;
Softlion
1
ISTO, ISTO é exatamente por que estou aqui, não consegui descobrir por que algum código (viewbag) NÃO estava quebrando. Obrigado.
precisa
11

Respondi a uma pergunta muito semelhante recentemente: como reflito sobre os membros do objeto dinâmico?

Em breve, ExpandoObject não é o único objeto dinâmico que você pode obter. O Reflection funcionaria para tipos estáticos (tipos que não implementam IDynamicMetaObjectProvider). Para tipos que implementam essa interface, a reflexão é basicamente inútil. Para ExpandoObject, você pode simplesmente verificar se a propriedade está definida como uma chave no dicionário subjacente. Para outras implementações, pode ser desafiador e, às vezes, a única maneira é trabalhar com exceções. Para detalhes, siga o link acima.

Alexandra Rusina
fonte
11

ATUALIZADO: você pode usar delegados e tentar obter um valor da propriedade do objeto dinâmico, se existir. Se não houver propriedade, simplesmente pegue a exceção e retorne false.

Dê uma olhada, ele funciona bem para mim:

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

A saída do código é a seguinte:

True
True
False
Alexander G
fonte
2
@marklam é ruim pegar todas as exceções quando você não sabe o que causa a exceção. Em nossos casos, está tudo bem, pois esperamos possivelmente ausência de um campo.
Alexander G
3
se você sabe o que causa a exceção, também deve conhecer seu tipo, então capture (WhateverException). Caso contrário, seu código continuará silenciosamente, mesmo que você tenha uma exceção inesperada - como OutOfMemoryException, por exemplo.
marklam
4
Você pode passar para qualquer getter IsPropertyExist. Neste exemplo, você sabe que é possível lançar um InvalidOperationException. Na prática, você não tem idéia de que exceção pode ser lançada. +1 para neutralizar o culto à carga.
Piedar
2
Esta solução é inaceitável se o desempenho for importante, por exemplo, se usado em um loop com mais de 500 iterações, ele adiciona e pode causar muitos segundos de atraso. Toda vez que uma exceção é capturada, a pilha deve ser copiada para o objeto de exceção
Jim109
1
Re: Desempenho: O depurador sendo anexado e Console.WriteLine são os bits lentos. 10.000 iterações aqui levam menos de 200 ms (com 2 exceções por iteração). O mesmo teste, sem exceções, leva alguns milissegundos. Isso significa que, se você espera que seu uso deste código raramente falte a uma propriedade, ou se você o chama um número limitado de vezes, ou pode armazenar em cache os resultados, saiba que tudo tem seu lugar e não é excessivo. avisos regurgitados aqui importam.
Caos
10

Eu queria criar um método de extensão para poder fazer algo como:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

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

... mas você não pode criar extensões de ExpandoObjectacordo com a documentação do C # 5 (mais informações aqui ).

Então acabei criando um auxiliar de classe:

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

Para usá-lo:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}
Maxime
fonte
4
voto positivo para comentários úteis e link para extensões do ExpandoObject.
22417 Roberto
1

Por que você não deseja usar o Reflection para obter um conjunto de tipos properyes? Como isso

 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
 } 
Vokinneberg
fonte
Não tenho certeza se isso retornará as propriedades adicionadas dinamicamente, meu palpite é que ele retorne os métodos do objeto Dynamic.
Dykam
1

Esse método de extensão verifica a existência de uma propriedade e, em seguida, retorna o valor ou nulo. Isso é útil se você não deseja que seus aplicativos gerem exceções desnecessárias, pelo menos as que você pode ajudar.

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

Se pode ser usado como tal:

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

ou se sua variável local for 'dinâmica', será necessário convertê-la no ExpandoObject primeiro.

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

Dependendo do seu caso de uso, se nulo puder ser considerado o mesmo que indefinido, você poderá transformar seu ExpandoObject em um 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"));

Resultado:

a is 1
b is 2.5
c is undefined
Wolfgang Grinfeld
fonte
-2
(authorDynamic as ExpandoObject).Any(pair => pair.Key == "YourProp");
Kasper Roma
fonte
-3

Ei, parem de usar o Reflection para tudo o que custa muitos ciclos de CPU.

Aqui está a solução:

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;
    }
}
Softlion
fonte
4
Isso mostra como implementar um objeto dinâmico, não como vê-lo como uma propriedade sai em um objeto dinâmico.
Matt Warren
Você pode verificar se uma instância dinâmica tem uma propriedade fazendo uma verificação nula na propriedade em questão.
Ctorx
2
"Isso mostra como implementar um objeto dinâmico": sim, de fato é. A solução para esta pergunta é: não existe uma solução genérica, pois depende da implementação.
Softlion
@Softlion Não, a solução é aquela coisa que nós temos de parar de usar
nik.shornikov
@ Softlion Qual é o sentido dos métodos Tryxxx? O TryGet nunca retornará false quando não encontrar a propriedade; portanto, você ainda precisará verificar o resultado. O retorno é inútil. No TrySet, se a chave não existir, lançará uma exceção em vez de retornar false. Não entendo por que você usaria isso como resposta, se você mesmo escreveu aqui nos comentários "A solução para esta pergunta é: não existe uma solução genérica, pois depende da implementação", isso também não é verdade. Veja a resposta da Dykam para a solução real.
Pqsk 27/05
-5

Tente este

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}
Venkat
fonte
3
Isso verificaria a existência de uma propriedade do objeto oculta sob o nome dinâmico, que é um detalhe de implementação. Você verificou sua solução em código real antes de postar? Não deve funcionar.
Softlion
Eu usei esse pedaço de código no cenário em tempo real. Isso funciona bem.
Venkat 30/01
6
Dá-me nulo o tempo todo, mesmo que a propriedade exista.
atlantis
Funcionaria com objetos básicos, mas não com ExpandoObjects. Dinâmica, não tenho certeza.
Joe
1
Para confirmar, isso também não funciona com dynamicobjetos (sempre retorna null).
Codificação Gone