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?


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


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.


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.


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.


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

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


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

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.


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.


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

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.


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

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.