Como acessar a propriedade do tipo anônimo em c #?


125

Eu tenho isto:

List<object> nodes = new List<object>(); 

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});

... e estou pensando se posso pegar a propriedade "Verificado" do objeto anônimo. Não tenho certeza se isso é possível. Tentei fazer isso:

if (nodes.Any(n => n["Checked"] == false)) ... mas não funciona.

obrigado

Respostas:


63

Se você deseja uma lista fortemente tipada de tipos anônimos, também precisará tornar a lista um tipo anônimo. A maneira mais fácil de fazer isso é projetar uma sequência como uma matriz em uma lista, por exemplo,

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

Então você poderá acessá-lo como:

nodes.Any(n => n.Checked);

Por causa da maneira como o compilador funciona, o seguinte também deve funcionar depois que você criar a lista, porque os tipos anônimos têm a mesma estrutura e também o mesmo tipo. Eu não tenho um compilador à mão para verificar isso.

nodes.Add(new { Checked = false, /* etc */ });

263

Se você estiver armazenando o objeto como tipo object, precisará usar a reflexão. Isso vale para qualquer tipo de objeto, anônimo ou não. Em um objeto o, você pode obter seu tipo:

Type t = o.GetType();

Então, a partir disso, você pesquisa uma propriedade:

PropertyInfo p = t.GetProperty("Foo");

Depois disso, você pode obter um valor:

object v = p.GetValue(o, null);

Esta resposta está muito atrasada para uma atualização para C # 4:

dynamic d = o;
object v = d.Foo;

E agora outra alternativa no C # 6:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

Observe que, ao usar, ?.fazemos com que o resultado vocorra nullem três situações diferentes!

  1. oé null, então não há nenhum objeto
  2. oé não, nullmas não tem uma propriedadeFoo
  3. otem uma propriedade, Foomas seu valor real é null.

Portanto, isso não é equivalente aos exemplos anteriores, mas pode fazer sentido se você quiser tratar os três casos da mesma forma.


4
Nunca usei uma dinâmica antes até agora, boa atualização para o .NET 4.0 #
Alan Alan

na solução c # 4, você receberá uma exceção de tempo de execução se a propriedade não existir ( object v = d.Foo), enquanto GetValue(o, null)será nulo se não existir.
usar o seguinte código

1
Não, GetPropertyretornará nulle GetValuelançará se for aprovado null, portanto o efeito geral é uma exceção. A versão do C # 4.0 oferece uma exceção mais descritiva.
Daniel Earwicker

4
Se você estiver usando dinâmico em uma montagem diferente da origem, precisará usar [InternalsVisibleTo] #
Sarath

2
@DanielEarwicker obrigado pela conclusão. Também se aplica a tipos anônimos. Como todas as propriedades geradas para tipos anônimos são internas.
Sarath

13

Você pode iterar sobre as propriedades do tipo anônimo usando Reflexão; veja se existe uma propriedade "Marcada" e, se houver, obtenha seu valor.

Consulte esta postagem do blog: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Então, algo como:

foreach(object o in nodes)
{
    Type t = o.GetType();

    PropertyInfo[] pi = t.GetProperties(); 

    foreach (PropertyInfo p in pi)
    {
        if (p.Name=="Checked" && !(bool)p.GetValue(o))
            Console.WriteLine("awesome!");
    }
}

6
Se você precisa apenas de uma propriedade e já sabe o nome dela, não faz sentido passar por todas elas; basta usar GetProperty e GetValue. Além disso, System.out.println é Java, C # não ...
Chris Charabaruk

Opa, assim é, Chris! Um pouco embaraçoso ... resolvido agora.
glennkentwell

6

A resposta aceita descreve corretamente como a lista deve ser declarada e é altamente recomendada para a maioria dos cenários.

Mas me deparei com um cenário diferente, que também cobre a pergunta. E se você precisar usar uma lista de objetos existente, como ViewData["htmlAttributes"]no MVC ? Como você pode acessar suas propriedades (geralmente são criadas via new { @style="width: 100px", ... })?

Para esse cenário um pouco diferente, quero compartilhar com você o que descobri. Nas soluções abaixo, estou assumindo a seguinte declaração para nodes:

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

1. Solução com dinâmica

No C # 4.0 e versões superiores , você pode simplesmente converter para dinâmico e escrever:

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found not checked element!");

Nota: Isso está usando ligação tardia, o que significa que ele reconhecerá apenas em tempo de execução se o objeto não tiver uma Checkedpropriedade e dispara um RuntimeBinderExceptionnesse caso - portanto, se você tentar usar uma Checked2propriedade não existente , receberá a seguinte mensagem em tempo de execução: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Solução com reflexão

A solução com reflexão funciona com versões antigas e novas do compilador C # . Para versões antigas do C #, considere a dica no final desta resposta.

fundo

Como ponto de partida, encontrei uma boa resposta aqui . A idéia é converter o tipo de dados anônimos em um dicionário usando reflexão. O dicionário facilita o acesso às propriedades, pois seus nomes são armazenados como chaves (você pode acessá-las como myDict["myProperty"]).

Inspirado pelo código no link acima, eu criei uma classe de extensão fornecendo GetProp, UnanonymizePropertiese UnanonymizeListItemscomo métodos de extensão, que o acesso simplificar a propriedades anónimos. Com essa classe, você pode simplesmente fazer a consulta da seguinte maneira:

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

ou você pode usar a expressão nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()como ifcondição, que filtra implicitamente e depois verifica se há algum elemento retornado.

Para obter o primeiro objeto que contém a propriedade "Marcado" e retornar sua propriedade "profundidade", você pode usar:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

ou mais curto: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Nota: Se você possui uma lista de objetos que não contêm necessariamente todas as propriedades (por exemplo, alguns não contêm a propriedade "Verificado") e ainda deseja criar uma consulta com base nos valores "Verificados", é possível faça isso:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

Isso impede que isso KeyNotFoundExceptionocorra se a propriedade "Marcado" não existir.


A classe abaixo contém os seguintes métodos de extensão:

  • UnanonymizeProperties: É usado para cancelar o anonimato das propriedades contidas em um objeto. Este método usa reflexão. Ele converte o objeto em um dicionário que contém as propriedades e seus valores.
  • UnanonymizeListItems: É usado para converter uma lista de objetos em uma lista de dicionários que contêm as propriedades. Opcionalmente, pode conter uma expressão lambda para filtrar previamente.
  • GetProp: É usado para retornar um valor único que corresponde ao nome da propriedade. Permite tratar propriedades não existentes como valores nulos (true) e não como KeyNotFoundException (false)

Para os exemplos acima, tudo o que é necessário é adicionar a classe de extensão abaixo:

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object obj)
    {
        Type type = obj?.GetType();
        var properties = type?.GetProperties()
               ?.Select(n => n.Name)
               ?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj, null));
        return properties;
    }

    // converts object list into list of properties that meet the filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<IDictionary>();
        foreach (object obj in objectList)
        {
            var props = obj.UnanonymizeProperties();
            if (filterCriteria == default
               || filterCriteria((IDictionary<string, object>)props) == true)
            { accessibleList.Add(props); }
        }
        return accessibleList;
    }

    // returns specific property, i.e. obj.GetProp(propertyName)
    // requires prior usage of AccessListItems and selection of one element, because
    // object needs to be a IDictionary<string, object>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

Dica: O código acima está usando os nulos-condicional operadores, disponível desde C # versão 6.0 - Se você está trabalhando com compiladores mais antigos C # (por exemplo, C # 3.0), simplesmente substituir ?.por .e ?[por [toda a parte, por exemplo,

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

Se você não for forçado a usar um compilador C # mais antigo, mantenha-o como está, pois o uso de condições nulas facilita muito o tratamento nulo.

Nota: Como a outra solução com dinâmico, esta solução também está usando ligação tardia, mas neste caso você não está recebendo uma exceção - ele simplesmente não encontrará o elemento se estiver se referindo a uma propriedade não existente, desde enquanto você mantém os operadores condicionais nulos .

O que pode ser útil para algumas aplicações é que a propriedade é referida por meio de uma string na solução 2, portanto, pode ser parametrizada.


1

Recentemente, tive o mesmo problema no .NET 3.5 (nenhuma dinâmica disponível). Aqui está como eu resolvi:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Adaptado de algum lugar no stackoverflow:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

Agora retorne o objeto via cast:

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

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