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?


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
} 

2
Vou marcar este como a resposta como a sua sido tão longo, ele não parece ser a melhor resposta
roundcrisis


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.


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

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

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

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;

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.


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

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

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.


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 ...


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

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


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.