Teste se uma propriedade está disponível em uma variável dinâmica

225

Minha situação é muito simples. Em algum lugar do meu código, tenho o seguinte:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Então, basicamente, minha pergunta é como verificar (sem gerar uma exceção) se uma determinada propriedade está disponível na minha variável dinâmica. Eu poderia fazer, GetType()mas prefiro evitar isso, já que realmente não preciso saber o tipo do objeto. Tudo o que eu realmente quero saber é se uma propriedade (ou método, se isso facilita a vida) está disponível. Alguma dica?

roundcrisis
fonte
1
Há algumas sugestões aqui: stackoverflow.com/questions/2985161/… - mas nenhuma resposta aceita até o momento.
Andrew Anderson
obrigado, eu posso ver como fazer um abeto das soluções, tho eu queria saber se há algo que eu estou perdendo
roundcrisis

Respostas:

159

Eu acho que não há como descobrir se uma dynamicvariável tem um determinado membro sem tentar acessá-la, a menos que você reimplemente a maneira como a ligação dinâmica é tratada no compilador C #. O que provavelmente incluiria muita adivinhação, porque é definido pela implementação, de acordo com a especificação C #.

Portanto, você deve realmente tentar acessar o membro e capturar uma exceção, se falhar:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
svick
fonte
2
Vou marcar este como a resposta como a sua sido tão longo, ele não parece ser a melhor resposta
roundcrisis
8
Melhor solução - stackoverflow.com/questions/2839598/…
ministermason
20
@ministrymason Se você quer transmitir IDictionarye trabalhar com isso, isso só funciona ExpandoObject, não funcionará em nenhum outro dynamicobjeto.
svick 12/07/2012
5
RuntimeBinderExceptionestá no Microsoft.CSharp.RuntimeBinderespaço para nome.
21815 DavidRR
8
Eu ainda sinto que usar try / catch em vez de if / else é uma má prática em geral, independentemente das especificidades deste cenário.
Alexander Ryan Baggett
74

Pensei em fazer uma comparação da resposta de Martijn e resposta de svick ...

O programa a seguir retorna os seguintes resultados:

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

Como resultado, eu sugiro usar a reflexão. Ver abaixo.


Respondendo ao comentário de Bland:

As proporções são reflection:exceptionmarcações para 100000 iterações:

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

... justo o suficiente - se você espera que falhe com uma probabilidade inferior a ~ 1/47, procure a exceção.


O acima pressupõe que você esteja executando GetProperties()cada vez. Você pode acelerar o processo armazenando em cache o resultado de GetProperties()cada tipo em um dicionário ou similar. Isso pode ajudar se você estiver verificando o mesmo conjunto de tipos repetidamente.

dav_i
fonte
7
Eu concordo e gosto de refletir no meu trabalho, quando apropriado. Os ganhos obtidos com o Try / Catch são apenas quando a exceção é lançada. Então, o que alguém deveria perguntar antes de usar a reflexão aqui - é provável que seja de uma certa maneira? 90% ou 75% das vezes, seu código será aprovado? Então o Try / Catch ainda é o ideal. Se estiver no ar, ou houver muitas opções para uma, provavelmente, seu reflexo está no local.
branda
@bland Resposta editada.
dav_i
1
Obrigado, parece realmente completo agora.
branda
@dav_i não é justo comparar os dois, pois ambos se comportam de maneira diferente. A resposta de svick é mais completa.
Nawfal
1
@dav_i Não, eles não realizam a mesma função. A resposta de Martijn verifica se existe uma propriedade em um tipo de tempo de compilação regular em C #, declarado dinâmico (o que significa que ignora as verificações de segurança do tempo de compilação). Enquanto a resposta do svick verifica se existe uma propriedade em um objeto verdadeiramente dinâmico , ou seja, algo que implementa IIDynamicMetaObjectProvider. Entendo a motivação por trás da sua resposta e agradeço. É justo responder isso.
Nawfal
52

Talvez use reflexão?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Martijn
fonte
2
Citar a pergunta "Eu poderia fazer GetType (), mas eu prefiro evitar isso."
roundcrisis
Isso não tem as mesmas desvantagens da minha sugestão? RouteValueDictionary usa reflexão para obter propriedades .
Steve Wilkes
12
Você pode simplesmente fazer sem o Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i
Por favor, veja minha resposta para comparação de respostas.
Dav_i
3
Como um one-liner: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). A conversão de tipo é necessária para deixar o compilador satisfeito com o lambda.
MushinNoShin
38

Apenas no caso de ajudar alguém:

Se o método GetDataThatLooksVerySimilarButNotTheSame()retornar um, ExpandoObjectvocê também poderá converter para a IDictionaryantes de verificar.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
karask
fonte
3
Não sei por que essa resposta não tem mais votos, porque faz exatamente o que foi solicitado (sem exceção ou lance).
precisa
7
@ Wolfshead Esta resposta é ótima se você souber que seu objeto dinâmico é um ExpandoObject ou qualquer outra coisa que implemente IDictionary <string, objeto>, mas se acontecer de ser outra coisa, isso irá falhar.
Damian Powell
9

As duas soluções comuns para isso incluem fazer a chamada e capturar o RuntimeBinderException, usar reflexão para verificar a chamada ou serializar para um formato de texto e analisar a partir daí. O problema das exceções é que elas são muito lentas, porque quando uma é construída, a pilha de chamadas atual é serializada. Serializar para JSON ou algo análogo incorre em uma penalidade semelhante. Isso nos deixa com reflexão, mas só funciona se o objeto subjacente for realmente um POCO com membros reais. Se for um invólucro dinâmico em torno de um dicionário, um objeto COM ou um serviço da Web externo, a reflexão não ajudará.

Outra solução é usar o DynamicMetaObjectpara obter os nomes dos membros conforme o DLR os vê. No exemplo abaixo, eu uso uma classe estática ( Dynamic) para testar o Agecampo e exibi-lo.

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
fonte
Acontece que o Dynamiteypacote nuget já faz isso. ( Nuget.org/packages/Dynamitey )
Damian Powell
8

A resposta de Denis me fez pensar em outra solução usando JsonObjects,

um verificador de propriedade do cabeçalho:

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

ou talvez melhor:

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

por exemplo:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Charles HETIER
fonte
1
Existe uma chance de saber o que há de errado com esta resposta, por favor?
Charles HETIER
Não sei por que isso foi rejeitado, funcionou muito bem para mim. Mudei o Predicado para cada propriedade para uma classe auxiliar e chamei o método Invoke para retornar um bool de cada uma.
markp3rry
7

Bem, eu enfrentei um problema semelhante, mas em testes de unidade.

Usando o SharpTestsEx, você pode verificar se existe uma propriedade. Uso esse teste em meus controladores, porque, como o objeto JSON é dinâmico, alguém pode alterar o nome e esquecer de alterá-lo no javascript ou algo assim, portanto, testar todas as propriedades ao escrever o controlador deve aumentar minha segurança.

Exemplo:

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

Agora, usando SharTestsEx:

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

Usando isso, testo todas as propriedades existentes usando "Should (). NotThrow ()".

Provavelmente está fora de tópico, mas pode ser útil para alguém.

Diego Santin
fonte
Obrigado, muito útil. Usando SharpTestsEx, uso a seguinte linha para testar também o valor da propriedade dinâmica:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen
2

Seguindo a resposta de @karask, você pode agrupar a função como um auxiliar da seguinte maneira:

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

Para mim, isso funciona:

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;
    }
}
Jester
fonte
nullnão significa que a propriedade não existe
quetzalcoatl
Eu sei, mas se ele é nulo Eu não preciso fazer nada com o valor, portanto, para o meu usecase é ok
Jester
0

Se você controlar o tipo que está sendo usado como dinâmico, não poderá retornar uma tupla em vez de um valor para cada acesso à propriedade? Algo como...

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

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

Possivelmente uma implementação ingênua, mas se você construir uma delas internamente a cada vez e retornar isso em vez do valor real, poderá verificar Existstodos os acessos à propriedade e, em seguida, clicar Valuese isso ocorrer com o valor sendo default(T)(e irrelevante) se isso não acontecer.

Dito isto, posso estar perdendo algum conhecimento sobre como a dinâmica funciona e isso pode não ser uma sugestão viável.

Shibumi
fonte
0

No meu caso, eu precisava verificar a existência de um método com um nome específico, então usei uma interface para isso

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

Além disso, as interfaces podem conter mais do que apenas métodos:

As interfaces podem conter métodos, propriedades, eventos, indexadores ou qualquer combinação desses quatro tipos de membros.

De: Interfaces (Guia de Programação em C #)

Elegante e sem necessidade de capturar exceções ou brincar com reflexões ...

Fred Mauroy
fonte
0

Eu sei que esse post é realmente antigo, mas aqui está uma solução simples para trabalhar com o dynamictipo c#.

  1. pode usar reflexão simples para enumerar propriedades diretas
  2. ou pode usar o objectmétodo de extensão
  3. ou use o GetAsOrDefault<int>método para obter um novo objeto fortemente tipado com valor, se existir, ou o padrão, se não existir.
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 );
    }
}
SimperT
fonte
0

Como ExpandoObjectherda, IDictionary<string, object>você pode usar a seguinte verificação

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Você pode criar um método utilitário para executar essa verificação, que tornará o código muito mais limpo e reutilizável

Mukesh Bhojwani
fonte
-1

Aqui está o outro caminho:

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
fonte
2
de onde você tirou a ideia de que a pergunta é sobre o teste das propriedades do JObject? Sua resposta é limitada a objetos / classes que expõem IEnumerable sobre suas propriedades. Não garantido por dynamic. dynamicpalavra-chave é um assunto muito mais amplo. Verificação Go se você pode testar para Countem dynamic foo = new List<int>{ 1,2,3,4 }como essa
quetzalcoatl