Jak uzyskać dostęp do właściwości typu anonimowego w C #?

125

Mam to:

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

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

... i zastanawiam się, czy mogę wtedy pobrać właściwość „Checked” anonimowego obiektu. Nie jestem pewien, czy jest to w ogóle możliwe. Próbowałem to zrobić:

if (nodes.Any(n => n["Checked"] == false)) ... ale to nie działa.

Dzięki

wgpubs
źródło

Odpowiedzi:

63

Jeśli chcesz mieć silnie wpisaną listę typów anonimowych, musisz również uczynić ją typem anonimowym. Najłatwiej to zrobić, rzutując sekwencję, taką jak tablica, na listę, np

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

Wtedy będziesz mógł uzyskać do niego dostęp na przykład:

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

Ze względu na sposób działania kompilatora poniższe czynności powinny również działać po utworzeniu listy, ponieważ typy anonimowe mają tę samą strukturę, więc są również tego samego typu. Nie mam jednak pod ręką kompilatora, który mógłby to zweryfikować.

nodes.Add(new { Checked = false, /* etc */ });
Greg Beech
źródło
263

Jeśli przechowujesz obiekt jako typ object, musisz użyć odbicia. Dotyczy to każdego typu obiektu, anonimowego lub innego. Na obiekcie o możesz pobrać jego typ:

Type t = o.GetType();

Następnie wyszukujesz nieruchomość:

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

Następnie możesz uzyskać wartość:

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

Ta odpowiedź jest od dawna oczekiwana na aktualizację dla C # 4:

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

A teraz kolejna alternatywa w C # 6:

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

Zauważ, że używając ?., sprawiamy, że wynik vznajduje się nullw trzech różnych sytuacjach!

  1. ojest null, więc nie ma żadnego obiektu
  2. onie jest, nullale nie ma właściwościFoo
  3. oma własność, Fooale tak się właśnie dzieje null.

Nie jest to więc odpowiednik wcześniejszych przykładów, ale może mieć sens, jeśli chcesz traktować wszystkie trzy przypadki w ten sam sposób.

Daniel Earwicker
źródło
4
Nigdy wcześniej nie korzystałem z dynamiki, niezła aktualizacja .NET 4.0
Alan,
w rozwiązaniu C # 4 otrzymasz wyjątek czasu wykonywania, jeśli właściwość nie istnieje ( object v = d.Foo), natomiast GetValue(o, null)będzie miała wartość null, jeśli nie istnieje.
YaakovHatam
1
Nie, GetPropertyzwróci nulli GetValuewyrzuci, jeśli zostanie przekazany null, więc ogólny efekt jest wyjątkiem. Wersja C # 4.0 zawiera bardziej opisowy wyjątek.
Daniel Earwicker,
4
Jeśli używasz dynamiki w innym zestawie niż źródło, musisz użyć [InternalsVisibleTo]
Sarath
2
@DanielEarwicker dzięki za ukończenie. Dotyczy to również typów anonimowych. Ponieważ wszystkie właściwości wygenerowane dla typów anonimowych są wewnętrzne.
Sarath
13

Możesz iterować po właściwościach typu anonimowego używając Reflection; sprawdź, czy istnieje właściwość „Zaznaczona”, a jeśli tak, pobierz jej wartość.

Zobacz ten post na blogu: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Więc coś takiego:

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!");
    }
}
glennkentwell
źródło
6
Jeśli potrzebujesz tylko jednej nieruchomości i znasz już jej nazwę, nie ma sensu przeglądać ich wszystkich; po prostu użyj GetProperty i GetValue. Ponadto System.out.println to Java, a nie C # ...
Chris Charabaruk,
Ups, tak jest, Chris! Trochę zawstydzające ... naprawione teraz.
glennkentwell
6

Przyjęta odpowiedź poprawnie opisuje sposób deklaracji listy i jest wysoce zalecana dla większości scenariuszy.

Ale trafiłem na inny scenariusz, który obejmuje również zadane pytanie. Co jeśli musisz użyć istniejącej listy obiektów, jak ViewData["htmlAttributes"]w MVC ? Jak uzyskać dostęp do jego właściwości (są one zwykle tworzone przez new { @style="width: 100px", ... })?

W przypadku tego nieco innego scenariusza chcę się z wami podzielić tym, czego się dowiedziałem. W poniższych rozwiązaniach przyjmuję następującą deklarację dla nodes:

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

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

1. Rozwiązanie z dynamiką

W C # 4.0 i wyższych wersjach możesz po prostu rzutować na dynamiczny i pisać:

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

Uwaga: to używa późnego wiązania, co oznacza, że ​​będzie rozpoznawać tylko w czasie wykonywania, jeśli obiekt nie ma Checkedwłaściwości i zgłasza RuntimeBinderExceptionw tym przypadku - więc jeśli spróbujesz użyć nieistniejącej Checked2właściwości, otrzymasz następujący komunikat o Czas trwania: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Rozwiązanie z refleksją

Rozwiązanie z odbiciem działa zarówno ze starymi, jak i nowymi wersjami kompilatora C # . W przypadku starszych wersji języka C # należy zapoznać się ze wskazówką na końcu tej odpowiedzi.

tło

Jako punkt wyjścia, znalazłem dobrej odpowiedzi tutaj . Pomysł polega na przekonwertowaniu anonimowego typu danych na słownik przy użyciu odbicia. Słownik ułatwia dostęp do właściwości, ponieważ ich nazwy są przechowywane jako klucze (można uzyskać do nich dostęp np myDict["myProperty"].).

Zainspirowany kodu w linku powyżej, stworzyłem klasę wewnętrzny zapewniający GetProp, UnanonymizePropertiesa UnanonymizeListItemsjako metod rozszerzenie, które upraszczają dostęp do właściwości anonimowych. Dzięki tej klasie możesz po prostu wykonać zapytanie w następujący sposób:

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

lub możesz użyć wyrażenia nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()jako ifwarunku, który filtruje niejawnie, a następnie sprawdza, czy zostały zwrócone jakieś elementy.

Aby pobrać pierwszy obiekt zawierający właściwość „Checked” i zwrócić jego właściwość „depth”, możesz użyć:

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

lub krócej: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Uwaga: jeśli masz listę obiektów, które niekoniecznie zawierają wszystkie właściwości (na przykład niektóre nie zawierają właściwości „Zaznaczone”) i nadal chcesz utworzyć zapytanie w oparciu o wartości „Zaznaczone”, możesz Zrób to:

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

Zapobiega to KeyNotFoundExceptionwystępowaniu a, jeśli właściwość „Checked” nie istnieje.


Poniższa klasa zawiera następujące metody rozszerzające:

  • UnanonymizeProperties: Służy do usuwania anonimowości właściwości zawartych w obiekcie. Ta metoda wykorzystuje odbicie. Konwertuje obiekt na słownik zawierający właściwości i ich wartości.
  • UnanonymizeListItems: Służy do konwersji listy obiektów na listę słowników zawierających właściwości. Może opcjonalnie zawierać wyrażenie lambda do wcześniejszego filtrowania .
  • GetProp: Służy do zwracania pojedynczej wartości pasującej do podanej nazwy właściwości. Umożliwia traktowanie nieistniejących właściwości jako wartości null (true), a nie jako KeyNotFoundException (false)

W przypadku powyższych przykładów wystarczy dodać poniższą klasę rozszerzenia:

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

Podpowiedź: powyższy kod używa operatorów warunkowych zerowych , dostępnych od wersji C # 6.0 - jeśli pracujesz ze starszymi kompilatorami C # (np. C # 3.0), po prostu zamień ?.na .i ?[przez [wszędzie, np.

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

Jeśli nie zmuszony do korzystania ze starszej kompilatora C #, aby go jak jest, bo przy użyciu null warunkowe sprawia NULL obsługi znacznie łatwiejsze.

Uwaga: Podobnie jak inne rozwiązanie z dynamiką, to rozwiązanie również używa późnego wiązania, ale w tym przypadku nie otrzymujesz wyjątku - po prostu nie znajdzie elementu, jeśli odnosisz się do nieistniejącej właściwości, o ile zachowując zerowe operatory warunkowe .

W przypadku niektórych aplikacji przydatne może być odwołanie się do właściwości za pośrednictwem ciągu znaków w rozwiązaniu 2, dlatego można ją sparametryzować.

Matt
źródło
1

Ostatnio miałem ten sam problem w .NET 3.5 (brak dynamicznej wersji). Oto jak rozwiązałem:

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

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

Zaadaptowano gdzieś na stackoverflow:

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

Teraz odzyskaj obiekt przez rzut:

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}
orowoc
źródło