Jaki jest najlepszy sposób zrzucania całych obiektów do dziennika w języku C #?

132

Więc jeśli chodzi o przeglądanie stanu bieżącego obiektu w czasie wykonywania, bardzo podoba mi się to, co daje mi okno Visual Studio Immediate. Po prostu robię proste

? objectname

Da mi ładnie sformatowany „zrzut” obiektu.

Czy istnieje łatwy sposób na zrobienie tego w kodzie, abym mógł zrobić coś podobnego podczas logowania?

Dan Esparza
źródło
W końcu całkiem sporo używałem T.Dump. To całkiem solidne rozwiązanie - wystarczy uważać na rekursję.
Dan Esparza
To stare pytanie, które pojawia się na szczycie wielu wyników wyszukiwania. Dla przyszłych czytelników: zobacz to porównanie z rozszerzeniem . Świetnie działał dla mnie w VS2015.
Jesse Good
1
Aktualizacja na rok 2020, ponieważ ta wtyczka VS nie jest obsługiwana i brakuje jej niektórych funkcji. Następująca biblioteka robi to samo w kodzie - i ma kilka dodatkowych funkcji, np. Śledzi, gdzie została już odwiedzona, aby uniknąć pętli: github.com/thomasgalliker/ObjectDumper
Nick Westgate

Odpowiedzi:

55

Możesz oprzeć coś na kodzie ObjectDumper, który jest dostarczany z próbkami Linq .
Spójrz również na odpowiedź na to powiązane pytanie, aby pobrać próbkę.

Mike Scott
źródło
5
Nie działa również dla tablic (wyświetla tylko typ i długość tablicy, ale nie drukuje jej zawartości).
Konrad Morawski
5
Pakiet nuget dla ObjectDumper jest teraz dostępny. Zapewnia również metodę rozszerzenia DumpToStringi Dumpdo Objectklasy. Poręczny.
IsmailS
2
w3wp.exeulega awarii, gdy próbuję użyć ObjectDumperRequest.DumpToString("aaa");
Paul
63

W przypadku większego grafu obiektowego popieram użycie Json, ale z nieco inną strategią. Najpierw mam statyczną klasę, która jest łatwa do wywołania i ze statyczną metodą, która otacza konwersję Json (uwaga: może to uczynić metodę rozszerzającą).

using Newtonsoft.Json;

public static class F
{
    public static string Dump(object obj)
    {
        return JsonConvert.SerializeObject(obj);
    }
}

Następnie w twoim Immediate Window,

var lookHere = F.Dump(myobj);

look Tutaj pojawi się automatycznie w Localsoknie poprzedzonym znakiem $ lub możesz dodać do niego zegarek. Po prawej stronie Valuekolumny w inspektorze znajduje się szkło powiększające z rozwijaną kropką obok. Wybierz rozwijany kursor i wybierz wizualizator Json.

Zrzut ekranu okna Visual Studio 2013 Locals

Używam Visual Studio 2013.

Jason
źródło
2
SerializeObj -> SerializeObject?
Wiseman
Świetnie, dziękuję. Nie mogę zainstalować narzędzi debugowania dla programu Visual Studio na moim serwerze zdalnym, a ta rzecz działa bardzo dobrze w mojej aplikacji mvc asp.net.
Liam Kernighan
1
Aby uzyskać ładne formatowanie, możesz zrobić:Newtonsoft.Json.JsonConvert.SerializeObject(sampleData, Formatting.Indented)
Zorgarath
o wiele łatwiejsze niż zrobienie tego ręcznie. To się komplikuje
ahong
W programach .NET Core 3.1 i .NET 5+ można również używać gotowego interfejsu API System.Text.Json.JsonSerializer.Serialize(yourObject).
Vulcan Raven
26

Jestem pewien, że istnieją lepsze sposoby na zrobienie tego, ale w przeszłości korzystałem z metody podobnej do poniższej, aby serializować obiekt w ciąg, który mogę rejestrować:

  private string ObjectToXml(object output)
  {
     string objectAsXmlString;

     System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(output.GetType());
     using (System.IO.StringWriter sw = new System.IO.StringWriter())
     {
        try
        {
           xs.Serialize(sw, output);
           objectAsXmlString = sw.ToString();
        }
        catch (Exception ex)
        {
           objectAsXmlString = ex.ToString();
        }
     }

     return objectAsXmlString;
  }

Zobaczysz, że metoda może również zwrócić wyjątek zamiast zserializowanego obiektu, więc będziesz chciał upewnić się, że obiekty, które chcesz rejestrować, są serializowane.

Bernhard Hofmann
źródło
2
W świetle funkcji dodanych do C # po udzieleniu odpowiedzi na pytanie, przydatne może być wskazanie, że ta implementacja działa dobrze jako metoda rozszerzenia. W przypadku zastosowania do klasy Object i odwoływania się do rozszerzenia wszędzie tam, gdzie jest to potrzebne, może to być wygodny sposób wywołania funkcji.
Nikita G.
Wciąż otrzymuję z tego: Failed to access type 'System.__ComObject' failed. Noob do c #, byłby wdzięczny za pomoc.
GuySoft
1
@GuySoft Podejrzewam, że jedna z właściwości twojego obiektu lub sam obiekt nie jest możliwy do serializacji.
Bernhard Hofmann
Nie można niestety użyć tej metody na klasach bez konstruktora bez parametrów. Nie można ich serializować.
Jarekczek
23

Możesz użyć programu Visual Studio Immediate Window

Po prostu wklej to ( actualoczywiście zmień na nazwę obiektu):

Newtonsoft.Json.JsonConvert.SerializeObject(actual);

Powinien wydrukować obiekt w formacie JSON wprowadź opis obrazu tutaj

Powinieneś być w stanie skopiować go za pomocą narzędzia tekstowego mechanicznego lub notatnika ++ i zamienić cudzysłowy ( \") na "i newlines ( \r\n) na puste miejsce, a następnie usunąć podwójne cudzysłowy ( ") z początku i końca i wkleić go do jsbeautifier, aby był bardziej czytelny.

UPDATE do komentarza OP

public static class Dumper
{
    public static void Dump(this object obj)
    {
        Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(obj)); // your logger
    }
}

powinno to pozwolić na zrzucenie dowolnego obiektu.

Mam nadzieję, że zaoszczędzi ci to trochę czasu.

Matas Vaitkevicius
źródło
Dzięki. Być może nie złapałeś tego w moim pierwotnym pytaniu, ale wskazałem, że już wiem o bezpośrednim oknie i chciałem zrobić to samo podczas logowania do mojej aplikacji.
Dan Esparza
@DanEsparza Console.Log(Newtonsoft.Json.JsonConvert.SerializeObject(actual));? :) i tak, rzeczywiście tęskniłem. To pytanie pojawia się podczas wyszukiwania google.co.uk/…
Matas Vaitkevicius
2
Do Twojej wiadomości, gdy masz ciąg JSON w ciągu C #, kliknij ikonę lunety po prawej stronie ciągu i wybierz wizualizator tekstu. Spowoduje to wyświetlenie okna, które pokazuje zwykłą wersję tekstową ciągu JSON (bez cudzysłowów lub \ r \ n).
Walter
19

ServiceStack.Text ma metodę rozszerzenia T.Dump (), która robi dokładnie to, rekurencyjnie zrzuca wszystkie właściwości dowolnego typu w ładnym, czytelnym formacie.

Przykładowe użycie:

var model = new TestModel();
Console.WriteLine(model.Dump());

i wyjście:

{
    Int: 1,
    String: One,
    DateTime: 2010-04-11,
    Guid: c050437f6fcd46be9b2d0806a0860b3e,
    EmptyIntList: [],
    IntList:
    [
        1,
        2,
        3
    ],
    StringList:
    [
        one,
        two,
        three
    ],
    StringIntMap:
    {
        a: 1,
        b: 2,
        c: 3
    }
}
mit
źródło
1
Nie działa na polach. PO wyraźnie pytał o „całe obiekty”.
Konrad Morawski
5
He didn't say fields- powiedział entire objects, co obejmuje pola. Wspomniał również o funkcji Immediate Window w Visual Studio jako przykład tego, co chciał osiągnąć ( „Wykonanie prostego polecenia ? objectnameda mi ładnie sformatowany zrzut obiektu” ). ? objectnamewypisuje również wszystkie pola. This has been immensely helpful - one of my most used extension methods to date- Nie kwestionuję, że jest przydatny, tylko że zrzuca całe obiekty.
Konrad Morawski
3
@KonradMorawski Błędne całe obiekty oznaczają rekurencyjny zrzut obiektu, a NIE to, że zawiera on pola, co może łatwo doprowadzić do nieskończonej pętli rekurencyjnej. Nie powinieneś zakładać tego, co sugerują inni. Moja odpowiedź jest zarówno istotna, jak i pomocna, a Twój głos negatywny + komentarz nie.
mythz
1
@mythz tak, oczywiście, trzeba zapobiegać przepełnieniu stosu (np. każde Int32pole ma MaxValuepole, które jest Int32samo w sobie ...), to dobra uwaga, ale nie zmienia to faktu, że obiekty - a na pewno całe - składają się również z pól, a nie tylko z właściwości. Co więcej (nie odniósł się, że jeden), ? objectnamew Immediate Window robi pól wyświetlacza - bez wyzwalania nieskończoną pętlę. Jeśli chodzi o mój głos przeciw, mogę go wycofać (to znaczy, jeśli pozwolisz mi go odblokować). W zasadzie i tak się z tym nie zgadzam.
Konrad Morawski
4
-1 w celu uzyskania odpowiedzi obejmującej tylko łącze, chociaż wygląda świetnie, gdybym mógł jej użyć! Może jestem ślepy, ale nie mogę znaleźć źródła przez ten link; dwa foldery przesyłania są puste. Czy kod nie jest zbyt długi, aby uwzględnić go w odpowiedzi?
14

Oto głupio prosty sposób na napisanie płaskiego obiektu, ładnie sformatowanego:

using Newtonsoft.Json.Linq;

Debug.WriteLine("The object is " + JObject.FromObject(theObjectToDump).ToString());

Chodzi o to, że obiekt jest najpierw konwertowany na wewnętrzną reprezentację JSON przez JObject.FromObject, a następnie konwertowany na ciąg JSON przez ToString. (I oczywiście ciąg JSON jest bardzo ładną reprezentacją prostego obiektu, zwłaszcza że ToStringbędzie zawierał znaki nowej linii i wcięcia.) „ToString” jest oczywiście nieistotne (jak to wynika z użycia +do łączenia ciągu i obiektu), ale Lubię to tutaj określać.

Hot Licks
źródło
5
JsonConvert.SerializeObject (docenienie, Formatting.Indented) do wygodnego czytania w dzienniku
Tertium
1
HotLicks - chcę Ci przekazać, jak ważny jest dla mnie ten wkład w tej chwili. Mam obowiązek przedstawić audyt tego, co zmieniło się podczas aktualizacji i właśnie przeniosłeś mój stres z poziomu „paniki” z powrotem do poziomu „zmartwień”, który można opanować. Dziękuję panu, oby miał pan bardzo długie i błogosławione życie
Iofacture
4

Możesz użyć odbicia i zapętlić wszystkie właściwości obiektu, a następnie pobrać ich wartości i zapisać je w dzienniku. Formatowanie jest naprawdę trywialne (możesz użyć \ t do wcięcia właściwości obiektu i jego wartości):

MyObject
    Property1 = value
    Property2 = value2
    OtherObject
       OtherProperty = value ...
Ricardo Villamil
źródło
4

Lubię zastępować ToString (), aby uzyskać bardziej przydatne dane wyjściowe poza nazwą typu. Jest to przydatne w debugerze, możesz zobaczyć potrzebne informacje o obiekcie bez konieczności jego rozwijania.

Darryl Braaten
źródło
3

Znalazłem bibliotekę o nazwie ObjectPrinter, która pozwala na łatwe zrzucanie obiektów i kolekcji do łańcuchów (i nie tylko). Robi dokładnie to, czego potrzebowałem.

Marek Dzikiewicz
źródło
3

Poniżej znajduje się kolejna wersja, która robi to samo (i obsługuje zagnieżdżone właściwości), co moim zdaniem jest prostsze (brak zależności od bibliotek zewnętrznych i można je łatwo zmodyfikować, aby robić inne rzeczy niż rejestrowanie):

public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel = 0)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().IsPrimitive)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj))
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");
                DumpObject(value, nestingLevel + 1);
            }
        }
    }

    bool ImplementsDictionary(Type t)
    {
        return t.GetInterfaces().Any(i => i.Name.Contains("IDictionary"));
    }
}
engineforce
źródło
1
to umrze strasznie, jeśli masz Datewłasność w swoim wewnętrznym obiekcie ... po prostu mówiąc ...
Noctis
2

Możesz napisać własną metodę WriteLine-

public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var props = t.GetProperties();
        StringBuilder sb = new StringBuilder();
        foreach (var item in props)
        {
            sb.Append($"{item.Name}:{item.GetValue(obj,null)}; ");
        }
        sb.AppendLine();
        Console.WriteLine(sb.ToString());
    }

Użyj tego jak-

WriteLine(myObject);

Aby napisać kolekcję, możemy użyć-

 var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }   

Metoda może wyglądać następująco:

 public static void WriteLine<T>(T obj)
    {
        var t = typeof(T);
        var ifaces = t.GetInterfaces();
        if (ifaces.Any(o => o.Name.StartsWith("ICollection")))
        {

            dynamic lst = t.GetMethod("GetEnumerator").Invoke(obj, null);
            while (lst.MoveNext())
            {
                WriteLine(lst.Current);
            }
        }            
        else if (t.GetProperties().Any())
        {
            var props = t.GetProperties();
            StringBuilder sb = new StringBuilder();
            foreach (var item in props)
            {
                sb.Append($"{item.Name}:{item.GetValue(obj, null)}; ");
            }
            sb.AppendLine();
            Console.WriteLine(sb.ToString());
        }
    }

Używając if, else ifi sprawdzając interfejsy, atrybuty, typ bazowy itp. Oraz rekurencję (ponieważ jest to metoda rekurencyjna) w ten sposób możemy uzyskać zrzut obiektu, ale na pewno jest to uciążliwe. Korzystanie z obiektu zrzutu z LINQ Sample firmy Microsoft pozwoliłoby zaoszczędzić czas.

Wspaniały islam
źródło
Z ciekawości: jak to obsługuje tablice lub listy? Lub właściwości, które odwołują się do obiektów macierzystych?
Dan Esparza
@DanEsparza Dziękuję, że pokazałeś mi drogę do bycia bardziej szczegółowym.
Ariful Islam
2

Na podstawie odpowiedzi @engineforce utworzyłem tę klasę, której używam w projekcie PCL rozwiązania Xamarin:

/// <summary>
/// Based on: https://stackoverflow.com/a/42264037/6155481
/// </summary>
public class ObjectDumper
{
    public static string Dump(object obj)
    {
        return new ObjectDumper().DumpObject(obj);
    }

    StringBuilder _dumpBuilder = new StringBuilder();

    string DumpObject(object obj)
    {
        DumpObject(obj, 0);
        return _dumpBuilder.ToString();
    }

    void DumpObject(object obj, int nestingLevel)
    {
        var nestingSpaces = "".PadLeft(nestingLevel * 4);

        if (obj == null)
        {
            _dumpBuilder.AppendFormat("{0}null\n", nestingSpaces);
        }
        else if (obj is string || obj.GetType().GetTypeInfo().IsPrimitive || obj.GetType().GetTypeInfo().IsEnum)
        {
            _dumpBuilder.AppendFormat("{0}{1}\n", nestingSpaces, obj);
        }
        else if (ImplementsDictionary(obj.GetType()))
        {
            using (var e = ((dynamic)obj).GetEnumerator())
            {
                var enumerator = (IEnumerator)e;
                while (enumerator.MoveNext())
                {
                    dynamic p = enumerator.Current;

                    var key = p.Key;
                    var value = p.Value;
                    _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, key, value != null ? value.GetType().ToString() : "<null>");
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
        else if (obj is IEnumerable)
        {
            foreach (dynamic p in obj as IEnumerable)
            {
                DumpObject(p, nestingLevel);
            }
        }
        else
        {
            foreach (PropertyInfo descriptor in obj.GetType().GetRuntimeProperties())
            {
                string name = descriptor.Name;
                object value = descriptor.GetValue(obj);

                _dumpBuilder.AppendFormat("{0}{1} ({2})\n", nestingSpaces, name, value != null ? value.GetType().ToString() : "<null>");

                // TODO: Prevent recursion due to circular reference
                if (name == "Self" && HasBaseType(obj.GetType(), "NSObject"))
                {
                    // In ObjC I need to break the recursion when I find the Self property
                    // otherwise it will be an infinite recursion
                    Console.WriteLine($"Found Self! {obj.GetType()}");
                }
                else
                {
                    DumpObject(value, nestingLevel + 1);
                }
            }
        }
    }

    bool HasBaseType(Type type, string baseTypeName)
    {
        if (type == null) return false;

        string typeName = type.Name;

        if (baseTypeName == typeName) return true;

        return HasBaseType(type.GetTypeInfo().BaseType, baseTypeName);
    }

    bool ImplementsDictionary(Type t)
    {
        return t is IDictionary;
    }
}
gianlucaparadise
źródło
0

We wszystkich powyższych ścieżkach założono, że obiekty można serializować do formatu XML lub JSON
albo trzeba zaimplementować własne rozwiązanie.

Ale w końcu nadal dochodzisz do punktu, w którym musisz rozwiązać problemy, takie jak

  • rekurencja w obiektach
  • obiekty, których nie można serializować
  • wyjątki
  • ...

Plus log, aby uzyskać więcej informacji:

  • kiedy zdarzenie miało miejsce
  • stos rozmów
  • które trzyad
  • co było w sesji internetowej
  • który adres IP
  • url
  • ...

Jest najlepsze rozwiązanie, które rozwiązuje to wszystko i wiele więcej.
Użyj tego pakietu Nuget : Desharp .
Do wszystkich typów aplikacji - zarówno internetowych, jak i stacjonarnych .
Zobacz dokumentację Desharp Github . Posiada wiele opcji konfiguracyjnych .

Po prostu zadzwoń wszędzie:

Desharp.Debug.Log(anyException);
Desharp.Debug.Log(anyCustomValueObject);
Desharp.Debug.Log(anyNonserializableObject);
Desharp.Debug.Log(anyFunc);
Desharp.Debug.Log(anyFunc, Desharp.Level.EMERGENCY); // you can store into different files
  • może zapisać log w ładnym HTML (lub w formacie TEKSTOWYM, konfigurowalny)
  • opcjonalnie można pisać w wątku w tle (konfigurowalne)
  • ma opcje maksymalnej głębokości obiektów i maksymalnej długości ciągów (konfigurowalne)
  • używa pętli dla obiektów iterowalnych i odbicia wstecznego dla wszystkiego innego, a
    właściwie dla wszystkiego, co można znaleźć w środowisku .NET .

Wierzę, że to pomoże.

Tom Flídr
źródło