From 5f592520e704c60b3b2e92f9b62ce5f871f01981 Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Thu, 12 Jan 2023 19:57:26 +0200
Subject: [PATCH 1/9] Refactoring and fixing of HeapExplorer

* Introduced the `Option<A>` type to represent values that might not be there.
  * Replaced values representing failure (like `-1`) with `Option` to indicate the possibility of failure.

* Replaced possibly-invalid types with `Option`s. Now if you if have an instance - it's guaranteed to be valid.

* Replaced magic values (like using positive integers for managed object indexes and negative integers for static object indexes) with proper semantic types to help with code clarity.

* Replaced dangerous numeric casts that could under/overflow with typesafe alternatives (`PInt` positive 32bit integer type) that guarantee no such things can happen.

* Replaced instances of `List` which were being used as stacks with `Stack`.

* Replaced as many fields in objects as possible with readonly ones to indicate that we have no intent of changing them after the object is constructed. This is a part of 'objects should stay valid after construction' philosophy.

* Added logging for potentially dangerous operations (like int -> uint).

* Replaced primitive loop guards with cycle tracking that tracks seen objects. This eliminates the false positives and errors out sooner if we actually have an error.

* Replaced places where things got cancelled silently with emitting warnings / errors. Failures should never be silent.

* Fixed algorithm that checks if a type contains reference types: it didn't account for nested value-types which eventually have reference types in them and only looked for reference types in the first level.

* Fixed bad lookups in `PackedCoreTypes`. Also made `PackedCoreTypes` to be fully initialized, without any missing entries.

* Added a warning about the inability to crawl the `[ThreadStatic]` variables.

* Improved search speed by:
  * caching search results.
  * downcasing everything before the search and then using fast ordinal string comparisons instead of slower `OrdinalIgnoreCase` comparisons.

* Improved documentation by adding new docstrings and converting existing docstrings into proper XMLDocs. Now IDEs and tools can properly render the documentation.

* Rewrote a part of code using C# 7.3 (supported since Unity 2019) to enhance readability.

* bumped version to 4.1.2

* Added capability to copy the root path as text in "Find GC roots" window.

* Added information about source fields that are pointing to the object in "Find GC roots" window.

* Added progress reporting to Unity when a memory snapshot is being converted from the Unity format to our format.
---
 Editor/Scripts/AbstractTreeView.cs            |  69 +-
 Editor/Scripts/AbstractView.cs                |   2 +-
 .../CompareSnapshotsControl.cs                |  15 +-
 .../ConnectionsView/ConnectionsControl.cs     | 209 +++--
 .../ConnectionsView/ConnectionsView.cs        |  98 +-
 Editor/Scripts/CsvExport/CsvExportView.cs     |  20 +-
 .../Scripts/DataVisualizer/DataVisualizer.cs  | 110 +--
 .../Scripts/GCHandlesView/GCHandlesControl.cs |  47 +-
 Editor/Scripts/GCHandlesView/GCHandlesView.cs |  25 +-
 Editor/Scripts/HeGlobals.cs                   |   2 +-
 Editor/Scripts/HeapExplorerWindow.cs          |  89 +-
 .../ManagedEmptyShellObjectsControl.cs        |  12 +-
 .../ManagedEmptyShellObjectsView.cs           |   4 +-
 .../ManagedHeapSectionsControl.cs             |  11 +-
 .../ManagedHeapSectionsView.cs                |  36 +-
 .../ManagedObjectDuplicatesControl.cs         |   5 +-
 .../ManagedObjectDuplicatesView.cs            |  33 +-
 .../ManagedObjectsControl.cs                  |  25 +-
 .../ManagedObjectsView/ManagedObjectsView.cs  |  57 +-
 .../ManagedTypesView/ManagedTypesControl.cs   |  34 +-
 .../ManagedTypesView/ManagedTypesView.cs      |  14 +-
 Editor/Scripts/MemoryReader.cs                | 538 +++++------
 Editor/Scripts/MemoryWindow.cs                |   8 +-
 .../AbstractNativeObjectsView.cs              |  27 +-
 .../NativeObjectsView/NativeObjectControl.cs  |   3 +-
 .../NativeObjectDuplicatesView.cs             |   4 +-
 .../NativeObjectPreviewView.cs                |  41 +-
 .../NativeObjectsView/NativeObjectsControl.cs |  64 +-
 .../NativeObjectsView/NativeObjectsView.cs    |   6 +-
 Editor/Scripts/OverviewView/OverviewView.cs   |  43 +-
 .../Scripts/PackedTypes/PackedConnection.cs   | 171 ++--
 Editor/Scripts/PackedTypes/PackedCoreTypes.cs | 331 +++++--
 Editor/Scripts/PackedTypes/PackedGCHandle.cs  |  58 +-
 .../Scripts/PackedTypes/PackedManagedField.cs |  82 +-
 .../PackedManagedObject.ArrayIndex.cs         |  56 ++
 .../PackedManagedObject.ArrayIndex.cs.meta    |   3 +
 .../PackedTypes/PackedManagedObject.cs        | 112 ++-
 .../PackedTypes/PackedManagedObjectCrawler.cs | 826 +++++++++--------
 .../PackedTypes/PackedManagedStaticField.cs   |  37 +-
 .../Scripts/PackedTypes/PackedManagedType.cs  | 504 +++++++----
 .../PackedTypes/PackedMemorySection.cs        |  39 +-
 .../PackedTypes/PackedMemorySnapshot.cs       | 150 +++-
 .../PackedTypes/PackedMemorySnapshotEx.cs     | 835 +++++++-----------
 .../Scripts/PackedTypes/PackedNativeType.cs   |  76 +-
 .../PackedNativeUnityEngineObject.cs          |  63 +-
 .../PackedVirtualMachineInformation.cs        | 105 ++-
 .../ArrayElementPropertyGridItem.cs           |   6 +-
 .../PropertyGrid/ArrayPropertyGridItem.cs     |  70 +-
 .../PrimitiveTypePropertyGridItem.cs          |   4 +-
 .../PropertyGrid/PropertyGridControl.cs       |  18 +-
 .../Scripts/PropertyGrid/PropertyGridItem.cs  |  18 +-
 .../Scripts/PropertyGrid/PropertyGridView.cs  |  45 +-
 .../ReferenceTypePropertyGridItem.cs          |   7 +-
 .../PropertyGrid/ValueTypePropertyGridItem.cs |  11 +-
 Editor/Scripts/RichTypes/RichGCHandle.cs      | 109 +--
 Editor/Scripts/RichTypes/RichManagedObject.cs | 158 +---
 Editor/Scripts/RichTypes/RichManagedType.cs   | 120 +--
 Editor/Scripts/RichTypes/RichNativeObject.cs  | 207 +----
 Editor/Scripts/RichTypes/RichNativeType.cs    |  96 +-
 Editor/Scripts/RichTypes/RichStaticField.cs   | 116 +--
 .../Scripts/RootPathView/RootPathControl.cs   | 318 ++++---
 Editor/Scripts/RootPathView/RootPathView.cs   |  20 +-
 Editor/Scripts/SearchTextParser.cs            | 105 +--
 .../StaticFieldsView/StaticFieldsControl.cs   |  13 +-
 .../StaticFieldsView/StaticFieldsView.cs      |  58 +-
 Editor/Scripts/Utilities.meta                 |   3 +
 Editor/Scripts/Utilities/CycleTracker.cs      |  77 ++
 Editor/Scripts/Utilities/CycleTracker.cs.meta |   3 +
 Editor/Scripts/Utilities/Option.cs            | 177 ++++
 Editor/Scripts/Utilities/Option.cs.meta       |   3 +
 Editor/Scripts/Utilities/PInt.cs              |  88 ++
 Editor/Scripts/Utilities/PInt.cs.meta         |   3 +
 Editor/Scripts/Utilities/Utils.cs             | 153 ++++
 Editor/Scripts/Utilities/Utils.cs.meta        |   3 +
 Editor/Scripts/ViewConsts.cs                  |  11 +
 Editor/Scripts/ViewConsts.cs.meta             |   3 +
 Editor/Scripts/WelcomeView/WelcomeView.cs     |   6 +-
 77 files changed, 4006 insertions(+), 3122 deletions(-)
 create mode 100644 Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs
 create mode 100644 Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs.meta
 create mode 100644 Editor/Scripts/Utilities.meta
 create mode 100644 Editor/Scripts/Utilities/CycleTracker.cs
 create mode 100644 Editor/Scripts/Utilities/CycleTracker.cs.meta
 create mode 100644 Editor/Scripts/Utilities/Option.cs
 create mode 100644 Editor/Scripts/Utilities/Option.cs.meta
 create mode 100644 Editor/Scripts/Utilities/PInt.cs
 create mode 100644 Editor/Scripts/Utilities/PInt.cs.meta
 create mode 100644 Editor/Scripts/Utilities/Utils.cs
 create mode 100644 Editor/Scripts/Utilities/Utils.cs.meta
 create mode 100644 Editor/Scripts/ViewConsts.cs
 create mode 100644 Editor/Scripts/ViewConsts.cs.meta

diff --git a/Editor/Scripts/AbstractTreeView.cs b/Editor/Scripts/AbstractTreeView.cs
index 4fa345e..e0e041b 100644
--- a/Editor/Scripts/AbstractTreeView.cs
+++ b/Editor/Scripts/AbstractTreeView.cs
@@ -5,9 +5,11 @@
 
 using System;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -36,7 +38,6 @@ public HeapExplorerWindow window
         IList<int> m_Expanded = new List<int>(32);
         TreeViewItem m_Tree;
         string[] m_SearchCache = new string[32];
-        System.Text.StringBuilder m_SearchBuilder = new System.Text.StringBuilder();
 
         public AbstractTreeView(HeapExplorerWindow window, string editorPrefsKey, TreeViewState state)
             : base(state)
@@ -301,23 +302,36 @@ protected override bool DoesItemMatchSearch(TreeViewItem item, string search)
             var i = item as AbstractTreeViewItem;
             if (i != null)
             {
-                int searchCount;
-                string type;
-                string label;
-                i.GetItemSearchString(m_SearchCache, out searchCount, out type, out label);
+                if (!i.m_MaybeCachedItemSearchString.valueOut(out var searchString)) {
+                    int searchCount;
+                    string type;
+                    string label;
+                    i.GetItemSearchString(m_SearchCache, out searchCount, out type, out label);
+
+                    var names = new List<string>(capacity: searchCount);
+                    for (var n=0; n < searchCount; ++n) 
+                    {
+                        var str = m_SearchCache[n];
+                        if (!string.IsNullOrEmpty(str)) names.Add(str.ToLowerInvariant());
+                    }
 
-                if (!m_Search.IsTypeMatch(type) || !m_Search.IsLabelMatch(label))
-                    return false;
+                    searchString = new AbstractTreeViewItem.Cache(
+                        lowerCasedNames: names.ToArray(), type: type, label: label
+                    );
+                    i.m_MaybeCachedItemSearchString = Some(searchString);
+                }
 
-                m_SearchBuilder.Length = 0;
-                for (var n=0; n < searchCount; ++n)
-                {
-                    m_SearchBuilder.Append(m_SearchCache[n]);
-                    m_SearchBuilder.Append(" ");
+                if (!m_Search.IsTypeMatch(searchString.type) || !m_Search.IsLabelMatch(searchString.label)) {
+                    return false;
                 }
-                m_SearchBuilder.Append("\0");
+                else {
+                    // ReSharper disable once LoopCanBeConvertedToQuery
+                    foreach (var lowerCasedName in searchString.lowerCasedNames) {
+                        if (m_Search.IsNameMatch(lowerCasedName)) return true;
+                    }
 
-                return m_Search.IsNameMatch(m_SearchBuilder.ToString());
+                    return false;
+                }
             }
 
             return base.DoesItemMatchSearch(item, search);
@@ -461,7 +475,34 @@ public virtual void GetItemSearchString(string[] target, out int count, out stri
             label = null;
         }
 
+        /// <summary>
+        /// Results of <see cref="GetItemSearchString"/> are cached here to avoid re-computation. If this is `None`,
+        /// invoke the <see cref="GetItemSearchString"/> and store the result here.
+        /// </summary>
+        public Option<Cache> m_MaybeCachedItemSearchString;
+
         public abstract void OnGUI(Rect position, int column);
+
+        public sealed class Cache {
+            /// <summary>
+            /// Parameters for <see cref="SearchTextParser.Result.IsNameMatch"/>.
+            /// <para/>
+            /// The search will match if any of these matches.
+            /// </summary>
+            public readonly string[] lowerCasedNames;
+            
+            /// <summary>Parameter for <see cref="SearchTextParser.Result.IsTypeMatch"/>.</summary>
+            public readonly string type;
+            
+            /// <summary>Parameter for <see cref="SearchTextParser.Result.IsLabelMatch"/>.</summary>
+            public readonly string label;
+
+            public Cache(string[] lowerCasedNames, string type, string label) {
+                this.lowerCasedNames = lowerCasedNames;
+                this.type = type;
+                this.label = label;
+            } 
+        } 
     }
 
     public static class TreeViewUtility
diff --git a/Editor/Scripts/AbstractView.cs b/Editor/Scripts/AbstractView.cs
index 970cac9..b685b69 100644
--- a/Editor/Scripts/AbstractView.cs
+++ b/Editor/Scripts/AbstractView.cs
@@ -81,7 +81,7 @@ protected string GetPrefsKey(Expression<Func<object>> exp)
                 body = ubody.Operand as MemberExpression;
             }
 
-            return string.Format("HeapExplorer.{0}.{1}", editorPrefsKey, body.Member.Name);
+            return $"HeapExplorer.{editorPrefsKey}.{body.Member.Name}";
         }
 
         public HeapExplorerView()
diff --git a/Editor/Scripts/CompareSnapshotsView/CompareSnapshotsControl.cs b/Editor/Scripts/CompareSnapshotsView/CompareSnapshotsControl.cs
index e483266..0045295 100644
--- a/Editor/Scripts/CompareSnapshotsView/CompareSnapshotsControl.cs
+++ b/Editor/Scripts/CompareSnapshotsView/CompareSnapshotsControl.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
 using UnityEngine;
@@ -139,7 +140,7 @@ void BuildMemoryTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeVie
 
                 foreach (var section in snapshot.managedHeapSections)
                 {
-                    parent.size[k] += (long)section.size;
+                    parent.size[k] += section.size;
                 }
 
                 parent.count[k] += snapshot.managedHeapSections.Length;
@@ -164,7 +165,9 @@ void BuildGCHandleTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeV
 
                 var snapshot = snapshots[k];
 
-                parent.size[k] += snapshot.gcHandles.Length * snapshot.virtualMachineInformation.pointerSize;
+                parent.size[k] += (
+                    snapshot.gcHandles.Length * snapshot.virtualMachineInformation.pointerSize.sizeInBytes()
+                ).ToUIntClamped();
                 parent.count[k] += snapshot.gcHandles.Length;
             }
         }
@@ -303,14 +306,14 @@ void BuildManagedTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeVi
 
         class Item : AbstractTreeViewItem
         {
-            public long[] size = new long[2];
+            public ulong[] size = new ulong[2];
             public long[] count = new long[2];
 
             public long sizeDiff
             {
                 get
                 {
-                    return size[1] - size[0];
+                    return size[1].ToLongClamped() - size[0].ToLongClamped();
                 }
             }
 
@@ -357,11 +360,11 @@ public override void OnGUI(Rect position, int column)
                         break;
 
                     case EColumn.SizeA:
-                        HeEditorGUI.Size(position, size[0]);
+                        HeEditorGUI.Size(position, size[0].ToLongClamped());
                         break;
 
                     case EColumn.SizeB:
-                        HeEditorGUI.Size(position, size[1]);
+                        HeEditorGUI.Size(position, size[1].ToLongClamped());
                         break;
 
                     case EColumn.SizeDiff:
diff --git a/Editor/Scripts/ConnectionsView/ConnectionsControl.cs b/Editor/Scripts/ConnectionsView/ConnectionsControl.cs
index dc8464c..324bdf3 100644
--- a/Editor/Scripts/ConnectionsView/ConnectionsControl.cs
+++ b/Editor/Scripts/ConnectionsView/ConnectionsControl.cs
@@ -2,11 +2,15 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
+
+using System;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
+using static HeapExplorer.ViewConsts;
 
 namespace HeapExplorer
 {
@@ -14,11 +18,12 @@ public class GotoCommand
     {
         public HeapExplorerView fromView;
         public HeapExplorerView toView;
-        public RichGCHandle toGCHandle;
-        public RichManagedObject toManagedObject;
-        public RichNativeObject toNativeObject;
-        public RichStaticField toStaticField;
-        public RichManagedType toManagedType;
+        
+        public Option<RichGCHandle> toGCHandle;
+        public Option<RichManagedObject> toManagedObject;
+        public Option<RichNativeObject> toNativeObject;
+        public Option<RichStaticField> toStaticField;
+        public Option<RichManagedType> toManagedType;
 
         public GotoCommand()
         {
@@ -27,31 +32,31 @@ public GotoCommand()
         public GotoCommand(RichGCHandle value)
             : this()
         {
-            toGCHandle = value;
+            toGCHandle = Some(value);
         }
 
         public GotoCommand(RichManagedObject value)
             : this()
         {
-            toManagedObject = value;
+            toManagedObject = Some(value);
         }
 
         public GotoCommand(RichNativeObject value)
             : this()
         {
-            toNativeObject = value;
+            toNativeObject = Some(value);
         }
 
         public GotoCommand(RichStaticField value)
             : this()
         {
-            toStaticField = value;
+            toStaticField = Some(value);
         }
 
         public GotoCommand(RichManagedType value)
             : this()
         {
-            toManagedType = value;
+            toManagedType = Some(value);
         }
     }
 
@@ -133,25 +138,24 @@ public int count
         }
 
         PackedMemorySnapshot m_Snapshot;
-        PackedConnection[] m_Connections;
-        bool m_AddFrom;
-        bool m_AddTo;
+        
         int m_UniqueId = 1;
 
         enum Column
         {
             Type,
             Name,
+            SourceField,
             Address,
         }
 
         public ConnectionsControl(HeapExplorerWindow window, string editorPrefsKey, TreeViewState state)
             : base(window, editorPrefsKey, state, new MultiColumnHeader(
-                new MultiColumnHeaderState(new[]
-                {
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Type"), width = 200, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("C++ Name"), width = 200, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Address"), width = 200, autoResize = true },
+                new MultiColumnHeaderState(new[] {
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Type"), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent(COLUMN_CPP_NAME, COLUMN_CPP_NAME_DESCRIPTION), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent(COLUMN_SOURCE_FIELD, COLUMN_SOURCE_FIELD_DESCRIPTION), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Address"), width = 200, autoResize = true },
                 })))
         {
             extraSpaceBeforeIconAndLabel = 4;
@@ -161,71 +165,49 @@ public ConnectionsControl(HeapExplorerWindow window, string editorPrefsKey, Tree
             Reload();
         }
 
-        public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, PackedConnection[] connections, bool addFrom, bool addTo)
-        {
+        /// <summary>
+        /// The <see cref="connections"/> here are stored as <see cref="PackedConnection.From"/> because this component
+        /// is reused for both `from` and `to` connections and `from` connections contain more data.
+        /// <para/>
+        /// The `to` connections will be converted to <see cref="PackedConnection.From"/> with
+        /// <see cref="<see cref="PackedConnection.From.field"/> missing.
+        /// </summary>
+        public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, PackedConnection.From[] connections) {
             m_Snapshot = snapshot;
-            m_Connections = connections;
-            m_AddFrom = addFrom;
-            m_AddTo = addTo;
-
             m_UniqueId = 1;
 
             var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
-            if (m_Snapshot == null || m_Connections == null || m_Connections.Length < 1)
-            {
+            if (m_Snapshot == null || connections == null || connections.Length < 1) {
                 root.AddChild(new TreeViewItem { id = 1, depth = -1, displayName = "" });
                 return root;
             }
 
-            for (int n = 0, nend = m_Connections.Length; n < nend; ++n)
+            for (int n = 0, nend = connections.Length; n < nend; ++n)
             {
                 if (window.isClosing) // the window is closing
                     break;
 
-                var connection = m_Connections[n];
+                var connection = connections[n];
 
-                if (m_AddTo)
-                {
-                    switch (connection.toKind)
-                    {
-                        case PackedConnection.Kind.GCHandle:
-                            AddGCHandle(root, m_Snapshot.gcHandles[connection.to]);
-                            break;
-
-                        case PackedConnection.Kind.Managed:
-                            AddManagedObject(root, m_Snapshot.managedObjects[connection.to]);
-                            break;
-
-                        case PackedConnection.Kind.Native:
-                            AddNativeUnityObject(root, m_Snapshot.nativeObjects[connection.to]);
-                            break;
-
-                        case PackedConnection.Kind.StaticField:
-                            AddStaticField(root, m_Snapshot.managedStaticFields[connection.to]);
-                            break;
-                    }
-                }
-
-                if (m_AddFrom)
-                {
-                    switch (connection.fromKind)
-                    {
-                        case PackedConnection.Kind.GCHandle:
-                            AddGCHandle(root, m_Snapshot.gcHandles[connection.from]);
-                            break;
+                switch (connection.pair.kind) {
+                    case PackedConnection.Kind.GCHandle:
+                        AddGCHandle(root, m_Snapshot.gcHandles[connection.pair.index], connection.field);
+                        break;
 
-                        case PackedConnection.Kind.Managed:
-                            AddManagedObject(root, m_Snapshot.managedObjects[connection.from]);
-                            break;
+                    case PackedConnection.Kind.Managed:
+                        AddManagedObject(root, m_Snapshot.managedObjects[connection.pair.index], connection.field);
+                        break;
 
-                        case PackedConnection.Kind.Native:
-                            AddNativeUnityObject(root, m_Snapshot.nativeObjects[connection.from]);
-                            break;
+                    case PackedConnection.Kind.Native:
+                        AddNativeUnityObject(root, m_Snapshot.nativeObjects[connection.pair.index], connection.field);
+                        break;
 
-                        case PackedConnection.Kind.StaticField:
-                            AddStaticField(root, m_Snapshot.managedStaticFields[connection.from]);
-                            break;
-                    }
+                    case PackedConnection.Kind.StaticField:
+                        AddStaticField(root, m_Snapshot.managedStaticFields[connection.pair.index], connection.field);
+                        break;
+                    
+                    default:
+                        throw new ArgumentOutOfRangeException(nameof(connection.pair.kind), connection.pair.kind, "unknown kind");
                 }
             }
 
@@ -243,48 +225,55 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, PackedConnection[]
             return root;
         }
 
-        void AddGCHandle(TreeViewItem parent, PackedGCHandle gcHandle)
+        void AddGCHandle(TreeViewItem parent, PackedGCHandle gcHandle, Option<PackedManagedField> field)
         {
             var item = new GCHandleItem
             {
                 id = m_UniqueId++,
-                depth = parent.depth + 1
+                depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name) 
             };
 
             item.Initialize(this, m_Snapshot, gcHandle.gcHandlesArrayIndex);
             parent.AddChild(item);
         }
 
-        void AddManagedObject(TreeViewItem parent, PackedManagedObject managedObject)
-        {
+        void AddManagedObject(
+            TreeViewItem parent, PackedManagedObject managedObject, Option<PackedManagedField> field
+        ) {
             var item = new ManagedObjectItem
             {
                 id = m_UniqueId++,
-                depth = parent.depth + 1
+                depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name) 
             };
 
             item.Initialize(this, m_Snapshot, managedObject.managedObjectsArrayIndex);
             parent.AddChild(item);
         }
 
-        void AddNativeUnityObject(TreeViewItem parent, PackedNativeUnityEngineObject nativeObject)
-        {
+        void AddNativeUnityObject(
+            TreeViewItem parent, PackedNativeUnityEngineObject nativeObject, Option<PackedManagedField> field
+        ) {
             var item = new NativeObjectItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name) 
             };
 
             item.Initialize(this, m_Snapshot, nativeObject);
             parent.AddChild(item);
         }
 
-        void AddStaticField(TreeViewItem parent, PackedManagedStaticField staticField)
-        {
+        void AddStaticField(
+            TreeViewItem parent, PackedManagedStaticField staticField, Option<PackedManagedField> field
+        ) {
             var item = new ManagedStaticFieldItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name) 
             };
 
             item.Initialize(this, m_Snapshot, staticField.staticFieldsArrayIndex);
@@ -306,6 +295,10 @@ class Item : AbstractTreeViewItem
 
             protected ConnectionsControl m_Owner;
             protected string m_Value = "";
+            
+            /// <summary>Name of the field if it makes sense.</summary>
+            public string fieldName = "";
+            
             protected string m_Tooltip = "";
 
             public override void GetItemSearchString(string[] target, out int count, out string type, out string label)
@@ -330,6 +323,10 @@ public override void OnGUI(Rect position, int column)
                         EditorGUI.LabelField(position, m_Value);
                         break;
 
+                    case Column.SourceField:
+                        EditorGUI.LabelField(position, fieldName);
+                        break;
+
                     case Column.Address:
                         if (address != 0) // statics dont have an address in PackedMemorySnapshot and I don't want to display a misleading 0
                             HeEditorGUI.Address(position, address);
@@ -352,7 +349,7 @@ public void Initialize(ConnectionsControl owner, PackedMemorySnapshot snapshot,
                 m_GCHandle = new RichGCHandle(m_Snapshot, gcHandleArrayIndex);
 
                 displayName = "GCHandle";
-                m_Value = m_GCHandle.managedObject.isValid ? m_GCHandle.managedObject.type.name : "";
+                m_Value = m_GCHandle.managedObject.fold("", _ => _.type.name);
                 address = m_GCHandle.managedObjectAddress;
             }
 
@@ -365,21 +362,19 @@ public override void OnGUI(Rect position, int column)
                         m_Owner.window.OnGoto(new GotoCommand(m_GCHandle));
                     }
 
-                    if (m_GCHandle.nativeObject.isValid)
-                    {
+                    {if (m_GCHandle.nativeObject.valueOut(out var nativeObject)) {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
-                    }
+                    }}
 
-                    if (m_GCHandle.managedObject.isValid)
-                    {
+                    {if (m_GCHandle.managedObject.valueOut(out var managedObject)) {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
@@ -392,14 +387,15 @@ class ManagedObjectItem : Item
         {
             RichManagedObject m_ManagedObject;
 
-            public void Initialize(ConnectionsControl owner, PackedMemorySnapshot snapshot, int arrayIndex)
-            {
+            public void Initialize(
+                ConnectionsControl owner, PackedMemorySnapshot snapshot, PackedManagedObject.ArrayIndex arrayIndex
+            ) {
                 m_Owner = owner;
                 m_ManagedObject = new RichManagedObject(snapshot, arrayIndex);
 
                 displayName = m_ManagedObject.type.name;
                 address = m_ManagedObject.address;
-                m_Value = m_ManagedObject.nativeObject.isValid ? m_ManagedObject.nativeObject.name : "";
+                m_Value = m_ManagedObject.nativeObject.fold("", _ => _.name);
                 m_Tooltip = PackedManagedTypeUtility.GetInheritanceAsString(snapshot, m_ManagedObject.type.packed.managedTypesArrayIndex);
             }
 
@@ -407,26 +403,24 @@ public override void OnGUI(Rect position, int column)
             {
                 if (column == 0)
                 {
-                    if (m_ManagedObject.gcHandle.isValid)
-                    {
+                    {if (m_ManagedObject.gcHandle.valueOut(out var gcHandle)) {
                         if (HeEditorGUI.GCHandleButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject.gcHandle));
+                            m_Owner.window.OnGoto(new GotoCommand(gcHandle));
                         }
-                    }
+                    }}
 
                     if (HeEditorGUI.CsButton(HeEditorGUI.SpaceL(ref position, position.height)))
                     {
                         m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject));
                     }
 
-                    if (m_ManagedObject.nativeObject.isValid)
-                    {
+                    {if (m_ManagedObject.nativeObject.valueOut(out var nativeObject)) {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
@@ -486,11 +480,10 @@ public void Initialize(ConnectionsControl owner, PackedMemorySnapshot snapshot,
                 // It makes it easier to understand what it is, otherwise everything displays 'MonoBehaviour' only.
                 if (m_NativeObject.type.IsSubclassOf(m_Snapshot.coreTypes.nativeMonoBehaviour) || m_NativeObject.type.IsSubclassOf(m_Snapshot.coreTypes.nativeScriptableObject))
                 {
-                    string monoScriptName;
-                    if (m_Snapshot.FindNativeMonoScriptType(m_NativeObject.packed.nativeObjectsArrayIndex, out monoScriptName) != -1)
+                    if (m_Snapshot.FindNativeMonoScriptType(m_NativeObject.packed.nativeObjectsArrayIndex).valueOut(out var tpl))
                     {
-                        if (!string.IsNullOrEmpty(monoScriptName))
-                            displayName = monoScriptName;
+                        if (!string.IsNullOrEmpty(tpl.monoScriptName))
+                            displayName = tpl.monoScriptName;
                     }
                 }
             }
@@ -504,21 +497,19 @@ public override void OnGUI(Rect position, int column)
                         m_Owner.window.OnGoto(new GotoCommand(m_NativeObject));
                     }
 
-                    if (m_NativeObject.gcHandle.isValid)
-                    {
+                    {if (m_NativeObject.gcHandle.valueOut(out var gcHandle)) {
                         if (HeEditorGUI.GCHandleButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_NativeObject.gcHandle));
+                            m_Owner.window.OnGoto(new GotoCommand(gcHandle));
                         }
-                    }
+                    }}
 
-                    if (m_NativeObject.managedObject.isValid)
-                    {
+                    {if (m_NativeObject.managedObject.valueOut(out var managedObject)) {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_NativeObject.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
diff --git a/Editor/Scripts/ConnectionsView/ConnectionsView.cs b/Editor/Scripts/ConnectionsView/ConnectionsView.cs
index 7a58505..7660e68 100644
--- a/Editor/Scripts/ConnectionsView/ConnectionsView.cs
+++ b/Editor/Scripts/ConnectionsView/ConnectionsView.cs
@@ -2,11 +2,13 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
+using System.Linq;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -70,7 +72,7 @@ public void Inspect(PackedMemorySection item)
             var job = new Job
             {
                 snapshot = snapshot,
-                memorySection = item,
+                memorySection = Some(item),
                 referencedByControl = m_ReferencedByControl,
                 referencesControl = m_ReferencesControl
             };
@@ -80,22 +82,26 @@ public void Inspect(PackedMemorySection item)
 
         public void Inspect(PackedNativeUnityEngineObject item)
         {
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedManagedStaticField item)
         {
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedGCHandle item)
         {
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedManagedObject item)
         {
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedManagedStaticField[] items)
@@ -106,7 +112,7 @@ public void Inspect(PackedManagedStaticField[] items)
             var job = new Job
             {
                 snapshot = snapshot,
-                staticFields = items,
+                staticFields = Some(items),
                 referencedByControl = m_ReferencedByControl,
                 referencesControl = m_ReferencesControl
             };
@@ -156,7 +162,7 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            EditorGUILayout.LabelField(string.Format("References to {0} object(s)", m_ReferencesControl.count), EditorStyles.boldLabel);
+                            EditorGUILayout.LabelField($"References to {m_ReferencesControl.count} object(s)", EditorStyles.boldLabel);
                             if (m_ReferencesSearchField.OnToolbarGUI())
                                 m_ReferencesControl.Search(m_ReferencesSearchField.text);
                             if (afterReferencesToolbarGUI != null)
@@ -189,7 +195,7 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            EditorGUILayout.LabelField(string.Format("Referenced by {0} object(s)", m_ReferencedByControl.count), EditorStyles.boldLabel);
+                            EditorGUILayout.LabelField($"Referenced by {m_ReferencedByControl.count} object(s)", EditorStyles.boldLabel);
                             if (m_ReferencedBySearchField.OnToolbarGUI())
                                 m_ReferencedByControl.Search(m_ReferencedBySearchField.text);
 
@@ -213,7 +219,7 @@ void ScheduleJob(ObjectProxy objectProxy)
             var job = new Job
             {
                 snapshot = snapshot,
-                objectProxy = objectProxy,
+                objectProxy = Some(objectProxy),
                 referencedByControl = m_ReferencedByControl,
                 referencesControl = m_ReferencesControl
             };
@@ -223,9 +229,9 @@ void ScheduleJob(ObjectProxy objectProxy)
 
         class Job : AbstractThreadJob
         {
-            public ObjectProxy objectProxy;
-            public PackedManagedStaticField[] staticFields;
-            public PackedMemorySection? memorySection;
+            public Option<ObjectProxy> objectProxy;
+            public Option<PackedManagedStaticField[]> staticFields;
+            public Option<PackedMemorySection> memorySection;
 
             public PackedMemorySnapshot snapshot;
             public ConnectionsControl referencesControl;
@@ -238,32 +244,48 @@ class Job : AbstractThreadJob
 
             public override void ThreadFunc()
             {
-                var references = new List<PackedConnection>();
-                var referencedBy = new List<PackedConnection>();
-
-                if (objectProxy != null && objectProxy.gcHandle.isValid)
-                    snapshot.GetConnections(objectProxy.gcHandle.packed, references, referencedBy);
-
-                if (objectProxy != null && objectProxy.managed.isValid)
-                    snapshot.GetConnections(objectProxy.managed.packed, references, referencedBy);
-
-                if (objectProxy != null && objectProxy.native.isValid)
-                    snapshot.GetConnections(objectProxy.native.packed, references, referencedBy);
-
-                if (objectProxy != null && objectProxy.staticField.isValid)
-                    snapshot.GetConnections(objectProxy.staticField.packed, references, referencedBy);
-
-                if (memorySection.HasValue)
-                    snapshot.GetConnections(memorySection.Value, references, referencedBy);
-
-                if (staticFields != null)
-                {
+                // The `.to` endpoints of `PackedConnection`.
+                var references = new List<PackedConnection.Pair>();
+                PackedConnection.Pair convertReferences(PackedConnection connection) => connection.to;
+                // The `.from` endpoints of `PackedConnection`.
+                var referencedBy = new List<PackedConnection.From>();
+                PackedConnection.From convertReferencedBy(PackedConnection connection) => connection.from;
+
+                {if (this.objectProxy.valueOut(out var objectProxy) && objectProxy.gcHandle.valueOut(out var gcHandle))
+                    snapshot.GetConnections(
+                        gcHandle.packed, references, referencedBy, convertReferences, convertReferencedBy
+                    );}
+
+                {if (this.objectProxy.valueOut(out var objectProxy) && objectProxy.managed.valueOut(out var managedObject))
+                    snapshot.GetConnections(
+                        managedObject.packed, references, referencedBy, convertReferences, convertReferencedBy
+                    );}
+
+                {if (this.objectProxy.valueOut(out var objectProxy) && objectProxy.native.valueOut(out var nativeObject))
+                    snapshot.GetConnections(
+                        nativeObject.packed, references, referencedBy, convertReferences, convertReferencedBy
+                    );}
+
+                {if (this.objectProxy.valueOut(out var objectProxy) && objectProxy.staticField.valueOut(out var staticField))
+                    snapshot.GetConnections(
+                        staticField.packed, references, referencedBy, convertReferences, convertReferencedBy
+                    );}
+
+                {if (this.memorySection.valueOut(out var memorySection)) {
+                    snapshot.GetConnections(memorySection, references, _ => _);
+                }}
+
+                {if (this.staticFields.valueOut(out var staticFields)) {
                     foreach (var item in staticFields)
-                        snapshot.GetConnections(item, references, referencedBy);
-                }
-
-                referencesTree = referencesControl.BuildTree(snapshot, references.ToArray(), false, true);
-                referencedByTree = referencedByControl.BuildTree(snapshot, referencedBy.ToArray(), true, false);
+                        snapshot.GetConnections(item, references, referencedBy, convertReferences, convertReferencedBy);
+                }}
+
+                referencesTree = referencesControl.BuildTree(
+                    snapshot, 
+                    // See method documentation for reasoning.
+                    references.Select(to => new PackedConnection.From(to, field: None._)).ToArray()
+                );
+                referencedByTree = referencedByControl.BuildTree(snapshot, referencedBy.ToArray());
             }
 
             public override void IntegrateFunc()
diff --git a/Editor/Scripts/CsvExport/CsvExportView.cs b/Editor/Scripts/CsvExport/CsvExportView.cs
index d439ab6..db407db 100644
--- a/Editor/Scripts/CsvExport/CsvExportView.cs
+++ b/Editor/Scripts/CsvExport/CsvExportView.cs
@@ -2,8 +2,6 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
 using UnityEngine;
 using UnityEditor;
 
@@ -106,8 +104,8 @@ void Export()
 
             System.IO.Directory.CreateDirectory(folder);
 
-            ExportNativeObjects(string.Format("{0}/{1}_native_objects.csv", folder, m_ExportName));
-            ExportManagedObjects(string.Format("{0}/{1}_managed_objects.csv", folder, m_ExportName));
+            ExportNativeObjects($"{folder}/{m_ExportName}_native_objects.csv");
+            ExportManagedObjects($"{folder}/{m_ExportName}_managed_objects.csv");
             //ExportManagedStaticFields(string.Format("{0}/{1}_managed_static_fields.csv", folder, m_ExportName));
         }
 
@@ -143,7 +141,8 @@ void ExportNativeObjects(string filePath)
                     obj.instanceId,
                     obj.isManager,
                     obj.hideFlags,
-                    obj.managedObject.type.name);
+                    obj.managedObject.fold("<unresolved>", _ => _.type.name)
+                );
             }
 
             System.IO.File.WriteAllText(filePath, sb.ToString(), System.Text.Encoding.UTF8);
@@ -166,14 +165,16 @@ void ExportManagedObjects(string filePath)
             for (var n = 0; n < objs.Length; ++n)
             {
                 var obj = new RichManagedObject(snapshot, objs[n].managedObjectsArrayIndex);
+                var maybeNativeObject = obj.nativeObject;
                 sb.AppendFormat("\"{1}\"{0}\"{2}\"{0}\"{3}\"{0}\"{4}\"{0}\"{5}\"{0}\"{6}\"\n",
                     m_Delimiter,
                     obj.type.name,
                     obj.address,
                     obj.size,
                     obj.type.assemblyName,
-                    obj.nativeObject.address,
-                    obj.nativeObject.type.name);
+                    maybeNativeObject.fold("<unresolved>", _ => _.address.ToString()),
+                    maybeNativeObject.fold("<unresolved>", _ => _.type.name)
+                );
             }
 
             System.IO.File.WriteAllText(filePath, sb.ToString(), System.Text.Encoding.UTF8);
@@ -193,11 +194,12 @@ void ExportManagedStaticFields(string filePath)
             for (var n = 0; n < objs.Length; ++n)
             {
                 var obj = new RichStaticField(snapshot, objs[n].staticFieldsArrayIndex);
+                var fieldType = obj.fieldType;
                 sb.AppendFormat("\"{1}\"{0}\"{2}\"{0}\"{3}\"\n",
                     m_Delimiter,
                     obj.classType.name,
-                    obj.fieldType.name,
-                    obj.fieldType.assemblyName);
+                    fieldType.name,
+                    fieldType.assemblyName);
             }
 
             System.IO.File.WriteAllText(filePath, sb.ToString(), System.Text.Encoding.UTF8);
diff --git a/Editor/Scripts/DataVisualizer/DataVisualizer.cs b/Editor/Scripts/DataVisualizer/DataVisualizer.cs
index 9246038..ee0626e 100644
--- a/Editor/Scripts/DataVisualizer/DataVisualizer.cs
+++ b/Editor/Scripts/DataVisualizer/DataVisualizer.cs
@@ -2,14 +2,17 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2022 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
+
+using System;
 using System.Collections.Generic;
+using System.Globalization;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 
 namespace HeapExplorer
 {
-    abstract public class AbstractDataVisualizer
+    public abstract class AbstractDataVisualizer
     {
         public bool isFallback
         {
@@ -34,21 +37,21 @@ public bool hasMenu
         }
 
         protected PackedMemorySnapshot m_Snapshot;
-        protected System.UInt64 m_Address;
+        protected UInt64 m_Address;
         protected PackedManagedType m_Type;
         protected AbstractMemoryReader m_MemoryReader;
         protected string m_Title = "Visualizer";
         protected GenericMenu m_Menu;
 
-        static Dictionary<string, System.Type> s_Visualizers = new Dictionary<string, System.Type>();
+        static readonly Dictionary<string, Type> s_Visualizers = new Dictionary<string, Type>();
 
-        public void Initialize(PackedMemorySnapshot snapshot, AbstractMemoryReader memoryReader, System.UInt64 address, PackedManagedType type)
+        public void Initialize(PackedMemorySnapshot snapshot, AbstractMemoryReader memoryReader, UInt64 address, PackedManagedType type)
         {
             m_Snapshot = snapshot;
             m_Address = address;
             m_Type = type;
             m_MemoryReader = memoryReader;
-            m_Title = string.Format("{0} Visualizer", type.name);
+            m_Title = $"{type.name} Visualizer";
 
             OnInitialize();
         }
@@ -63,10 +66,10 @@ public void ShowMenu()
             m_Menu.ShowAsContext();
         }
 
-        abstract protected void OnInitialize();
-        abstract protected void OnGUI();
+        protected abstract void OnInitialize();
+        protected abstract void OnGUI();
 
-        public static void RegisterVisualizer(string typeName, System.Type visualizerType)
+        public static void RegisterVisualizer(string typeName, Type visualizerType)
         {
             s_Visualizers[typeName] = visualizerType;
         }
@@ -79,11 +82,10 @@ public static bool HasVisualizer(string typeName)
 
         public static AbstractDataVisualizer CreateVisualizer(string typeName)
         {
-            System.Type type;
-            if (!s_Visualizers.TryGetValue(typeName, out type))
+            if (!s_Visualizers.TryGetValue(typeName, out var type))
                 type = typeof(FallbackDataVisualizer);
 
-            var value = System.Activator.CreateInstance(type) as AbstractDataVisualizer;
+            var value = Activator.CreateInstance(type) as AbstractDataVisualizer;
             return value;
         }
 
@@ -102,7 +104,7 @@ protected override void OnGUI()
 
     class ColorDataVisualizer : AbstractDataVisualizer
     {
-        Color m_Color;
+        Option<Color> m_Color;
 
         [InitializeOnLoadMethod]
         static void RegisterVisualizer()
@@ -110,25 +112,20 @@ static void RegisterVisualizer()
             RegisterVisualizer("UnityEngine.Color", typeof(ColorDataVisualizer));
         }
 
-        protected override void OnInitialize()
-        {
-            int sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
-
-            m_Color.r = m_MemoryReader.ReadSingle(m_Address + (uint)(sizeOfSingle * 0));
-            m_Color.g = m_MemoryReader.ReadSingle(m_Address + (uint)(sizeOfSingle * 1));
-            m_Color.b = m_MemoryReader.ReadSingle(m_Address + (uint)(sizeOfSingle * 2));
-            m_Color.a = m_MemoryReader.ReadSingle(m_Address + (uint)(sizeOfSingle * 3));
+        protected override void OnInitialize() {
+            m_Color = m_MemoryReader.ReadColor(m_Address);
         }
 
         protected override void OnGUI()
         {
-            EditorGUILayout.ColorField(m_Color);
+            if (m_Color.valueOut(out var color)) EditorGUILayout.ColorField(color);
+            else EditorGUILayout.LabelField($"Couldn't read `Color` at {m_Address:X}");
         }
     }
 
     class Color32DataVisualizer : AbstractDataVisualizer
     {
-        Color32 m_Color;
+        Option<Color32> m_Color;
 
         [InitializeOnLoadMethod]
         static void RegisterVisualizer()
@@ -138,23 +135,19 @@ static void RegisterVisualizer()
 
         protected override void OnInitialize()
         {
-            int sizeOfByte = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemByte].size;
-
-            m_Color.r = m_MemoryReader.ReadByte(m_Address + (uint)(sizeOfByte * 0));
-            m_Color.g = m_MemoryReader.ReadByte(m_Address + (uint)(sizeOfByte * 1));
-            m_Color.b = m_MemoryReader.ReadByte(m_Address + (uint)(sizeOfByte * 2));
-            m_Color.a = m_MemoryReader.ReadByte(m_Address + (uint)(sizeOfByte * 3));
+            m_Color = m_MemoryReader.ReadColor32(m_Address);
         }
 
         protected override void OnGUI()
         {
-            EditorGUILayout.ColorField(m_Color);
+            if (m_Color.valueOut(out var color)) EditorGUILayout.ColorField(color);
+            else EditorGUILayout.LabelField($"Couldn't read `Color32` at {m_Address:X}");
         }
     }
 
     class Matrix4x4DataVisualizer : AbstractDataVisualizer
     {
-        Matrix4x4 m_Matrix;
+        Option<Matrix4x4> m_Matrix;
 
         [InitializeOnLoadMethod]
         static void RegisterVisualizer()
@@ -169,25 +162,28 @@ protected override void OnInitialize()
 
         protected override void OnGUI()
         {
-            using (new EditorGUILayout.VerticalScope())
-            {
-                for (var y = 0; y < 4; ++y)
+            if (m_Matrix.valueOut(out var matrix)) {
+                using (new EditorGUILayout.VerticalScope())
                 {
-                    using (new EditorGUILayout.HorizontalScope())
+                    for (var y = 0; y < 4; ++y)
                     {
-                        for (var x = 0; x < 4; ++x)
+                        using (new EditorGUILayout.HorizontalScope())
                         {
-                            EditorGUILayout.TextField(m_Matrix[y, x].ToString());
+                            for (var x = 0; x < 4; ++x)
+                            {
+                                EditorGUILayout.TextField(matrix[y, x].ToString(CultureInfo.InvariantCulture));
+                            }
                         }
                     }
                 }
             }
+            else EditorGUILayout.LabelField($"Couldn't read `Matrix4x4` at {m_Address:X}");
         }
     }
 
     class QuaternionDataVisualizer : AbstractDataVisualizer
     {
-        Quaternion m_Quaternion;
+        Option<Quaternion> m_Quaternion;
 
         [InitializeOnLoadMethod]
         static void RegisterVisualizer()
@@ -202,9 +198,12 @@ protected override void OnInitialize()
 
         protected override void OnGUI()
         {
-            var eulerAngles = m_Quaternion.eulerAngles;
-            EditorGUILayout.Vector3Field("Euler Angles", eulerAngles);
-            //EditorGUILayout.Vector4Field("Quaternion", new Vector4(m_quaternion.x, m_quaternion.y, m_quaternion.z, m_quaternion.w));
+            if (m_Quaternion.valueOut(out var quaternion)) {
+                var eulerAngles = quaternion.eulerAngles;
+                EditorGUILayout.Vector3Field("Euler Angles", eulerAngles);
+                //EditorGUILayout.Vector4Field("Quaternion", new Vector4(m_quaternion.x, m_quaternion.y, m_quaternion.z, m_quaternion.w));
+            }
+            else EditorGUILayout.LabelField($"Couldn't read `Quaternion` at {m_Address:X}");
         }
     }
 
@@ -225,8 +224,12 @@ protected override void OnInitialize()
             var pointer = m_Address;
             if (pointer == 0)
                 m_String = "null";
-            else
-                m_String = m_MemoryReader.ReadString(pointer + (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize);
+            else {
+                m_String = 
+                    m_MemoryReader
+                        .ReadString(pointer + m_Snapshot.virtualMachineInformation.objectHeaderSize)
+                        .getOrElse("<Error while reading>");
+            }
 
             if (m_String == null)
                 m_String = "<null>";
@@ -245,7 +248,7 @@ protected override void OnGUI()
             if (m_ShortString != null)
             {
                 text = m_ShortString;
-                EditorGUILayout.HelpBox(string.Format("Displaying {0} chars only!", k_MaxStringLength), MessageType.Info);
+                EditorGUILayout.HelpBox($"Displaying {k_MaxStringLength} chars only!", MessageType.Info);
             }
 
             EditorGUILayout.TextArea(text, EditorStyles.wordWrappedLabel);
@@ -261,7 +264,7 @@ void OnMenuOpenInTextEditor()
 
     class DateTimeDataVisualizer : AbstractDataVisualizer
     {
-        System.DateTime m_DateTime;
+        Option<DateTime> m_DateTime;
 
         [InitializeOnLoadMethod]
         static void RegisterVisualizer()
@@ -271,22 +274,23 @@ static void RegisterVisualizer()
 
         protected override void OnInitialize()
         {
-            var ticks = m_MemoryReader.ReadInt64(m_Address);
-            m_DateTime = new System.DateTime(ticks);
+            m_DateTime = m_MemoryReader.ReadInt64(m_Address).map(ticks => new DateTime(ticks));
         }
 
         protected override void OnGUI()
         {
-            EditorGUILayout.LabelField(m_DateTime.ToString());
+            EditorGUILayout.LabelField(m_DateTime.fold(
+                "<error while reading>", _ => _.ToString(DateTimeFormatInfo.InvariantInfo)
+            ));
         }
     }
 
-    sealed public class DataVisualizerWindow : EditorWindow
+    public sealed class DataVisualizerWindow : EditorWindow
     {
         AbstractDataVisualizer m_Visualizer;
         Vector2 m_ScrollPosition;
 
-        public static EditorWindow CreateWindow(PackedMemorySnapshot snapshot, AbstractMemoryReader memoryReader, System.UInt64 address, PackedManagedType type)
+        public static EditorWindow CreateWindow(PackedMemorySnapshot snapshot, AbstractMemoryReader memoryReader, UInt64 address, PackedManagedType type)
         {
             var visualizer = AbstractDataVisualizer.CreateVisualizer(type.name);
             if (visualizer == null)
@@ -296,7 +300,7 @@ public static EditorWindow CreateWindow(PackedMemorySnapshot snapshot, AbstractM
             }
             visualizer.Initialize(snapshot, memoryReader, address, type);
 
-            var window = DataVisualizerWindow.CreateInstance<DataVisualizerWindow>();
+            var window = CreateInstance<DataVisualizerWindow>();
             window.SetVisualizer(visualizer);
             window.ShowUtility();
             return window;
@@ -311,10 +315,6 @@ void SetVisualizer(AbstractDataVisualizer dataVisualizer)
             titleContent = new GUIContent(m_Visualizer.title);
         }
 
-        void OnEnable()
-        {
-        }
-
         void OnGUI()
         {
             if (m_Visualizer == null)
diff --git a/Editor/Scripts/GCHandlesView/GCHandlesControl.cs b/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
index fbeca8e..7ee3404 100644
--- a/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
+++ b/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
@@ -2,17 +2,21 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
+
+using System;
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class GCHandlesControl : AbstractTreeView
     {
-        public System.Action<PackedGCHandle?> onSelectionChange;
+        public System.Action<Option<PackedGCHandle>> onSelectionChange;
 
         PackedMemorySnapshot m_Snapshot;
         int m_UniqueId = 1;
@@ -42,7 +46,7 @@ public GCHandlesControl(HeapExplorerWindow window, string editorPrefsKey, TreeVi
 
         public void Select(PackedGCHandle obj)
         {
-            var item = FindItemByAddressRecursive(rootItem, (ulong)(obj.target));
+            var item = FindItemByAddressRecursive(rootItem, obj.target);
             if (item != null)
                 SetSelection(new[] { item.id }, TreeViewSelectionOptions.RevealAndFrame | TreeViewSelectionOptions.FireSelectionChanged);
         }
@@ -81,11 +85,11 @@ protected override void OnSelectionChanged(TreeViewItem selectedItem)
             var item = selectedItem as GCHandleItem;
             if (item == null)
             {
-                onSelectionChange.Invoke(null);
+                onSelectionChange.Invoke(None._);
                 return;
             }
 
-            onSelectionChange.Invoke(item.packed);
+            onSelectionChange.Invoke(Some(item.packed));
         }
 
         public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
@@ -109,16 +113,15 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                     break;
 
                 var gcHandle = m_Snapshot.gcHandles[n];
-                var managedTypeIndex = -1;
-                if (gcHandle.managedObjectsArrayIndex >= 0)
-                    managedTypeIndex = m_Snapshot.managedObjects[gcHandle.managedObjectsArrayIndex].managedTypesArrayIndex;
+                var maybeManagedTypeIndex = gcHandle.managedObjectsArrayIndex.map(m_Snapshot, (idx, snapshot) =>
+                    idx.isStatic
+                        ? snapshot.managedStaticFields[idx.index].managedTypesArrayIndex
+                        : snapshot.managedObjects[idx.index].managedTypesArrayIndex
+                );
 
                 var targetItem = root;
-                if (managedTypeIndex >= 0)
-                {
-                    GroupItem group;
-                    if (!groupLookup.TryGetValue(managedTypeIndex, out group))
-                    {
+                {if (maybeManagedTypeIndex.valueOut(out var managedTypeIndex)) {
+                    if (!groupLookup.TryGetValue(managedTypeIndex, out var group)) {
                         group = new GroupItem
                         {
                             id = m_UniqueId++,
@@ -132,7 +135,7 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                     }
 
                     targetItem = group;
-                }
+                }}
 
                 var item = new GCHandleItem
                 {
@@ -171,7 +174,7 @@ protected override int OnSortItem(TreeViewItem aa, TreeViewItem bb)
             switch ((Column)sortingColumn)
             {
                 case Column.GCHandle:
-                    return string.Compare(itemB.typeName, itemA.typeName, true);
+                    return string.Compare(itemB.typeName, itemA.typeName, StringComparison.OrdinalIgnoreCase);
 
                 case Column.Size:
                     return itemA.size.CompareTo(itemB.size);
@@ -225,7 +228,7 @@ public override string typeName
             {
                 get
                 {
-                    return m_GCHandle.managedObject.type.name;
+                    return m_GCHandle.managedObject.fold("broken handle", _ => _.type.name);
                 }
             }
 
@@ -277,21 +280,19 @@ public override void OnGUI(Rect position, int column)
                 {
                     GUI.Box(HeEditorGUI.SpaceL(ref position, position.height), HeEditorStyles.gcHandleImage, HeEditorStyles.iconStyle);
 
-                    if (m_GCHandle.nativeObject.isValid)
-                    {
+                    {if (m_GCHandle.nativeObject.valueOut(out var nativeObject)) {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
-                    }
+                    }}
 
-                    if (m_GCHandle.managedObject.isValid)
-                    {
+                    {if (m_GCHandle.managedObject.valueOut(out var managedObject)) {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
-                    }
+                    }}
                 }
 
                 switch ((Column)column)
diff --git a/Editor/Scripts/GCHandlesView/GCHandlesView.cs b/Editor/Scripts/GCHandlesView/GCHandlesView.cs
index 9c08ad9..ee9a30d 100644
--- a/Editor/Scripts/GCHandlesView/GCHandlesView.cs
+++ b/Editor/Scripts/GCHandlesView/GCHandlesView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
@@ -15,7 +16,7 @@ public class GCHandlesView : HeapExplorerView
         GCHandlesControl m_HandlesControl;
         HeSearchField m_HandlesSearchField;
         ConnectionsView m_ConnectionsView;
-        PackedGCHandle? m_Selected;
+        Option<PackedGCHandle> m_Selected;
         RootPathView m_RootPathView;
         float m_SplitterHorz = 0.33333f;
         float m_SplitterVert = 0.32f;
@@ -68,12 +69,16 @@ protected override void OnHide()
 
         public override void RestoreCommand(GotoCommand command)
         {
-            m_HandlesControl.Select(command.toGCHandle.packed);
+            if (!command.toGCHandle.valueOut(out var gcHandle)) {
+                Debug.LogError($"{nameof(RestoreCommand)}({command}) failed: no gc handle");
+                return;
+            }
+            m_HandlesControl.Select(gcHandle.packed);
         }
 
         public override int CanProcessCommand(GotoCommand command)
         {
-            if (command.toGCHandle.isValid)
+            if (command.toGCHandle.isSome)
                 return 10;
 
             return base.CanProcessCommand(command);
@@ -81,26 +86,26 @@ public override int CanProcessCommand(GotoCommand command)
 
         public override GotoCommand GetRestoreCommand()
         {
-            if (m_Selected.HasValue)
-                return new GotoCommand(new RichGCHandle(snapshot, m_Selected.Value.gcHandlesArrayIndex));
+            if (m_Selected.valueOut(out var gcHandle))
+                return new GotoCommand(new RichGCHandle(snapshot, gcHandle.gcHandlesArrayIndex));
 
             return base.GetRestoreCommand();
         }
 
         // Called if the selection changed in the list that contains the managed objects overview.
-        void OnListViewSelectionChange(PackedGCHandle? packedGCHandle)
+        void OnListViewSelectionChange(Option<PackedGCHandle> packedGCHandle)
         {
             m_Selected = packedGCHandle;
 
-            if (!packedGCHandle.HasValue)
+            if (!packedGCHandle.valueOut(out var gcHandle))
             {
                 m_RootPathView.Clear();
                 m_ConnectionsView.Clear();
                 return;
             }
 
-            m_ConnectionsView.Inspect(packedGCHandle.Value);
-            m_RootPathView.Inspect(m_Selected.Value);
+            m_ConnectionsView.Inspect(gcHandle);
+            m_RootPathView.Inspect(gcHandle);
         }
 
         public override void OnGUI()
@@ -115,7 +120,7 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            EditorGUILayout.LabelField(string.Format("{0} GCHandle(s)", snapshot.gcHandles.Length), EditorStyles.boldLabel);
+                            EditorGUILayout.LabelField($"{snapshot.gcHandles.Length} GCHandle(s)", EditorStyles.boldLabel);
 
                             if (m_HandlesSearchField.OnToolbarGUI())
                                 m_HandlesControl.Search(m_HandlesSearchField.text);
diff --git a/Editor/Scripts/HeGlobals.cs b/Editor/Scripts/HeGlobals.cs
index a5b6ddf..3f46189 100644
--- a/Editor/Scripts/HeGlobals.cs
+++ b/Editor/Scripts/HeGlobals.cs
@@ -11,7 +11,7 @@ namespace HeapExplorer
     public static class HeGlobals
     {
         public const string k_Title = "Heap Explorer";
-        public const string k_Version = "4.1";
+        public const string k_Version = "4.2";
         public const string k_DocuUrl = "https://github.com/pschraut/UnityHeapExplorer";
         public const string k_ChangelogUrl = "https://github.com/pschraut/UnityHeapExplorer/blob/master/CHANGELOG.md";
         public const string k_ForumUrl = "https://forum.unity.com/threads/wip-heap-explorer-memory-profiler-debugger-and-analyzer-for-unity.527949/";
diff --git a/Editor/Scripts/HeapExplorerWindow.cs b/Editor/Scripts/HeapExplorerWindow.cs
index 277a026..0798273 100644
--- a/Editor/Scripts/HeapExplorerWindow.cs
+++ b/Editor/Scripts/HeapExplorerWindow.cs
@@ -9,6 +9,7 @@
 using UnityEditor;
 using System;
 using System.Threading;
+using UnityEngine.Profiling.Memory.Experimental;
 
 namespace HeapExplorer
 {
@@ -139,7 +140,8 @@ static void Create()
         {
             if (!HeEditorUtility.IsVersionOrNewer(2019, 3))
             {
-                if (EditorUtility.DisplayDialog(HeGlobals.k_Title, string.Format("{0} requires Unity 2019.3 or newer.", HeGlobals.k_Title), "Forum", "Close"))
+                if (EditorUtility.DisplayDialog(HeGlobals.k_Title,
+                        $"{HeGlobals.k_Title} requires Unity 2019.3 or newer.", "Forum", "Close"))
                     Application.OpenURL(HeGlobals.k_ForumUrl);
                 return;
             }
@@ -505,7 +507,7 @@ void DrawToolbar()
                         if (!System.IO.File.Exists(path))
                             continue;
 
-                        menu.AddItem(new GUIContent(string.Format("Recent/{0}     {1}", (n + 1), path.Replace('/', '\\'))), false, delegate (System.Object obj)
+                        menu.AddItem(new GUIContent($"Recent/{(n + 1)}     {path.Replace('/', '\\')}"), false, delegate (System.Object obj)
                         {
                             var p = obj as string;
                             LoadFromFile(p);
@@ -611,8 +613,8 @@ void DrawToolbar()
                 if (GUILayout.Button(new GUIContent("Capture", HeEditorStyles.magnifyingGlassImage), EditorStyles.toolbarDropDown, GUILayout.Width(80)))
                 {
                     var menu = new GenericMenu();
-                    menu.AddItem(new GUIContent(string.Format("Capture and Save '{0}'...", connectedProfiler)), false, CaptureAndSaveHeap);
-                    menu.AddItem(new GUIContent(string.Format("Capture and Analyze '{0}'", connectedProfiler)), false, CaptureAndAnalyzeHeap);
+                    menu.AddItem(new GUIContent($"Capture and Save '{connectedProfiler}'..."), false, CaptureAndSaveHeap);
+                    menu.AddItem(new GUIContent($"Capture and Analyze '{connectedProfiler}'"), false, CaptureAndAnalyzeHeap);
                     menu.AddSeparator("");
                     menu.AddItem(new GUIContent(string.Format("Open Profiler")), false, delegate () { HeEditorUtility.OpenProfiler(); });
                     menu.DropDown(m_CaptureToolbarButtonRect);
@@ -784,6 +786,16 @@ void Reset(bool destroy = false)
             ActivateView(null);
         }
 
+        /// <summary>
+        /// Same flags as Unity memory profiler.
+        /// </summary>
+        const CaptureFlags CAPTURE_FLAGS =
+            CaptureFlags.ManagedObjects
+            | CaptureFlags.NativeObjects
+            | CaptureFlags.NativeAllocations
+            | CaptureFlags.NativeAllocationSites
+            | CaptureFlags.NativeStackTraces;
+
         void CaptureAndSaveHeap()
         {
             if (string.IsNullOrEmpty(autoSavePath))
@@ -801,7 +813,7 @@ void CaptureAndSaveHeap()
                 m_IsCapturing = true;
 
                 string snapshotPath = System.IO.Path.ChangeExtension(path, "snapshot");
-                UnityEngine.Profiling.Memory.Experimental.MemoryProfiler.TakeSnapshot(snapshotPath, OnHeapReceivedSaveOnly);
+                MemoryProfiler.TakeSnapshot(snapshotPath, OnHeapReceivedSaveOnly, CAPTURE_FLAGS);
             }
             finally
             {
@@ -810,18 +822,26 @@ void CaptureAndSaveHeap()
             }
         }
 
-        void OnHeapReceivedSaveOnly(string path, bool captureResult)
-        {
-            EditorUtility.DisplayProgressBar(HeGlobals.k_Title, "Saving memory...", 0.5f);
+        void OnHeapReceivedSaveOnly(string path, bool captureResult) {
+            const float BASE_PROGRESS = 0.5f;
+            const float PROGRESS_LEFT = 0.4f;
+            EditorUtility.DisplayProgressBar(HeGlobals.k_Title, "Saving memory...", BASE_PROGRESS);
             try
             {
-                var args = new MemorySnapshotProcessingArgs();
-                args.source = UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path);
+                var args = new MemorySnapshotProcessingArgs(
+                    UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path),
+                    maybeUpdateUI: (stepName, index, steps) => {
+                        var percentage = (float) index / (steps - 1);
+                        var totalPercentage = BASE_PROGRESS + percentage * PROGRESS_LEFT;
+                        EditorUtility.DisplayProgressBar(HeGlobals.k_Title, stepName, totalPercentage);
+                    }
+                );
 
                 var heap = PackedMemorySnapshot.FromMemoryProfiler(args);
+                EditorUtility.DisplayProgressBar(HeGlobals.k_Title, "Saving memory to file...", 0.95f);
                 heap.SaveToFile(autoSavePath);
                 HeMruFiles.AddPath(autoSavePath);
-                ShowNotification(new GUIContent(string.Format("Memory snapshot saved as\n'{0}'", autoSavePath)));
+                ShowNotification(new GUIContent($"Memory snapshot saved as\n'{autoSavePath}'"));
             }
             catch
             {
@@ -847,7 +867,7 @@ void CaptureAndAnalyzeHeap()
                 m_IsCapturing = true;
 
                 var path = FileUtil.GetUniqueTempPathInProject();
-                UnityEngine.Profiling.Memory.Experimental.MemoryProfiler.TakeSnapshot(path, OnHeapReceived);
+                MemoryProfiler.TakeSnapshot(path, OnHeapReceived, CAPTURE_FLAGS);
             }
             finally
             {
@@ -865,41 +885,44 @@ void OnHeapReceived(string path, bool captureResult)
 
                 if (useThreads)
                 {
-                    var job = new ReceiveThreadJob
-                    {
-                        threadFunc = ReceiveHeapThreaded,
-                        snapshot = UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path)
-                };
+                    var job = new ReceiveThreadJob {
+                        threadFunc = () => ReceiveHeapThreaded(new MemorySnapshotProcessingArgs(
+                            UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path)
+                        ))
+                    };
                     ScheduleJob(job);
                 }
                 else
                 {
-                    EditorUtility.DisplayProgressBar(HeGlobals.k_Title, "Analyzing memory...", 0.75f);
+                    const float BASE_PROGRESS = 0.75f;
+                    const float PROGRESS_LEFT = 0.25f;
+                    EditorUtility.DisplayProgressBar(HeGlobals.k_Title, "Analyzing memory...", BASE_PROGRESS);
                     var sshot = UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path);
-                    ReceiveHeapThreaded(sshot);
+                    ReceiveHeapThreaded(new MemorySnapshotProcessingArgs(
+                        sshot,
+                        maybeUpdateUI: (stepName, index, steps) => {
+                            var percentage = (float) index / (steps - 1);
+                            var totalPercentage = BASE_PROGRESS + percentage * PROGRESS_LEFT;
+                            EditorUtility.DisplayProgressBar(HeGlobals.k_Title, stepName, totalPercentage);
+                        }
+                    ));
                 }
             }
             finally
             {
-                snapshotPath = string.Format("Captured Snapshot at {0}", DateTime.Now.ToShortTimeString());
+                snapshotPath = $"Captured Snapshot at {DateTime.Now.ToShortTimeString()}";
                 m_IsCapturing = false;
                 m_Repaint = true;
                 EditorUtility.ClearProgressBar();
             }
         }
 
-        void ReceiveHeapThreaded(object userData)
-        {
-            var args = new MemorySnapshotProcessingArgs();
-            args.source = userData as UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot;
-
-            try
-            {
+        void ReceiveHeapThreaded(MemorySnapshotProcessingArgs args) {
+            try {
                 m_Heap = PackedMemorySnapshot.FromMemoryProfiler(args);
                 m_Heap.Initialize();
             }
-            catch
-            {
+            catch {
                 m_CloseDueToError = true;
                 throw;
             }
@@ -943,12 +966,10 @@ public override void ThreadFunc()
 
     class ReceiveThreadJob : AbstractThreadJob
     {
-        public UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot;
-        public Action<object> threadFunc;
+        public Action threadFunc;
 
-        public override void ThreadFunc()
-        {
-            threadFunc.Invoke(snapshot);
+        public override void ThreadFunc() {
+            threadFunc.Invoke();
         }
     }
 
diff --git a/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsControl.cs b/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsControl.cs
index 0b13463..b72c48c 100644
--- a/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsControl.cs
+++ b/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsControl.cs
@@ -34,7 +34,7 @@ protected override void OnBuildTree(TreeViewItem root)
                 if (obj.address == 0)
                     continue; // points to null
 
-                if (obj.nativeObjectsArrayIndex != -1)
+                if (obj.nativeObjectsArrayIndex.isSome)
                     continue; // has a native object, thus can't be an empty shell object
 
                 var type = m_Snapshot.managedTypes[obj.managedTypesArrayIndex];
@@ -51,12 +51,14 @@ protected override void OnBuildTree(TreeViewItem root)
                 var richType = new RichManagedType(m_Snapshot, obj.managedTypesArrayIndex);
 
                 // Try to get the m_InstanceID field (only exists in editor, not in built players)
-                PackedManagedField packedField;
-                if (richType.FindField("m_InstanceID", out packedField))
-                {
+                if (richType.FindField("m_InstanceID", out var packedField)) {
+                    var instanceIDPtr = obj.address + packedField.offset;
+                    if (!memoryReader.ReadInt32(instanceIDPtr).valueOut(out var instanceID)) {
+                        m_Snapshot.Error($"Can't read 'instanceID' from address {instanceIDPtr:X}, skipping."); 
+                        continue;
+                    }
                     // The editor contains various empty shell objects whose instanceID all contain 0.
                     // I guess it's some kind of special object? In this case we just ignore them.
-                    var instanceID = memoryReader.ReadInt32(obj.address + (ulong)packedField.offset);
                     if (instanceID == 0)
                         continue;
                 }
diff --git a/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsView.cs b/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsView.cs
index 67e6b53..674a138 100644
--- a/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsView.cs
+++ b/Editor/Scripts/ManagedEmptyShellObjectsView/ManagedEmptyShellObjectsView.cs
@@ -44,7 +44,7 @@ protected override AbstractManagedObjectsControl CreateObjectsTreeView(string ed
 
         protected override void OnDrawHeader()
         {
-            var text = string.Format("{0} empty shell object(s)", m_ObjectsControl.managedObjectsCount);
+            var text = $"{m_ObjectsControl.managedObjectsCount} empty shell object(s)";
             window.SetStatusbarString(text);
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
         }
@@ -56,7 +56,7 @@ public override void OnGUI()
             var control = (ManagedEmptyShellObjectsControl)m_ObjectsControl;
             if (control.progress.value < 1)
             {
-                window.SetBusy(string.Format("Analyzing Managed Objects, {0:F0}% done", control.progress.value * 100));
+                window.SetBusy($"Analyzing Managed Objects, {control.progress.value * 100:F0}% done");
             }
         }
 
diff --git a/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsControl.cs b/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsControl.cs
index 5f53801..22de087 100644
--- a/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsControl.cs
+++ b/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsControl.cs
@@ -3,14 +3,17 @@
 // https://github.com/pschraut/UnityHeapExplorer/
 //
 //#define HEAPEXPLORER_DISPLAY_REFS
+
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class ManagedHeapSectionsControl : AbstractTreeView
     {
-        public System.Action<PackedMemorySection?> onSelectionChange;
+        public System.Action<Option<PackedMemorySection>> onSelectionChange;
 
         public int count
         {
@@ -70,12 +73,12 @@ protected override void OnSelectionChanged(TreeViewItem selectedItem)
             var item = selectedItem as HeapSectionItem;
             if (item == null)
             {
-                onSelectionChange.Invoke(null);
+                onSelectionChange.Invoke(None._);
                 return;
             }
 
             var section = m_Snapshot.managedHeapSections[item.arrayIndex];
-            onSelectionChange.Invoke(section);
+            onSelectionChange.Invoke(Some(section));
         }
 
         //public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, bool removeUnalignedSections = false)
@@ -195,7 +198,7 @@ public void Initialize(ManagedHeapSectionsControl owner, PackedMemorySnapshot sn
                 address = m_Snapshot.managedHeapSections[arrayIndex].startAddress;
                 if (m_Snapshot.managedHeapSections[arrayIndex].bytes != null)
                 {
-                    size = (ulong)m_Snapshot.managedHeapSections[arrayIndex].bytes.LongLength;
+                    size = m_Snapshot.managedHeapSections[arrayIndex].bytes.LongLength.ToULongClamped();
 
 #if HEAPEXPLORER_DISPLAY_REFS
                     m_Snapshot.GetConnectionsCount(m_Snapshot.managedHeapSections[arrayIndex], out refs);
diff --git a/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsView.cs b/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsView.cs
index a942857..421596b 100644
--- a/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsView.cs
+++ b/Editor/Scripts/ManagedHeapSectionsView/ManagedHeapSectionsView.cs
@@ -2,8 +2,8 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -83,7 +83,7 @@ void OnSaveAsFile()
                             progressUpdate = Time.realtimeSinceStartup + 0.1f;
                             if (EditorUtility.DisplayCancelableProgressBar(
                                 "Saving...",
-                                string.Format("Memory Section {0} / {1}", n+1, sections.Length),
+                                $"Memory Section {n + 1} / {sections.Length}",
                                 (n + 1.0f) / sections.Length))
                                 break;
                         }
@@ -214,7 +214,8 @@ public override void OnGUI()
                     // Managed heap fragmentation view
                     using (new EditorGUILayout.VerticalScope(HeEditorStyles.panel))
                     {
-                        var text = string.Format("{0} managed heap sections ({1}) within an {2} address space", GetMemorySections().Length, EditorUtility.FormatBytes((long)GetTotalHeapSize()), EditorUtility.FormatBytes((long)GetHeapAddressSpace()));
+                        var text =
+                            $"{GetMemorySections().Length} managed heap sections ({EditorUtility.FormatBytes((long) GetTotalHeapSize())}) within an {EditorUtility.FormatBytes((long) GetHeapAddressSpace())} address space";
                         GUILayout.Label(text, EditorStyles.boldLabel);
                         GUI.DrawTexture(GUILayoutUtility.GetRect(100, window.position.height * 0.1f, GUILayout.ExpandWidth(true)), m_HeapFragTexture, ScaleMode.StretchToFill);
                     }
@@ -251,22 +252,23 @@ public override void OnGUI()
             }
         }
 
-        void OnListViewSelectionChange(PackedMemorySection? mo)
+        void OnListViewSelectionChange(Option<PackedMemorySection> mo)
         {
-            if (!mo.HasValue)
+            if (!mo.valueOut(out var packedMemorySection))
             {
                 m_ConnectionsView.Clear();
                 return;
             }
 
-            var job = new MemorySectionFragmentationJob();
-            job.texture = m_SectionFragTexture;
-            job.snapshot = snapshot;
-            job.memorySection = mo.Value;
+            var job = new MemorySectionFragmentationJob {
+                texture = m_SectionFragTexture,
+                snapshot = snapshot,
+                memorySection = packedMemorySection
+            };
             ScheduleJob(job);
 
-            m_ConnectionsView.Inspect(mo.Value);
-            m_HexView.Inspect(snapshot, mo.Value.startAddress, mo.Value.size);
+            m_ConnectionsView.Inspect(packedMemorySection);
+            m_HexView.Inspect(snapshot, packedMemorySection.startAddress, packedMemorySection.size);
         }
 
 
@@ -362,8 +364,8 @@ public static class ManagedHeapSectionsUtility
 
         public static Color32[] GetManagedMemorySectionUsageAsTextureData(PackedMemorySnapshot snapshot, PackedMemorySection memorySection)
         {
-            List<PackedConnection> references = new List<PackedConnection>();
-            snapshot.GetConnections(memorySection, references, null);
+            var references = new List<PackedConnection.Pair>();
+            snapshot.GetConnections(memorySection, references, _ => _);
 
             var pixels = new Color32[k_TextureWidth * k_TextureHeight];
 
@@ -378,15 +380,15 @@ public static Color32[] GetManagedMemorySectionUsageAsTextureData(PackedMemorySn
                 var reference = references[n];
                 ulong address = 0;
                 ulong size = 0;
-                switch (reference.toKind)
+                switch (reference.kind)
                 {
                     case PackedConnection.Kind.Managed:
-                        size = (ulong)snapshot.managedObjects[reference.to].size;
-                        address = snapshot.managedObjects[reference.to].address;
+                        size = snapshot.managedObjects[reference.index].size.getOrElse(0);
+                        address = snapshot.managedObjects[reference.index].address;
                         break;
 
                     default:
-                        Debug.LogErrorFormat("{0} not supported yet", reference.toKind);
+                        Debug.LogErrorFormat("{0} not supported yet", reference.kind);
                         continue;
                 }
 
diff --git a/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesControl.cs b/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesControl.cs
index ec1ac41..439d481 100644
--- a/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesControl.cs
+++ b/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesControl.cs
@@ -49,7 +49,10 @@ protected override void OnBuildTree(TreeViewItem root)
                 if (type.isValueType)
                     continue;
 
-                var hash = memoryReader.ComputeObjectHash(obj.address, type);
+                if (!memoryReader.ComputeObjectHash(obj.address, type).valueOut(out var hash)) {
+                    Debug.LogError($"Can't compute object hash for object of type {type.name} at address {obj.address:X}");
+                    continue;
+                }
 
                 AbstractItem parent;
                 if (!lookup.TryGetValue(hash, out parent))
diff --git a/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesView.cs b/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesView.cs
index bd63fb5..a7f5e03 100644
--- a/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesView.cs
+++ b/Editor/Scripts/ManagedObjectDuplicatesView/ManagedObjectDuplicatesView.cs
@@ -2,12 +2,12 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
+
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
-using System;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -17,7 +17,7 @@ public class ManagedObjectDuplicatesView : HeapExplorerView
         ManagedObjectDuplicatesControl m_ObjectsControl;
         HeSearchField m_ObjectsSearchField;
         ConnectionsView m_ConnectionsView;
-        RichManagedObject m_Selected;
+        Option<RichManagedObject> m_Selected;
         RootPathView m_RootPathView;
         PropertyGridView m_PropertyGridView;
         float m_SplitterHorzPropertyGrid = 0.32f;
@@ -79,17 +79,12 @@ protected override void OnHide()
             EditorPrefs.SetFloat(GetPrefsKey(() => m_SplitterVertRootPath), m_SplitterVertRootPath);
         }
 
-        public override GotoCommand GetRestoreCommand()
-        {
-            if (m_Selected.isValid)
-                return new GotoCommand(m_Selected);
-
-            return base.GetRestoreCommand();
-        }
+        public override GotoCommand GetRestoreCommand() => 
+            m_Selected.valueOut(out var selected) ? new GotoCommand(selected) : base.GetRestoreCommand();
 
         void OnListViewSelectionChange(PackedManagedObject? item)
         {
-            m_Selected = RichManagedObject.invalid;
+            m_Selected = None._;
             if (!item.HasValue)
             {
                 m_RootPathView.Clear();
@@ -98,10 +93,11 @@ void OnListViewSelectionChange(PackedManagedObject? item)
                 return;
             }
 
-            m_Selected = new RichManagedObject(snapshot, item.Value.managedObjectsArrayIndex);
-            m_PropertyGridView.Inspect(m_Selected.packed);
-            m_ConnectionsView.Inspect(m_Selected.packed);
-            m_RootPathView.Inspect(m_Selected.packed);
+            var selected = new RichManagedObject(snapshot, item.Value.managedObjectsArrayIndex);
+            m_Selected = Some(selected);
+            m_PropertyGridView.Inspect(selected.packed);
+            m_ConnectionsView.Inspect(selected.packed);
+            m_RootPathView.Inspect(selected.packed);
         }
 
         public override void OnGUI()
@@ -117,7 +113,8 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            var text = string.Format("{0} managed object duplicate(s) wasting {1} memory", m_ObjectsControl.managedObjectsCount, EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize));
+                            var text =
+                                $"{m_ObjectsControl.managedObjectsCount} managed object duplicate(s) wasting {EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize)} memory";
                             window.SetStatusbarString(text);
 
                             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
@@ -158,7 +155,7 @@ public override void OnGUI()
 
             if (m_ObjectsControl.progress.value < 1)
             {
-                window.SetBusy(string.Format("Analyzing Managed Objects Memory, {0:F0}% done", m_ObjectsControl.progress.value * 100));
+                window.SetBusy($"Analyzing Managed Objects Memory, {m_ObjectsControl.progress.value * 100:F0}% done");
             }
         }
 
diff --git a/Editor/Scripts/ManagedObjectsView/ManagedObjectsControl.cs b/Editor/Scripts/ManagedObjectsView/ManagedObjectsControl.cs
index 90e98bb..3ca31c4 100644
--- a/Editor/Scripts/ManagedObjectsView/ManagedObjectsControl.cs
+++ b/Editor/Scripts/ManagedObjectsView/ManagedObjectsControl.cs
@@ -7,6 +7,7 @@
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.ViewConsts;
 
 namespace HeapExplorer
 {
@@ -56,7 +57,7 @@ public AbstractManagedObjectsControl(HeapExplorerWindow window, string editorPre
                 new MultiColumnHeaderState(new[]
                 {
                 new MultiColumnHeaderState.Column() { headerContent = new GUIContent("C# Type"), width = 250, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("C++ Name", "If the C# object has a C++ counterpart, display its C++ object name in this column."), width = 150, autoResize = true },
+                new MultiColumnHeaderState.Column() { headerContent = new GUIContent(COLUMN_CPP_NAME, COLUMN_CPP_NAME_DESCRIPTION), width = 150, autoResize = true },
                 new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Size"), width = 80, autoResize = true },
                 new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Count"), width = 80, autoResize = true },
                 new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Address"), width = 120, autoResize = true },
@@ -70,7 +71,7 @@ public AbstractManagedObjectsControl(HeapExplorerWindow window, string editorPre
 
         public void Select(PackedManagedObject obj)
         {
-            var item = FindItemByAddressRecursive(rootItem, (ulong)(obj.address));
+            var item = FindItemByAddressRecursive(rootItem, obj.address);
             SelectItem(item);
         }
 
@@ -308,17 +309,7 @@ public override string assembly
                 }
             }
 
-            public override string cppName
-            {
-                get
-                {
-                    var nativeObj = m_Object.nativeObject;
-                    if (nativeObj.isValid)
-                        return nativeObj.name;
-
-                    return "";
-                }
-            }
+            public override string cppName => m_Object.nativeObject.valueOut(out var nativeObj) ? nativeObj.name : "";
 
             public override long size
             {
@@ -378,11 +369,11 @@ public override void OnGUI(Rect position, int column)
                     //    }
                     //}
 
-                    if (m_Object.nativeObject.isValid)
+                    if (m_Object.nativeObject.valueOut(out var nativeObject))
                     {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_Object.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
                     }
                 }
@@ -394,8 +385,8 @@ public override void OnGUI(Rect position, int column)
                         break;
 
                     case Column.CppCounterpart:
-                        if (m_Object.nativeObject.isValid)
-                            GUI.Label(position, m_Object.nativeObject.name);
+                        if (m_Object.nativeObject.valueOut(out var nativeObject))
+                            GUI.Label(position, nativeObject.name);
                         break;
 
                     case Column.Size:
diff --git a/Editor/Scripts/ManagedObjectsView/ManagedObjectsView.cs b/Editor/Scripts/ManagedObjectsView/ManagedObjectsView.cs
index 4a73487..603d702 100644
--- a/Editor/Scripts/ManagedObjectsView/ManagedObjectsView.cs
+++ b/Editor/Scripts/ManagedObjectsView/ManagedObjectsView.cs
@@ -2,11 +2,12 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -33,7 +34,8 @@ protected override AbstractManagedObjectsControl CreateObjectsTreeView(string ed
 
         protected override void OnDrawHeader()
         {
-            var text = string.Format("{0} managed object(s), {1} memory", m_ObjectsControl.managedObjectsCount, EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize));
+            var text =
+                $"{m_ObjectsControl.managedObjectsCount} managed object(s), {EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize)} memory";
             window.SetStatusbarString(text);
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
         }
@@ -41,7 +43,8 @@ protected override void OnDrawHeader()
 
     public class ManagedDelegateTargetsControl : AbstractManagedObjectsControl
     {
-        Dictionary<int, byte> m_delegateObjectTable = new Dictionary<int, byte>(64);
+        readonly Dictionary<PackedManagedObject.ArrayIndex, byte> m_delegateObjectTable = 
+            new Dictionary<PackedManagedObject.ArrayIndex, byte>(64);
 
         public ManagedDelegateTargetsControl(HeapExplorerWindow window, string editorPrefsKey, TreeViewState state)
             : base(window, editorPrefsKey, state)
@@ -56,8 +59,7 @@ protected override void OnBeforeBuildTree()
             var reader = new MemoryReader(m_Snapshot);
             var systemDelegate = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemDelegate];
 
-            PackedManagedField field;
-            if (!systemDelegate.TryGetField("m_target", out field))
+            if (!systemDelegate.TryGetField("m_target", out var field))
                 return;
 
             // Build a table that contains indices of all objects that are the "Target" of a delegate
@@ -76,17 +78,19 @@ protected override void OnBeforeBuildTree()
                     continue;
 
                 // Read the delegate m_target pointer
-                var pointer = reader.ReadPointer(obj.address + (uint)field.offset);
+                var m_targetPtr = obj.address + field.offset;
+                if (!reader.ReadPointer(obj.address + field.offset).valueOut(out var pointer)) {
+                    Debug.LogError($"Can't read 'm_target' pointer from address {m_targetPtr:X}");
+                    continue;
+                }
                 if (pointer == 0)
                     continue;
 
                 // Try to find the managed object where m_target points to
-                var target = m_Snapshot.FindManagedObjectOfAddress(pointer);
-                if (target < 0)
-                    continue;
-
-                // We found a managed object that is referenced by a System.Delegate
-                m_delegateObjectTable[target] = 1;
+                {if (m_Snapshot.FindManagedObjectOfAddress(pointer).valueOut(out var target)) {
+                    // We found a managed object that is referenced by a System.Delegate
+                    m_delegateObjectTable[target] = 1;
+                }}
             }
         }
 
@@ -122,7 +126,8 @@ protected override AbstractManagedObjectsControl CreateObjectsTreeView(string ed
 
         protected override void OnDrawHeader()
         {
-            var text = string.Format("{0} delegate(s), {1} memory", m_ObjectsControl.managedObjectsCount, EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize));
+            var text =
+                $"{m_ObjectsControl.managedObjectsCount} delegate(s), {EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize)} memory";
             window.SetStatusbarString(text);
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
         }
@@ -163,7 +168,7 @@ public override void Awake()
 
         public override int CanProcessCommand(GotoCommand command)
         {
-            if (command.toManagedObject.isValid)
+            if (command.toManagedObject.isSome)
                 return 10;
 
             return base.CanProcessCommand(command);
@@ -176,7 +181,8 @@ protected override AbstractManagedObjectsControl CreateObjectsTreeView(string ed
 
         protected override void OnDrawHeader()
         {
-            var text = string.Format("{0} managed object(s), {1} memory", m_ObjectsControl.managedObjectsCount, EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize));
+            var text =
+                $"{m_ObjectsControl.managedObjectsCount} managed object(s), {EditorUtility.FormatBytes(m_ObjectsControl.managedObjectsSize)} memory";
             window.SetStatusbarString(text);
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
         }
@@ -201,7 +207,7 @@ public abstract class AbstractManagedObjectsView : HeapExplorerView
 
         HeSearchField m_ObjectsSearchField;
         ConnectionsView m_ConnectionsView;
-        RichManagedObject m_Selected;
+        Option<RichManagedObject> m_Selected;
         RootPathView m_RootPathView;
         PropertyGridView m_PropertyGridView;
         float m_SplitterHorzPropertyGrid = 0.32f;
@@ -258,23 +264,23 @@ protected override void OnHide()
 
         public override void RestoreCommand(GotoCommand command)
         {
-            if (command.toManagedObject.isValid)
-                m_ObjectsControl.Select(command.toManagedObject.packed);
+            {if (command.toManagedObject.valueOut(out var managedObject))
+                m_ObjectsControl.Select(managedObject.packed);}
 
             base.RestoreCommand(command);
         }
 
         public override GotoCommand GetRestoreCommand()
         {
-            if (m_Selected.isValid)
-                return new GotoCommand(m_Selected);
+            if (m_Selected.valueOut(out var selected))
+                return new GotoCommand(selected);
 
             return base.GetRestoreCommand();
         }
 
         void OnListViewSelectionChange(PackedManagedObject? item)
         {
-            m_Selected = RichManagedObject.invalid;
+            m_Selected = None._;
             if (!item.HasValue)
             {
                 m_RootPathView.Clear();
@@ -283,10 +289,11 @@ void OnListViewSelectionChange(PackedManagedObject? item)
                 return;
             }
 
-            m_Selected = new RichManagedObject(snapshot, item.Value.managedObjectsArrayIndex);
-            m_ConnectionsView.Inspect(m_Selected.packed);
-            m_PropertyGridView.Inspect(m_Selected.packed);
-            m_RootPathView.Inspect(m_Selected.packed);
+            var selected = new RichManagedObject(snapshot, item.Value.managedObjectsArrayIndex);
+            m_Selected = Some(selected);
+            m_ConnectionsView.Inspect(selected.packed);
+            m_PropertyGridView.Inspect(selected.packed);
+            m_RootPathView.Inspect(selected.packed);
         }
 
         public override void OnGUI()
diff --git a/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs b/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
index bc18ef9..4635b47 100644
--- a/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
+++ b/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
@@ -2,17 +2,19 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
+
+using System;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class ManagedTypesControl : AbstractTreeView
     {
-        public System.Action<PackedManagedType?> onSelectionChange;
+        public Action<Option<PackedManagedType>> onSelectionChange;
 
         PackedMemorySnapshot m_Snapshot;
         int m_UniqueId = 1;
@@ -50,11 +52,11 @@ protected override void OnSelectionChanged(TreeViewItem selectedItem)
             var item = selectedItem as ManagedTypeItem;
             if (item == null)
             {
-                onSelectionChange.Invoke(null);
+                onSelectionChange.Invoke(None._);
                 return;
             }
 
-            onSelectionChange.Invoke(item.packed);
+            onSelectionChange.Invoke(Some(item.packed));
         }
 
         public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
@@ -69,6 +71,7 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                 return root;
             }
 
+            var cycleTracker = new CycleTracker<int>();
             for (int n = 0, nend = m_Snapshot.managedTypes.Length; n < nend; ++n)
             {
                 if (window.isClosing) // the window is closing
@@ -87,18 +90,19 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                 root.AddChild(item);
 
                 // Add its base-classes
-                var loopGuard = 0;
                 var baseType = type;
                 var itemDepth = 1;
-                while (baseType.baseOrElementTypeIndex != -1)
-                {
-                    if (++loopGuard > 128)
-                    {
-                        Debug.LogErrorFormat("Loop-guard kicked in for managed type '{0}'.", type.name);
+                cycleTracker.markStartOfSearch();
+                {while (baseType.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex)) {
+                    if (cycleTracker.markIteration(baseOrElementTypeIndex)) {
+                        cycleTracker.reportCycle(
+                            $"{nameof(BuildTree)}()", baseOrElementTypeIndex, 
+                            idx => m_Snapshot.managedTypes[idx].ToString()
+                        );
                         break;
                     }
 
-                    baseType = m_Snapshot.managedTypes[baseType.baseOrElementTypeIndex];
+                    baseType = m_Snapshot.managedTypes[baseOrElementTypeIndex];
 
                     var baseItem = new ManagedTypeItem
                     {
@@ -109,7 +113,7 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                     };
                     baseItem.Initialize(this, m_Snapshot, baseType.managedTypesArrayIndex);
                     item.AddChild(baseItem);
-                }
+                }}
             }
 
             // remove groups if it contains one item only
@@ -146,11 +150,11 @@ protected override int OnSortItem(TreeViewItem aa, TreeViewItem bb)
             switch ((Column)sortingColumn)
             {
                 case Column.Name:
-                    return string.Compare(itemB.typeName, itemA.typeName, true);
+                    return string.Compare(itemB.typeName, itemA.typeName, StringComparison.OrdinalIgnoreCase);
                 case Column.ValueType:
                     return itemA.isValueType.CompareTo(itemB.isValueType);
                 case Column.AssemblyName:
-                    return string.Compare(itemB.assemblyName, itemA.assemblyName, true);
+                    return string.Compare(itemB.assemblyName, itemA.assemblyName, StringComparison.OrdinalIgnoreCase);
             }
 
             return 0;
diff --git a/Editor/Scripts/ManagedTypesView/ManagedTypesView.cs b/Editor/Scripts/ManagedTypesView/ManagedTypesView.cs
index 5fbec94..d7ff920 100644
--- a/Editor/Scripts/ManagedTypesView/ManagedTypesView.cs
+++ b/Editor/Scripts/ManagedTypesView/ManagedTypesView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
@@ -14,7 +15,7 @@ public class ManagedTypesView : HeapExplorerView
     {
         ManagedTypesControl m_TypesControl;
         HeSearchField m_TypesSearchField;
-        PackedManagedType? m_Selected;
+        Option<PackedManagedType> m_Selected;
         float m_SplitterHorz = 0.33333f;
         float m_SplitterVert = 0.32f;
 
@@ -65,7 +66,7 @@ public override void RestoreCommand(GotoCommand command)
 
         //public override int CanProcessCommand(GotoCommand command)
         //{
-        //    if (command.toGCHandle.isValid)
+        //    if (command.toGCHandle.HasValue)
         //        return 10;
 
         //    return base.CanProcessCommand(command);
@@ -80,14 +81,9 @@ public override void RestoreCommand(GotoCommand command)
         //}
 
         // Called if the selection changed in the list that contains the managed objects overview.
-        void OnListViewSelectionChange(PackedManagedType? type)
+        void OnListViewSelectionChange(Option<PackedManagedType> type)
         {
             m_Selected = type;
-
-            if (!type.HasValue)
-            {
-                return;
-            }
         }
 
         public override void OnGUI()
@@ -102,7 +98,7 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            EditorGUILayout.LabelField(string.Format("{0} C# Type(s)", snapshot.managedTypes.Length), EditorStyles.boldLabel);
+                            EditorGUILayout.LabelField($"{snapshot.managedTypes.Length} C# Type(s)", EditorStyles.boldLabel);
 
                             if (m_TypesSearchField.OnToolbarGUI())
                                 m_TypesControl.Search(m_TypesSearchField.text);
diff --git a/Editor/Scripts/MemoryReader.cs b/Editor/Scripts/MemoryReader.cs
index a8daa4a..b410cd9 100644
--- a/Editor/Scripts/MemoryReader.cs
+++ b/Editor/Scripts/MemoryReader.cs
@@ -2,10 +2,11 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
+
+using System.Globalization;
+using HeapExplorer.Utilities;
 using UnityEngine;
-using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -16,26 +17,29 @@ public MemoryReader(PackedMemorySnapshot snapshot)
         {
         }
 
-        // returns the offset in the m_memorySection.bytes[] array of the specified address,
-        // or -1 if the address cannot be read.
-        protected override int TryBeginRead(System.UInt64 address)
+        /// <summary>
+        /// Returns the offset in the >m_memorySection.bytes[] array of the specified address,
+        /// or `None` if the address cannot be read.
+        /// </summary>
+        /// <param name="address"></param>
+        /// <returns></returns>
+        protected override Option<int> TryBeginRead(ulong address)
         {
             // trying to access null?
-            if (address == 0)
-                return -1;
+            if (address == 0) return Utils.zeroAddressAccessError<int>(nameof(address));
 
             // check if address still in the memory section we have already
-            if (address >= m_StartAddress && address < m_EndAddress)
-            {
-                return (int)(address - m_StartAddress);
+            if (address >= m_StartAddress && address < m_EndAddress) {
+                return Some((int)(address - m_StartAddress));
             }
 
             // it is a new section, try to find it
-            var heapIndex = m_Snapshot.FindHeapOfAddress(address);
-            if (heapIndex == -1)
-            {
-                Debug.LogWarningFormat("HeapExplorer: Heap at {0:X} not found. Haven't figured out why this happens yet. Perhaps related to .NET4 ScriptingRuntime?", address);
-                return -1;
+            if (!m_Snapshot.FindHeapOfAddress(address).valueOut(out var heapIndex)) {
+                Debug.LogWarning(
+                    $"HeapExplorer: Heap at address='{address:X}' not found. Haven't figured out why this happens yet. "
+                    + "Perhaps related to .NET4 ScriptingRuntime?"
+                );
+                return None._;
             }
 
             // setup new section
@@ -45,48 +49,49 @@ protected override int TryBeginRead(System.UInt64 address)
             m_Bytes = memorySection.bytes;
 
             //Debug.LogFormat("accessing heap {0:X}", address);
-            return (int)(address - m_StartAddress);
+            return Some((int)(address - m_StartAddress));
         }
     }
 
-
     public class StaticMemoryReader : AbstractMemoryReader
     {
-        public StaticMemoryReader(PackedMemorySnapshot snapshot, System.Byte[] staticBytes)
+        public StaticMemoryReader(PackedMemorySnapshot snapshot, byte[] staticBytes)
             : base(snapshot)
         {
             m_Bytes = staticBytes;
         }
 
-        // returns the offset in the m_memorySection.bytes[] array of the specified address,
-        // or -1 if the address cannot be read.
-        protected override int TryBeginRead(System.UInt64 address)
+        /// <summary>
+        /// returns the offset in the m_memorySection.bytes[] array of the specified address,
+        /// or `None` if the address cannot be read.
+        /// </summary>
+        protected override Option<int> TryBeginRead(ulong address)
         {
             // trying to access null?
             if (m_Bytes == null || m_Bytes.LongLength == 0 || address >= (ulong)m_Bytes.LongLength)
-                return -1;
+                return None._;
 
             // check if address still in the memory section we have already
             if (address >= m_StartAddress && address < m_EndAddress)
             {
-                return (int)(address);
+                return Some((int)address);
             }
 
             // setup new section
             m_StartAddress = 0;
             m_EndAddress = m_StartAddress + (ulong)m_Bytes.LongLength;
 
-            return (int)(address);
+            return Some((int)address);
         }
     }
 
     abstract public class AbstractMemoryReader
     {
         protected PackedMemorySnapshot m_Snapshot;
-        protected System.Byte[] m_Bytes;
-        protected System.UInt64 m_StartAddress = System.UInt64.MaxValue;
-        protected System.UInt64 m_EndAddress = System.UInt64.MaxValue;
-        protected System.Int32 m_RecursionGuard;
+        protected byte[] m_Bytes;
+        protected ulong m_StartAddress = ulong.MaxValue;
+        protected ulong m_EndAddress = ulong.MaxValue;
+        protected int m_RecursionGuard;
         protected System.Text.StringBuilder m_StringBuilder = new System.Text.StringBuilder(128);
         protected System.Security.Cryptography.MD5 m_Hasher;
 
@@ -95,82 +100,58 @@ protected AbstractMemoryReader(PackedMemorySnapshot snapshot)
             m_Snapshot = snapshot;
         }
 
-        protected abstract int TryBeginRead(System.UInt64 address);
+        protected abstract Option<int> TryBeginRead(ulong address);
 
-        public System.SByte ReadSByte(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.SByte);
+        public Option<byte> ReadByte(ulong address) => 
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => bytes[offset]);
 
-            var value = (System.SByte)m_Bytes[offset];
-            return value;
-        }
+        public Option<char> ReadChar(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToChar(bytes, offset));
 
-        public System.Byte ReadByte(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Byte);
+        public Option<bool> ReadBoolean(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToBoolean(bytes, offset));
 
-            var value = m_Bytes[offset];
-            return value;
-        }
+        public Option<float> ReadSingle(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToSingle(bytes, offset));
 
-        public System.Char ReadChar(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Char);
-
-            var value = System.BitConverter.ToChar(m_Bytes, offset);
-            return value;
-        }
-
-        public System.Boolean ReadBoolean(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Boolean);
+        public Option<Quaternion> ReadQuaternion(ulong address) {
+            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
 
-            var value = System.BitConverter.ToBoolean(m_Bytes, offset);
-            return value;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 0)).valueOut(out var x)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 1)).valueOut(out var y)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 2)).valueOut(out var z)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 3)).valueOut(out var w)) return None._;
+            
+            var value = new Quaternion { x = x, y = y, z = z, w = w };
+            return Some(value);
         }
 
-        public System.Single ReadSingle(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Single);
+        public Option<Color> ReadColor(ulong address) {
+            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
 
-            var value = System.BitConverter.ToSingle(m_Bytes, offset);
-            return value;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 0)).valueOut(out var r)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 1)).valueOut(out var g)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 2)).valueOut(out var b)) return None._;
+            if (!ReadSingle(address + (uint) (sizeOfSingle * 3)).valueOut(out var a)) return None._;
+            
+            var value = new Color(r, g, b, a);
+            return Some(value);
         }
 
-        public UnityEngine.Quaternion ReadQuaternion(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(Quaternion);
+        public Option<Color32> ReadColor32(ulong address) {
+            var sizeOfByte = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemByte].size;
 
-            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
-            var value = new Quaternion()
-            {
-                x = ReadSingle(address + (uint)(sizeOfSingle * 0)),
-                y = ReadSingle(address + (uint)(sizeOfSingle * 1)),
-                z = ReadSingle(address + (uint)(sizeOfSingle * 2)),
-                w = ReadSingle(address + (uint)(sizeOfSingle * 3))
-            };
-
-            return value;
+            if (!ReadByte(address + (uint) (sizeOfByte * 0)).valueOut(out var r)) return None._;
+            if (!ReadByte(address + (uint) (sizeOfByte * 1)).valueOut(out var g)) return None._;
+            if (!ReadByte(address + (uint) (sizeOfByte * 2)).valueOut(out var b)) return None._;
+            if (!ReadByte(address + (uint) (sizeOfByte * 3)).valueOut(out var a)) return None._;
+            
+            var value = new Color32(r, g, b, a);
+            return Some(value);
         }
 
-        public UnityEngine.Matrix4x4 ReadMatrix4x4(System.UInt64 address)
+        public Option<Matrix4x4> ReadMatrix4x4(ulong address)
         {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(Matrix4x4);
-
             var value = new Matrix4x4();
 
             var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
@@ -179,29 +160,21 @@ public UnityEngine.Matrix4x4 ReadMatrix4x4(System.UInt64 address)
             {
                 for (var x = 0; x < 4; ++x)
                 {
-                    value[y, x] = ReadSingle(address + (uint)(sizeOfSingle * element));
+                    if (!ReadSingle(address + (uint)(sizeOfSingle * element)).valueOut(out var single))
+                        return None._;
+                    value[y, x] = single;
                     element++;
                 }
             }
 
-            return value;
+            return Some(value);
         }
 
-        public System.Double ReadDouble(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Double);
+        public Option<double> ReadDouble(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToDouble(bytes, offset));
 
-            var value = System.BitConverter.ToDouble(m_Bytes, offset);
-            return value;
-        }
-
-        public System.Decimal ReadDecimal(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Decimal);
+        public Option<decimal> ReadDecimal(ulong address) {
+            if (!TryBeginRead(address).valueOut(out var offset)) return None._;
 
             // The lo, mid, hi, and flags fields contain the representation of the
             // Decimal value. The lo, mid, and hi fields contain the 96-bit integer
@@ -234,76 +207,31 @@ public System.Decimal ReadDecimal(System.UInt64 address)
             var isNegative = (flags & SignMask) != 0;
             var scale = (flags & ScaleMask) >> ScaleShift;
 
-            return new System.Decimal(lo, mid, hi, isNegative, (byte)scale);
-        }
-
-        public System.Int16 ReadInt16(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Int16);
-
-            var value = System.BitConverter.ToInt16(m_Bytes, offset);
-            return value;
-        }
-
-        public System.UInt16 ReadUInt16(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.UInt16);
-
-            var value = System.BitConverter.ToUInt16(m_Bytes, offset);
-            return value;
+            return Some(new decimal(lo, mid, hi, isNegative, (byte)scale));
         }
 
-        public System.Int32 ReadInt32(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Int32);
-
-            var value = System.BitConverter.ToInt32(m_Bytes, offset);
-            return value;
-        }
-
-        public System.UInt32 ReadUInt32(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.UInt32);
-
-            var value = System.BitConverter.ToUInt32(m_Bytes, offset);
-            return value;
-        }
+        public Option<short> ReadInt16(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToInt16(bytes, offset));
 
-        public System.Int64 ReadInt64(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.Int64);
+        public Option<ushort> ReadUInt16(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToUInt16(bytes, offset));
 
-            var value = System.BitConverter.ToInt64(m_Bytes, offset);
-            return value;
-        }
+        public Option<int> ReadInt32(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToInt32(bytes, offset));
 
-        public System.UInt64 ReadUInt64(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.UInt64);
+        public Option<uint> ReadUInt32(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToUInt32(bytes, offset));
 
-            var value = System.BitConverter.ToUInt64(m_Bytes, offset);
-            return value;
-        }
+        public Option<long> ReadInt64(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToInt64(bytes, offset));
 
-        public System.UInt64 ReadPointer(System.UInt64 address)
-        {
-            if (m_Snapshot.virtualMachineInformation.pointerSize == 8)
-                return ReadUInt64(address);
+        public Option<ulong> ReadUInt64(ulong address) =>
+            TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToUInt64(bytes, offset));
 
-            return ReadUInt32(address);
-        }
+        public Option<ulong> ReadPointer(ulong address) => 
+            m_Snapshot.virtualMachineInformation.pointerSize == PointerSize._64Bit 
+                ? ReadUInt64(address) 
+                : ReadUInt32(address).map(value => (ulong) value);
 
         //public Vector2 ReadVector2(System.UInt64 address)
         //{
@@ -320,32 +248,40 @@ public System.UInt64 ReadPointer(System.UInt64 address)
         //    return value;
         //}
 
-        public System.String ReadString(System.UInt64 address)
+        public Option<string> ReadString(ulong address)
         {
-            // strings differ from any other data type in the CLR (other than arrays) in that their size isn�t fixed.
-            // Normally the .NET GC knows the size of an object when it�s being allocated, because it�s based on the
-            // size of the fields/properties within the object and they don�t change. However in .NET a string object
-            // doesn�t contain a pointer to the actual string data, which is then stored elsewhere on the heap.
+            // strings differ from any other data type in the CLR (other than arrays) in that their size isn�t fixed.
+            // Normally the .NET GC knows the size of an object when it�s being allocated, because it�s based on the
+            // size of the fields/properties within the object and they don�t change. However in .NET a string object
+            // doesn�t contain a pointer to the actual string data, which is then stored elsewhere on the heap.
             // That raw data, the actual bytes that make up the text are contained within the string object itself.
             // http://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/
 
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return default(System.String);
+            if (!TryBeginRead(address).valueOut(out var offset)) return None._;
 
             // http://referencesource.microsoft.com/#mscorlib/system/string.cs
-            var length = ReadInt32(address);
-            if (length == 0)
-                return "";
+            if (!ReadInt32(address).valueOut(out var length)) {
+                Debug.LogError($"Can't determine length of a string at address {address:X}, offset={offset}");
+                return None._;
+            }
+            
+            if (length == 0) return Some("");
 
-            if (length < 0)
-                return "<error:length lesser 0>";
+            if (length < 0) {
+                Debug.LogError($"Length of a string at address {address:X}, offset={offset} is less than 0! length={length}");
+                return None._;
+            }
 
             const int kMaxStringLength = 1024 * 1024 * 10;
-            if (length > kMaxStringLength)
-                return string.Format("<error: length greater {0} bytes>", kMaxStringLength);
+            if (length > kMaxStringLength) {
+                Debug.LogError(
+                    $"Length of a string at address {address:X}, offset={offset} is greater than {kMaxStringLength}! "
+                    + $"length={length}"
+                );
+                return None._;
+            }
 
-            offset += sizeof(System.Int32); // the length data aka sizeof(int)
+            offset += sizeof(int); // the length data aka sizeof(int)
             length *= sizeof(char); // length is specified in characters, but each char is 2 bytes wide
 
             // In one memory snapshot, it occured that a 1mb StringBuffer wasn't entirely available in m_bytes.
@@ -354,88 +290,93 @@ public System.String ReadString(System.UInt64 address)
             {
                 var wantedLength = length;
                 length = m_Bytes.Length - offset;
-                Debug.LogErrorFormat("Cannot read entire string 0x{0:X}. The wanted length in bytes is {1}, but the memory segment holds {2} bytes only.\n{3}...", address, wantedLength, length, System.Text.Encoding.Unicode.GetString(m_Bytes, offset, Mathf.Min(length, 32)));
+                Debug.LogErrorFormat(
+                    "Cannot read entire string 0x{0:X}. The wanted length in bytes is {1}, but the memory segment "
+                    + "holds {2} bytes only.\n{3}...", 
+                    address, wantedLength, length, System.Text.Encoding.Unicode.GetString(m_Bytes, offset, Mathf.Min(length, 32))
+                );
             }
 
 
             var value = System.Text.Encoding.Unicode.GetString(m_Bytes, offset, length);
-            return value;
+            return Some(value);
         }
 
-        // Gets the number of characters in the string at the specified address.
-        public int ReadStringLength(System.UInt64 address)
-        {
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return 0;
-
-            var length = ReadInt32(address);
-            if (length <= 0)
-                return 0;
-
-            return length;
-        }
+        /// <summary>
+        /// Gets the number of characters in the string at the specified address.
+        /// </summary>
+        public Option<int> ReadStringLength(ulong address) => ReadInt32(address);
 
         /// <summary>
         /// Computes a checksum of the managed object specified at 'address'.
         /// This is used to find object duplicates.
         /// </summary>
-        public UnityEngine.Hash128 ComputeObjectHash(System.UInt64 address, PackedManagedType type)
+        public Option<Hash128> ComputeObjectHash(ulong address, PackedManagedType type)
         {
             if (m_Hasher == null)
                 m_Hasher = System.Security.Cryptography.MD5.Create();
 
-            var content = ReadObjectBytes(address, type);
+            if (!ReadObjectBytes(address, type).valueOut(out var content)) return None._;
             if (content.Count == 0)
-                return new Hash128();
+                return Some(new Hash128());
 
             var bytes = m_Hasher.ComputeHash(content.Array, content.Offset, content.Count);
-            if (bytes.Length != 16)
-                return new Hash128();
+            if (bytes.Length != 16) {
+                Debug.LogError($"Expected hash for address {address:X} to be 16 bytes, but it was {bytes.Length} bytes!");
+                return None._;
+            }
 
             var v0 = (uint)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24);
             var v1 = (uint)(bytes[4] << 32 | bytes[5] << 40 | bytes[6] << 48 | bytes[7] << 56);
             var v2 = (uint)(bytes[8] | bytes[9] << 8 | bytes[10] << 16 | bytes[11] << 24);
             var v3 = (uint)(bytes[12] << 32 | bytes[13] << 40 | bytes[14] << 48 | bytes[15] << 56);
 
-            return new Hash128(v0, v1, v2, v3);
+            return Some(new Hash128(v0, v1, v2, v3));
         }
 
         // address = object address (start of header)
-        System.ArraySegment<byte> ReadObjectBytes(System.UInt64 address, PackedManagedType typeDescription)
-        {
-            var size = ReadObjectSize(address, typeDescription);
-            if (size <= 0)
-                return new System.ArraySegment<byte>();
+        Option<System.ArraySegment<byte>> ReadObjectBytes(ulong address, PackedManagedType typeDescription) {
+            if (!ReadObjectSize(address, typeDescription).valueOut(out var sizeP)) return None._;
+            var size = sizeP.asInt;
+            if (size <= 0) {
+                Debug.LogError($"Object size for object at address {address:X} is 0 or less! (size={size})");
+                return None._;
+            }
 
-            var offset = TryBeginRead(address);
-            if (offset < 0)
-                return new System.ArraySegment<byte>();
+            if (!TryBeginRead(address).valueOut(out var offset)) return None._;
+            if (offset < 0) {
+                Debug.LogError($"Object offset for object at address {address:X} is negative! (offset={offset})");
+                return None._;
+            }
 
             // Unity bug? For a reason that I do not understand, sometimes a memory segment is smaller
             // than the actual size of an object. In order to workaround this issue, we make sure to never
             // try to read more data from the segment than is available.
-            if ((m_Bytes.Length - offset) < size)
+            if (m_Bytes.Length - offset < size)
             {
                 //var wantedLength = size;
                 size = m_Bytes.Length - offset;
                 //Debug.LogErrorFormat("Cannot read entire string 0x{0:X}. The requested length in bytes is {1}, but the memory segment holds {2} bytes only.\n{3}...", address, wantedLength, size, System.Text.Encoding.Unicode.GetString(m_bytes, offset, Mathf.Min(size, 32)));
-                if (size <= 0)
-                    return new System.ArraySegment<byte>();
+                if (size <= 0) {
+                    Debug.LogError($"Object size for object at address {address:X} is 0 or less! (size={size})");
+                    return None._;
+                }
             }
 
             var segment = new System.ArraySegment<byte>(m_Bytes, offset, size);
-            return segment;
+            return Some(segment);
         }
 
-        public int ReadObjectSize(System.UInt64 address, PackedManagedType typeDescription)
+        public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescription)
         {
             // System.Array
             // Do not display its pointer-size, but the actual size of its content.
             if (typeDescription.isArray)
             {
-                if (typeDescription.baseOrElementTypeIndex < 0 || typeDescription.baseOrElementTypeIndex >= m_Snapshot.managedTypes.Length)
-                {
+                if (
+                    !typeDescription.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex) 
+                    || baseOrElementTypeIndex >= m_Snapshot.managedTypes.Length
+                ) {
                     var details = "";
                     details = "arrayRank=" + typeDescription.arrayRank + ", " +
                         "isArray=" + typeDescription.isArray + ", " +
@@ -445,65 +386,72 @@ public int ReadObjectSize(System.UInt64 address, PackedManagedType typeDescripti
                         "isValueType=" + typeDescription.isValueType;
 
                     Debug.LogErrorFormat("ERROR: '{0}.baseOrElementTypeIndex' = {1} is out of range, ignoring. Details in second line\n{2}", typeDescription.name, typeDescription.baseOrElementTypeIndex, details);
-                    return 1;
+                    return Some(PInt._1);
                 }
 
-                var arrayLength = ReadArrayLength(address, typeDescription);
-                var elementType = m_Snapshot.managedTypes[typeDescription.baseOrElementTypeIndex];
-                var elementSize = elementType.isValueType ? elementType.size : m_Snapshot.virtualMachineInformation.pointerSize;
+                if (!ReadArrayLength(address, typeDescription).valueOut(out var arrayLength)) return None._;
+                var elementType = m_Snapshot.managedTypes[baseOrElementTypeIndex];
+                var elementSize = elementType.isValueType ? elementType.size.asInt : m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes();
 
-                var size = m_Snapshot.virtualMachineInformation.arrayHeaderSize;
+                var size = m_Snapshot.virtualMachineInformation.arrayHeaderSize.asInt;
                 size += elementSize * arrayLength;
-                return size;
+                return Some(PInt.createOrThrow(size));
             }
 
             // System.String
             if (typeDescription.managedTypesArrayIndex == m_Snapshot.coreTypes.systemString)
             {
-                var size = m_Snapshot.virtualMachineInformation.objectHeaderSize;
-                size += sizeof(System.Int32); // string length
-                size += ReadStringLength(address + (uint)m_Snapshot.virtualMachineInformation.objectHeaderSize) * sizeof(char);
+                var size = m_Snapshot.virtualMachineInformation.objectHeaderSize.asInt;
+                size += sizeof(int); // string length
+                var maybeStringLength = ReadStringLength(address + m_Snapshot.virtualMachineInformation.objectHeaderSize);
+                if (!maybeStringLength.valueOut(out var stringLength)) return None._;
+                size += stringLength * sizeof(char);
                 size += 2; // two null terminators aka \0\0
-                return size;
+                return Some(PInt.createOrThrow(size));
             }
 
-            return typeDescription.size;
+            return Some(typeDescription.size);
         }
 
-        public int ReadArrayLength(System.UInt64 address, PackedManagedType arrayType)
+        public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType)
         {
             var vm = m_Snapshot.virtualMachineInformation;
 
-            var bounds = ReadPointer(address + (ulong)vm.arrayBoundsOffsetInHeader);
+            if (!ReadPointer(address + vm.arrayBoundsOffsetInHeader).valueOut(out var bounds)) return None._;
             if (bounds == 0)
-                return (int)ReadPointer(address + (ulong)vm.arraySizeOffsetInHeader);
+                return ReadPointer(address + vm.arraySizeOffsetInHeader).map(v => (int)v);
 
             int length = 1;
             for (int i = 0; i != arrayType.arrayRank; i++)
             {
-                var ptr = bounds + (ulong)(i * vm.pointerSize);
-                length *= (int)ReadPointer(ptr);
+                var ptr = bounds + (ulong)(i * vm.pointerSize.sizeInBytes());
+                if (!ReadPointer(ptr).valueOut(out var value)) return None._;
+                length *= (int)value;
             }
-            return length;
+            return Some(length);
         }
 
-        public int ReadArrayLength(System.UInt64 address, PackedManagedType arrayType, int dimension)
+        public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType, int dimension)
         {
-            if (dimension >= arrayType.arrayRank)
-                return 0;
+            if (dimension >= arrayType.arrayRank) {
+                Debug.LogError(
+                    $"Trying to read dimension {dimension} while the array rank is {arrayType.arrayRank} for array at "
+                    + $"address {address:X} of type '{arrayType.name}'. Returning `None`."
+                );
+                return None._;
+            }
 
             var vm = m_Snapshot.virtualMachineInformation;
 
-            var bounds = ReadPointer(address + (ulong)vm.arrayBoundsOffsetInHeader);
+            if (!ReadPointer(address + vm.arrayBoundsOffsetInHeader).valueOut(out var bounds)) return None._;
             if (bounds == 0)
-                return (int)ReadPointer(address + (ulong)vm.arraySizeOffsetInHeader);
+                return ReadPointer(address + vm.arraySizeOffsetInHeader).map(v => (int)v);
 
-            var pointer = bounds + (ulong)(dimension * vm.pointerSize);
-            var length = (int)ReadPointer(pointer);
-            return length;
+            var pointer = bounds + (ulong)(dimension * vm.pointerSize.sizeInBytes());
+            return ReadPointer(pointer).map(v => (int)v);
         }
 
-        public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType type)
+        public Option<string> ReadFieldValueAsString(ulong address, PackedManagedType type)
         {
             ///////////////////////////////////////////////////////////////////
             // PRIMITIVE TYPES
@@ -512,85 +460,82 @@ public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType ty
             ///////////////////////////////////////////////////////////////////
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemByte)
-                return string.Format(StringFormat.Unsigned, ReadByte(address));
+                return Some(string.Format(StringFormat.Unsigned, ReadByte(address)));
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemSByte)
-                return ReadByte(address).ToString();
+                return ReadByte(address).map(v => v.ToString());
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemChar)
-                return string.Format("'{0}'", ReadChar(address));
+                return ReadChar(address).map(c => $"'{c}'");
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemBoolean)
-                return ReadBoolean(address).ToString();
+                return ReadBoolean(address).map(v => v.ToString());
 
-            if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemSingle)
-            {
-                var v = ReadSingle(address);
+            if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemSingle) {
+                if (!ReadSingle(address).valueOut(out var v)) return None._;
 
-                if (float.IsNaN(v)) return "float.NaN";
-                if (v == float.MinValue) return string.Format("float.MinValue ({0:E})", v);
-                if (v == float.MaxValue) return string.Format("float.MaxValue ({0:E})", v);
-                if (float.IsPositiveInfinity(v)) return string.Format("float.PositiveInfinity ({0:E})", v);
-                if (float.IsNegativeInfinity(v)) return string.Format("float.NegativeInfinity ({0:E})", v);
-                if (v > 10000000 || v < -10000000) return v.ToString("E"); // If it's a big number, use scientified notation
+                if (float.IsNaN(v)) return Some("float.NaN");
+                if (v == float.MinValue) return Some($"float.MinValue ({v:E})");
+                if (v == float.MaxValue) return Some($"float.MaxValue ({v:E})");
+                if (float.IsPositiveInfinity(v)) return Some($"float.PositiveInfinity ({v:E})");
+                if (float.IsNegativeInfinity(v)) return Some($"float.NegativeInfinity ({v:E})");
+                if (v > 10000000 || v < -10000000) return Some(v.ToString("E")); // If it's a big number, use scientified notation
 
-                return v.ToString("F");
+                return Some(v.ToString("F"));
             }
 
-            if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemDouble)
-            {
-                var v = ReadDouble(address);
+            if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemDouble) {
+                if (!ReadDouble(address).valueOut(out var v)) return None._;
 
-                if (double.IsNaN(v)) return "double.NaN";
-                if (v == double.MinValue) return string.Format("double.MinValue ({0:E})", v);
-                if (v == double.MaxValue) return string.Format("double.MaxValue ({0:E})", v);
-                if (double.IsPositiveInfinity(v)) return string.Format("double.PositiveInfinity ({0:E})", v);
-                if (double.IsNegativeInfinity(v)) return string.Format("double.NegativeInfinity ({0:E})", v);
-                if (v > 10000000 || v < -10000000) return v.ToString("E"); // If it's a big number, use scientified notation
+                if (double.IsNaN(v)) return Some("double.NaN");
+                if (v == double.MinValue) return Some($"double.MinValue ({v:E})");
+                if (v == double.MaxValue) return Some($"double.MaxValue ({v:E})");
+                if (double.IsPositiveInfinity(v)) return Some($"double.PositiveInfinity ({v:E})");
+                if (double.IsNegativeInfinity(v)) return Some($"double.NegativeInfinity ({v:E})");
+                if (v > 10000000 || v < -10000000) return Some(v.ToString("E")); // If it's a big number, use scientified notation
 
-                return v.ToString("G");
+                return Some(v.ToString("G"));
             }
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemInt16)
-                return ReadInt16(address).ToString();
+                return ReadInt16(address).map(v => v.ToString());
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemUInt16)
-                return string.Format(StringFormat.Unsigned, ReadUInt16(address));
+                return ReadUInt16(address).map(v => string.Format(StringFormat.Unsigned, v));
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemInt32)
-                return ReadInt32(address).ToString();
+                return ReadInt32(address).map(v => v.ToString());
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemUInt32)
-                return string.Format(StringFormat.Unsigned, ReadUInt32(address));
+                return ReadUInt32(address).map(v => string.Format(StringFormat.Unsigned, v));
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemInt64)
-                return ReadInt64(address).ToString();
+                return ReadInt64(address).map(v => v.ToString());
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemUInt64)
-                return string.Format(StringFormat.Unsigned, ReadUInt64(address));
+                return ReadUInt64(address).map(v => string.Format(StringFormat.Unsigned, v));
 
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemDecimal)
-                return ReadDecimal(address).ToString();
+                return ReadDecimal(address).map(v => v.ToString(CultureInfo.InvariantCulture));
 
             // String
             if (type.managedTypesArrayIndex == m_Snapshot.coreTypes.systemString)
             {
                 // While string is actually a reference type, we handle it here, because it's so common.
                 // Rather than showing the address, we show the value, which is the text.
-                var pointer = ReadPointer(address);
-                if (pointer == 0)
-                    return "null";
+                if (!ReadPointer(address).valueOut(out var pointer)) return None._;
+                if (pointer == 0) return Some("null");
 
                 // TODO: HACK: Reading a static pointer, points actually into the HEAP. However,
                 // since it's a StaticMemory reader in that case, we can't read the heap.
                 // Therefore we create a new MemoryReader here.
-                var heapreader = this;
-                if (!(heapreader is MemoryReader))
-                    heapreader = new MemoryReader(m_Snapshot);
+                var heapReader = this;
+                if (!(heapReader is MemoryReader))
+                    heapReader = new MemoryReader(m_Snapshot);
 
                 // https://stackoverflow.com/questions/3815227/understanding-clr-object-size-between-32-bit-vs-64-bit
-                var value = '\"' + heapreader.ReadString(pointer + (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize) + '\"';
-                return value;
+                return heapReader.ReadString(pointer + m_Snapshot.virtualMachineInformation.objectHeaderSize)
+                    .map(v => '\"' + v + '\"');
             }
 
             ///////////////////////////////////////////////////////////////////
@@ -599,14 +544,12 @@ public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType ty
             // Simply display the address of it. A pointer type is either
             // a ReferenceType, or an IntPtr and UIntPtr.
             ///////////////////////////////////////////////////////////////////
-            if (type.isPointer)
-            {
-                var pointer = ReadPointer(address);
-                if (pointer == 0)
-                    return "null";
+            if (type.isPointer) {
+                if (!ReadPointer(address).valueOut(out var pointer)) return None._;
+                if (pointer == 0) return Some("null");
 
                 var value = string.Format(StringFormat.Address, pointer);
-                return value;
+                return Some(value);
             }
 
             ///////////////////////////////////////////////////////////////////
@@ -618,7 +561,7 @@ public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType ty
             if (type.isValueType)
             {
                 if (m_RecursionGuard >= 1)
-                    return "{...}";
+                    return Some("{...}");
 
                 // For the actual value, or value preview, we are interested in instance fields only.
                 var instanceFields = type.instanceFields;
@@ -640,7 +583,12 @@ public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType ty
                         if (n > 0)
                             offset += instanceFields[n].offset - m_Snapshot.virtualMachineInformation.objectHeaderSize; // TODO: this is trial&error. make sure to understand it!
 
-                        m_StringBuilder.Append(ReadFieldValueAsString(address + (ulong)offset, m_Snapshot.managedTypes[instanceFields[n].managedTypesArrayIndex]));
+                        m_StringBuilder.Append(
+                            ReadFieldValueAsString(
+                                address + (ulong)offset, 
+                                m_Snapshot.managedTypes[instanceFields[n].managedTypesArrayIndex]
+                            ).getOrElse("<read error>")
+                        );
                         if (n < count - 1)
                             m_StringBuilder.Append(", ");
                     }
@@ -654,10 +602,10 @@ public string ReadFieldValueAsString(System.UInt64 address, PackedManagedType ty
                 }
 
                 m_StringBuilder.Append(')');
-                return m_StringBuilder.ToString();
+                return Some(m_StringBuilder.ToString());
             }
 
-            return "<???>";
+            return Some("<???>");
         }
     }
 
diff --git a/Editor/Scripts/MemoryWindow.cs b/Editor/Scripts/MemoryWindow.cs
index 4f4f346..ea554f9 100644
--- a/Editor/Scripts/MemoryWindow.cs
+++ b/Editor/Scripts/MemoryWindow.cs
@@ -33,7 +33,9 @@ public void Inspect(PackedMemorySnapshot memory, System.UInt64 address, System.U
             if (address == 0)
                 return;
 
-            var heapIndex = memory.FindHeapOfAddress(address);
+            if (!memory.FindHeapOfAddress(address).valueOut(out var heapIndex)) {
+                Debug.LogError($"Can't find heap of address {address:X} in memory.");
+            }
             if (heapIndex < 0)
                 return;
 
@@ -123,7 +125,9 @@ void InspectInternal(PackedMemorySnapshot memory, System.UInt64 address, System.
             if (address == 0)
                 return;
 
-            var heapIndex = memory.FindHeapOfAddress(address);
+            if (!memory.FindHeapOfAddress(address).valueOut(out var heapIndex)) {
+                Debug.LogError($"Can't find heap of address {address:X} in memory.");
+            }
             var heap = memory.managedHeapSections[heapIndex];
 
             m_segment = new ArraySegment64<byte>(heap.bytes, address - heap.startAddress, size);
diff --git a/Editor/Scripts/NativeObjectsView/AbstractNativeObjectsView.cs b/Editor/Scripts/NativeObjectsView/AbstractNativeObjectsView.cs
index 6325af6..81b1e0c 100644
--- a/Editor/Scripts/NativeObjectsView/AbstractNativeObjectsView.cs
+++ b/Editor/Scripts/NativeObjectsView/AbstractNativeObjectsView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -17,7 +18,7 @@ public class AbstractNativeObjectsView : HeapExplorerView
         NativeObjectControl m_NativeObjectControl;
         HeSearchField m_SearchField;
         ConnectionsView m_ConnectionsView;
-        PackedNativeUnityEngineObject? m_Selected;
+        Option<PackedNativeUnityEngineObject> m_Selected;
         RootPathView m_RootPathView;
         NativeObjectPreviewView m_PreviewView;
         float m_SplitterHorz = 0.33333f;
@@ -163,9 +164,9 @@ protected void DrawFilterToolbarButton()
 
         public override void RestoreCommand(GotoCommand command)
         {
-            if (command.toNativeObject.isValid)
+            if (command.toNativeObject.valueOut(out var nativeObject))
             {
-                m_NativeObjectsControl.Select(command.toNativeObject.packed);
+                m_NativeObjectsControl.Select(nativeObject.packed);
                 return;
             }
 
@@ -174,8 +175,8 @@ public override void RestoreCommand(GotoCommand command)
 
         public override GotoCommand GetRestoreCommand()
         {
-            if (m_Selected.HasValue)
-                return new GotoCommand(new RichNativeObject(snapshot, m_Selected.Value.nativeObjectsArrayIndex));
+            if (m_Selected.valueOut(out var selected))
+                return new GotoCommand(new RichNativeObject(snapshot, selected.nativeObjectsArrayIndex));
 
             return base.GetRestoreCommand();
         }
@@ -227,9 +228,9 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope(GUILayout.MaxWidth(16)))
                         {
-                            if (m_Selected.HasValue)
+                            if (m_Selected.valueOut(out var selected))
                             {
-                                HeEditorGUI.NativeObjectIcon(GUILayoutUtility.GetRect(16, 16), m_Selected.Value);
+                                HeEditorGUI.NativeObjectIcon(GUILayoutUtility.GetRect(16, 16), selected);
                                 //GUI.DrawTexture(r, HeEditorStyles.assetImage);
                             }
 
@@ -261,10 +262,10 @@ protected virtual void OnDrawHeader()
         {
         }
 
-        void OnListViewSelectionChange(PackedNativeUnityEngineObject? nativeObject)
+        void OnListViewSelectionChange(Option<PackedNativeUnityEngineObject> nativeObject)
         {
             m_Selected = nativeObject;
-            if (!m_Selected.HasValue)
+            if (!m_Selected.valueOut(out var selected))
             {
                 m_RootPathView.Clear();
                 m_ConnectionsView.Clear();
@@ -273,10 +274,10 @@ void OnListViewSelectionChange(PackedNativeUnityEngineObject? nativeObject)
                 return;
             }
 
-            m_ConnectionsView.Inspect(m_Selected.Value);
-            m_NativeObjectControl.Inspect(snapshot, m_Selected.Value);
-            m_PreviewView.Inspect(m_Selected.Value);
-            m_RootPathView.Inspect(m_Selected.Value);
+            m_ConnectionsView.Inspect(selected);
+            m_NativeObjectControl.Inspect(snapshot, selected);
+            m_PreviewView.Inspect(selected);
+            m_RootPathView.Inspect(selected);
         }
 
         // The 'Filer' menu displays this content
diff --git a/Editor/Scripts/NativeObjectsView/NativeObjectControl.cs b/Editor/Scripts/NativeObjectsView/NativeObjectControl.cs
index 90dd2e1..4cd3f19 100644
--- a/Editor/Scripts/NativeObjectsView/NativeObjectControl.cs
+++ b/Editor/Scripts/NativeObjectsView/NativeObjectControl.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -58,7 +59,7 @@ protected override TreeViewItem BuildRoot()
 
             AddTreeViewItem(root, new Item() { displayName = "Name", value = m_Object.name });
             AddTreeViewItem(root, new Item() { displayName = "Type", value = m_Snapshot.nativeTypes[m_Object.nativeTypesArrayIndex].name });
-            AddTreeViewItem(root, new Item() { displayName = "Size", value = EditorUtility.FormatBytes(m_Object.size) });
+            AddTreeViewItem(root, new Item() { displayName = "Size", value = EditorUtility.FormatBytes(m_Object.size.ToLongClamped()) });
             AddTreeViewItem(root, new Item() { displayName = "Address", value = string.Format(StringFormat.Address, m_Object.nativeObjectAddress) });
             AddTreeViewItem(root, new Item() { displayName = "InstanceID", value = m_Object.instanceId.ToString() });
             AddTreeViewItem(root, new Item() { displayName = "Persistent", value = m_Object.isPersistent.ToString() });
diff --git a/Editor/Scripts/NativeObjectsView/NativeObjectDuplicatesView.cs b/Editor/Scripts/NativeObjectsView/NativeObjectDuplicatesView.cs
index b0df8af..5c54ce2 100644
--- a/Editor/Scripts/NativeObjectsView/NativeObjectDuplicatesView.cs
+++ b/Editor/Scripts/NativeObjectsView/NativeObjectDuplicatesView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -51,7 +52,8 @@ protected override void OnDrawHeader()
 
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
 
-            var text = string.Format("{0} native UnityEngine object guessed duplicate(s) wasting {1} memory", m_NativeObjectsControl.nativeObjectsCount, EditorUtility.FormatBytes(m_NativeObjectsControl.nativeObjectsSize));
+            var text =
+                $"{m_NativeObjectsControl.nativeObjectsCount} native UnityEngine object guessed duplicate(s) wasting {EditorUtility.FormatBytes(m_NativeObjectsControl.nativeObjectsSize.ToLongClamped())} memory";
             window.SetStatusbarString(text);
         }
 
diff --git a/Editor/Scripts/NativeObjectsView/NativeObjectPreviewView.cs b/Editor/Scripts/NativeObjectsView/NativeObjectPreviewView.cs
index c168705..c778c87 100644
--- a/Editor/Scripts/NativeObjectsView/NativeObjectPreviewView.cs
+++ b/Editor/Scripts/NativeObjectsView/NativeObjectPreviewView.cs
@@ -4,16 +4,18 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class NativeObjectPreviewView : HeapExplorerView
     {
         Editor m_Editor;
-        RichNativeObject m_Object;
+        Option<RichNativeObject> m_Object;
         bool m_HasPreviewAssets;
         List<string> m_Guids = new List<string>();
         List<UnityEngine.Object> m_LoadedAssets = new List<Object>();
@@ -36,7 +38,7 @@ public override void Awake()
             base.Awake();
 
             titleContent = new GUIContent("Asset Preview", "");
-            m_Object = RichNativeObject.invalid;
+            m_Object = None._;
         }
 
         public override void OnDestroy()
@@ -51,7 +53,7 @@ public override void OnDestroy()
 
             m_Guids = new List<string>();
             m_LoadedAssets = new List<Object>();
-            m_Object = RichNativeObject.invalid;
+            m_Object = None._;
         }
 
         public void Clear()
@@ -62,7 +64,7 @@ public void Clear()
                 m_Editor = null;
             }
 
-            m_Object = RichNativeObject.invalid;
+            m_Object = None._;
             m_HasPreviewAssets = false;
             m_LoadedAssets = new List<Object>();
             m_Guids = new List<string>();
@@ -75,18 +77,19 @@ public void Inspect(PackedNativeUnityEngineObject obj)
             Clear();
 
             m_PreviewTime = Time.realtimeSinceStartup;
-            m_Object = new RichNativeObject(snapshot, obj.nativeObjectsArrayIndex);
+            var nativeObject = new RichNativeObject(snapshot, obj.nativeObjectsArrayIndex);
+            m_Object = Some(nativeObject);
 
-            if (autoLoad && m_Object.isValid && m_Object.isPersistent)
+            if (autoLoad && nativeObject.isPersistent)
                 LoadAssetPreviews();
         }
 
         void LoadAssetPreviews()
         {
-            if (!m_Object.isValid)
+            if (!m_Object.valueOut(out var obj))
                 return;
 
-            m_Guids = new List<string>(AssetDatabase.FindAssets(string.Format("t:{0} {1}", m_Object.type.name, m_Object.name)));
+            m_Guids = new List<string>(AssetDatabase.FindAssets($"t:{obj.type.name} {obj.name}"));
             m_LoadPreview = true;
             window.Repaint();
         }
@@ -106,12 +109,14 @@ public override void OnGUI()
                     // Make sure the filename and object name match
                     var path = AssetDatabase.GUIDToAssetPath(guid);
                     var fileName = System.IO.Path.GetFileNameWithoutExtension(path);
-                    if (string.Equals(fileName, m_Object.name, System.StringComparison.OrdinalIgnoreCase))
-                    {
+                    {if (
+                        m_Object.valueOut(out var obj)
+                        && string.Equals(fileName, obj.name, System.StringComparison.OrdinalIgnoreCase)
+                    ) {
                         var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(guid));
                         if (asset != null)
                             m_LoadedAssets.Add(asset);
-                    }
+                    }}
 
                     if (m_Guids.Count == 0 && m_LoadedAssets.Count > 0)
                     {
@@ -137,14 +142,13 @@ public override void OnGUI()
                 return;
             }
 
-            if (!m_Object.isValid || !m_Object.isPersistent)
-            {
+            {if (!m_Object.valueOut(out var obj) || !obj.isPersistent) {
                 DrawPreviewButtons(false);
 
                 var text = "Select an asset to display its preview here.";
                 DrawBackground(HeEditorGUILayout.GetLargeRect(), text, true);
                 return;
-            }
+            }}
 
             // Tried to load preview already?
             if (!m_LoadPreview)
@@ -167,7 +171,10 @@ public override void OnGUI()
             {
                 DrawPreviewButtons(false);
 
-                var text = string.Format("Could not find any asset named '{1}' of type '{0}' in the project.", m_Object.type.name, m_Object.name);
+                var text = 
+                    m_Object.valueOut(out var obj)
+                    ? string.Format("Could not find any asset named '{1}' of type '{0}' in the project.", obj.type.name, obj.name)
+                    : "no object selected";
                 DrawBackground(HeEditorGUILayout.GetLargeRect(), text, true);
                 return;
             }
@@ -202,8 +209,8 @@ void DrawPreviewButtons(bool drawSettings)
                 autoLoad = GUILayout.Toggle(autoLoad, new GUIContent(HeEditorStyles.previewAutoLoadImage, "Automatically preview assets."), HeEditorStyles.previewButton, GUILayout.Width(24));
                 if (EditorGUI.EndChangeCheck())
                 {
-                    if (m_Object.isValid)
-                        Inspect(m_Object.packed);
+                    if (m_Object.valueOut(out var obj))
+                        Inspect(obj.packed);
                 }
 
                 using (new EditorGUI.DisabledScope(m_LoadedAssets.Count == 0))
diff --git a/Editor/Scripts/NativeObjectsView/NativeObjectsControl.cs b/Editor/Scripts/NativeObjectsView/NativeObjectsControl.cs
index c91c5f8..428a2f5 100644
--- a/Editor/Scripts/NativeObjectsView/NativeObjectsControl.cs
+++ b/Editor/Scripts/NativeObjectsView/NativeObjectsControl.cs
@@ -3,19 +3,20 @@
 // https://github.com/pschraut/UnityHeapExplorer/
 //
 //#define HEAPEXPLORER_DISPLAY_REFS
-using System.Collections;
+
+using System;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
-using System.Reflection;
-using System.Threading;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class NativeObjectsControl : AbstractTreeView
     {
-        public System.Action<PackedNativeUnityEngineObject?> onSelectionChange;
+        public System.Action<Option<PackedNativeUnityEngineObject>> onSelectionChange;
 
         public long nativeObjectsCount
         {
@@ -25,7 +26,7 @@ public long nativeObjectsCount
             }
         }
 
-        public long nativeObjectsSize
+        public ulong nativeObjectsSize
         {
             get
             {
@@ -34,7 +35,7 @@ public long nativeObjectsSize
         }
 
         protected long m_NativeObjectsCount;
-        protected long m_NativeObjectsSize;
+        protected ulong m_NativeObjectsSize;
 
         PackedMemorySnapshot m_Snapshot;
         int m_UniqueId = 1;
@@ -81,7 +82,7 @@ public NativeObjectsControl(HeapExplorerWindow window, string editorPrefsKey, Tr
 
         public void Select(PackedNativeUnityEngineObject obj)
         {
-            var item = FindItemByAddressRecursive(rootItem, (ulong)obj.nativeObjectAddress);
+            var item = FindItemByAddressRecursive(rootItem, obj.nativeObjectAddress);
             SelectItem(item);
         }
 
@@ -119,11 +120,11 @@ protected override void OnSelectionChanged(TreeViewItem selectedItem)
             var item = selectedItem as NativeObjectItem;
             if (item == null)
             {
-                onSelectionChange.Invoke(null);
+                onSelectionChange.Invoke(None._);
                 return;
             }
 
-            onSelectionChange.Invoke(item.packed);
+            onSelectionChange.Invoke(Some(item.packed));
         }
 
         public struct BuildArgs
@@ -200,12 +201,11 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, BuildArgs buildArgs
                 if (no.nativeTypesArrayIndex == m_Snapshot.coreTypes.nativeMonoBehaviour ||
                     no.nativeTypesArrayIndex == m_Snapshot.coreTypes.nativeScriptableObject)
                 {
-                    string monoScriptName;
-                    var monoScriptIndex = m_Snapshot.FindNativeMonoScriptType(no.nativeObjectsArrayIndex, out monoScriptName);
-                    if (monoScriptIndex != -1 && monoScriptIndex < m_Snapshot.nativeTypes.Length)
+                    var maybeMonoScript = m_Snapshot.FindNativeMonoScriptType(no.nativeObjectsArrayIndex);
+                    if (maybeMonoScript.valueOut(out var monoScript) && monoScript.index < m_Snapshot.nativeTypes.Length)
                     {
-                        typeNameOverride = monoScriptName;
-                        long key = (monoScriptName.GetHashCode() << 32) | monoScriptIndex;
+                        typeNameOverride = monoScript.monoScriptName;
+                        long key = (monoScript.monoScriptName.GetHashCode() << 32) | monoScript.index;
 
                         GroupItem group2;
                         if (!groupLookup.TryGetValue(key, out group2))
@@ -215,7 +215,7 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, BuildArgs buildArgs
                                 id = m_UniqueId++,
                                 depth = group.depth + 1,
                                 //displayName = monoScriptName,
-                                typeNameOverride = monoScriptName,
+                                typeNameOverride = monoScript.monoScriptName,
                             };
                             group2.Initialize(m_Snapshot, no.nativeTypesArrayIndex);
 
@@ -295,13 +295,13 @@ public TreeViewItem BuildDuplicatesTree(PackedMemorySnapshot snapshot, BuildArgs
                     continue;
 
                 var nativeType = m_Snapshot.nativeTypes[no.nativeTypesArrayIndex];
-                if (nativeType.managedTypeArrayIndex == -1)
+                if (!nativeType.managedTypeArrayIndex.valueOut(out var managedTypeArrayIndex))
                     continue;
 
-                if (m_Snapshot.IsSubclassOf(m_Snapshot.managedTypes[nativeType.managedTypeArrayIndex], m_Snapshot.coreTypes.unityEngineComponent))
+                if (m_Snapshot.IsSubclassOf(m_Snapshot.managedTypes[managedTypeArrayIndex], m_Snapshot.coreTypes.unityEngineComponent))
                     continue;
 
-                if (m_Snapshot.IsSubclassOf(m_Snapshot.managedTypes[nativeType.managedTypeArrayIndex], m_Snapshot.coreTypes.unityEngineGameObject))
+                if (m_Snapshot.IsSubclassOf(m_Snapshot.managedTypes[managedTypeArrayIndex], m_Snapshot.coreTypes.unityEngineGameObject))
                     continue;
 
                 var hash = new Hash128((uint)no.nativeTypesArrayIndex, (uint)no.size, (uint)no.name.GetHashCode(), 0);
@@ -374,10 +374,10 @@ protected override int OnSortItem(TreeViewItem aa, TreeViewItem bb)
             switch ((Column)sortingColumn)
             {
                 case Column.Name:
-                    return string.Compare(itemB.name ?? "", itemA.name ?? "", true);
+                    return string.Compare(itemB.name ?? "", itemA.name ?? "", StringComparison.OrdinalIgnoreCase);
 
                 case Column.Type:
-                    return string.Compare(itemB.typeName, itemA.typeName, true);
+                    return string.Compare(itemB.typeName, itemA.typeName, StringComparison.OrdinalIgnoreCase);
 
                 case Column.Size:
                     return itemA.size.CompareTo(itemB.size);
@@ -419,7 +419,7 @@ abstract class AbstractItem : AbstractTreeViewItem
 
             public abstract string typeName { get; }
             public abstract string name { get; }
-            public abstract long size { get; }
+            public abstract ulong size { get; }
             public abstract int count { get; }
             public abstract System.UInt64 address { get; }
             public abstract bool isDontDestroyOnLoad { get; }
@@ -489,7 +489,7 @@ public override string name
                 }
             }
 
-            public override long size
+            public override ulong size
             {
                 get
                 {
@@ -509,7 +509,7 @@ public override System.UInt64 address
             {
                 get
                 {
-                    return (ulong)m_Object.packed.nativeObjectAddress;
+                    return m_Object.packed.nativeObjectAddress;
                 }
             }
 
@@ -563,11 +563,11 @@ public override void OnGUI(Rect position, int column)
                 {
                     HeEditorGUI.NativeObjectIcon(HeEditorGUI.SpaceL(ref position, position.height), m_Object.packed);
 
-                    if (m_Object.managedObject.isValid)
+                    if (m_Object.managedObject.valueOut(out var managedObject))
                     {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_Object.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
                     }
                 }
@@ -597,7 +597,7 @@ public override void OnGUI(Rect position, int column)
                         break;
 
                     case Column.Size:
-                        HeEditorGUI.Size(position, size);
+                        HeEditorGUI.Size(position, size.ToLongClamped());
                         break;
 
                     case Column.Address:
@@ -635,7 +635,7 @@ void OnShowNameContextMenu(GenericMenu menu)
                     menu.AddItem(new GUIContent("Find in Project"), false, (GenericMenu.MenuFunction2)delegate (object userData)
                     {
                         var o = (RichNativeObject)userData;
-                        HeEditorUtility.SearchProjectBrowser(string.Format("t:{0} {1}", o.type.name, o.name));
+                        HeEditorUtility.SearchProjectBrowser($"t:{o.type.name} {o.name}");
                     }, m_Object);
                 }
             }
@@ -725,7 +725,7 @@ public override string name
             }
 
             long m_Size = -1;
-            public override long size
+            public override ulong size
             {
                 get
                 {
@@ -738,12 +738,12 @@ public override long size
                             {
                                 var child = children[n] as AbstractItem;
                                 if (child != null)
-                                    m_Size += child.size;
+                                    m_Size += child.size.ToLongClamped();
                             }
                         }
                     }
 
-                    return m_Size;
+                    return m_Size.ToULongClamped();
                 }
             }
 
@@ -803,7 +803,7 @@ public override int instanceId
                 }
             }
 
-            public void Initialize(PackedMemorySnapshot snapshot, int managedTypeArrayIndex)
+            public void Initialize(PackedMemorySnapshot snapshot, PInt managedTypeArrayIndex)
             {
                 m_Type = new RichNativeType(snapshot, managedTypeArrayIndex);
             }
@@ -821,7 +821,7 @@ public override void OnGUI(Rect position, int column)
                         break;
 
                     case Column.Size:
-                        HeEditorGUI.Size(position, size);
+                        HeEditorGUI.Size(position, size.ToLongClamped());
                         break;
 
                     case Column.Count:
diff --git a/Editor/Scripts/NativeObjectsView/NativeObjectsView.cs b/Editor/Scripts/NativeObjectsView/NativeObjectsView.cs
index 40fd0d6..0e6d0fc 100644
--- a/Editor/Scripts/NativeObjectsView/NativeObjectsView.cs
+++ b/Editor/Scripts/NativeObjectsView/NativeObjectsView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -30,7 +31,7 @@ public override void Awake()
 
         public override int CanProcessCommand(GotoCommand command)
         {
-            if (command.toNativeObject.isValid)
+            if (command.toNativeObject.isSome)
                 return 10;
 
             return base.CanProcessCommand(command);
@@ -57,7 +58,8 @@ protected override void OnDrawHeader()
 
             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
 
-            var text = string.Format("{0} native UnityEngine object(s) using {1} memory", m_NativeObjectsControl.nativeObjectsCount, EditorUtility.FormatBytes(m_NativeObjectsControl.nativeObjectsSize));
+            var text =
+                $"{m_NativeObjectsControl.nativeObjectsCount} native UnityEngine object(s) using {EditorUtility.FormatBytes(m_NativeObjectsControl.nativeObjectsSize.ToLongClamped())} memory";
             window.SetStatusbarString(text);
         }
 
diff --git a/Editor/Scripts/OverviewView/OverviewView.cs b/Editor/Scripts/OverviewView/OverviewView.cs
index 03c6dd4..971c263 100644
--- a/Editor/Scripts/OverviewView/OverviewView.cs
+++ b/Editor/Scripts/OverviewView/OverviewView.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -19,12 +20,12 @@ public class OverviewView : HeapExplorerView
         struct Entry
         {
             public int typeIndex;
-            public long size;
+            public ulong size;
         }
         Entry[] m_NativeMemory;
         Entry[] m_ManagedMemory;
         Entry[] m_StaticMemory;
-        long m_NativeMemoryTotal;
+        ulong m_NativeMemoryTotal;
         long m_ManagedMemoryTotal;
         long m_StaticMemoryTotal;
         Texture2D m_HeapFragTexture;
@@ -100,8 +101,9 @@ void AnalyzeManaged()
                 var obj = snapshot.managedObjects[n];
                 var type = snapshot.managedTypes[obj.managedTypesArrayIndex];
 
-                m_ManagedMemoryTotal += obj.size;
-                m_ManagedMemory[type.managedTypesArrayIndex].size += obj.size;
+                var objSize = obj.size.getOrElse(0);
+                m_ManagedMemoryTotal += objSize;
+                m_ManagedMemory[type.managedTypesArrayIndex].size += objSize;
                 m_ManagedMemory[type.managedTypesArrayIndex].typeIndex = obj.managedTypesArrayIndex;
             }
             System.Array.Sort(m_ManagedMemory, delegate (Entry x, Entry y)
@@ -120,7 +122,7 @@ void AnalyzeStatic()
                 if (type.staticFieldBytes != null)
                 {
                     m_StaticMemoryTotal += type.staticFieldBytes.Length;
-                    m_StaticMemory[type.managedTypesArrayIndex].size += type.staticFieldBytes.Length;
+                    m_StaticMemory[type.managedTypesArrayIndex].size += type.staticFieldBytes.Length.ToUIntClamped();
                 }
 
                 m_StaticMemory[type.managedTypesArrayIndex].typeIndex = type.managedTypesArrayIndex;
@@ -157,7 +159,7 @@ public override void OnGUI()
                 // Native Memory
                 using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.Width(k_CellWidth)))
                 {
-                    EditorGUILayout.LabelField(new GUIContent(string.Format("Top {0} Native Memory Usage", k_ListItemCount), HeEditorStyles.cppImage), EditorStyles.boldLabel);
+                    EditorGUILayout.LabelField(new GUIContent($"Top {k_ListItemCount} Native Memory Usage", HeEditorStyles.cppImage), EditorStyles.boldLabel);
                     GUILayout.Space(8);
 
                     for (var n = 0; n < Mathf.Min(k_ListItemCount, m_NativeMemory.Length); ++n)
@@ -167,8 +169,8 @@ public override void OnGUI()
 
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            GUILayout.Label(string.Format("{0:F2}%", (size / (float)m_NativeMemoryTotal) * 100), GUILayout.Width(k_ColumnPercentageWidth));
-                            GUILayout.Label(EditorUtility.FormatBytes(size), GUILayout.Width(k_ColumnSizeWidth));
+                            GUILayout.Label($"{(size / (float) m_NativeMemoryTotal) * 100:F2}%", GUILayout.Width(k_ColumnPercentageWidth));
+                            GUILayout.Label(EditorUtility.FormatBytes(size.ToLongClamped()), GUILayout.Width(k_ColumnSizeWidth));
                             HeEditorGUI.TypeName(GUILayoutUtility.GetRect(10, GUI.skin.label.CalcHeight(new GUIContent("Wg"), 32), GUILayout.ExpandWidth(true)), type.name);
                         }
                     }
@@ -182,7 +184,7 @@ public override void OnGUI()
                     using (new EditorGUILayout.HorizontalScope())
                     {
                         GUILayout.Label("Total", GUILayout.Width(k_ColumnPercentageWidth));
-                        GUILayout.Label(EditorUtility.FormatBytes(m_NativeMemoryTotal), EditorStyles.boldLabel, GUILayout.Width(k_ColumnSizeWidth));
+                        GUILayout.Label(EditorUtility.FormatBytes(m_NativeMemoryTotal.ToLongClamped()), EditorStyles.boldLabel, GUILayout.Width(k_ColumnSizeWidth));
                         if (GUILayout.Button("Investigate"))
                             window.OnGoto(new GotoCommand(new RichNativeObject(snapshot, 0)));
                     }
@@ -191,7 +193,7 @@ public override void OnGUI()
                 // Managed Memory
                 using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.Width(k_CellWidth)))
                 {
-                    EditorGUILayout.LabelField(new GUIContent(string.Format("Top {0} Managed Memory Usage", k_ListItemCount), HeEditorStyles.csImage), EditorStyles.boldLabel);
+                    EditorGUILayout.LabelField(new GUIContent($"Top {k_ListItemCount} Managed Memory Usage", HeEditorStyles.csImage), EditorStyles.boldLabel);
                     GUILayout.Space(8);
 
                     for (var n = 0; n < Mathf.Min(k_ListItemCount, m_ManagedMemory.Length); ++n)
@@ -201,8 +203,8 @@ public override void OnGUI()
 
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            GUILayout.Label(string.Format("{0:F2}%", (size / (float)m_ManagedMemoryTotal) * 100), GUILayout.Width(k_ColumnPercentageWidth));
-                            GUILayout.Label(EditorUtility.FormatBytes(size), GUILayout.Width(k_ColumnSizeWidth));
+                            GUILayout.Label($"{(size / (float) m_ManagedMemoryTotal) * 100:F2}%", GUILayout.Width(k_ColumnPercentageWidth));
+                            GUILayout.Label(EditorUtility.FormatBytes(size.ToLongClamped()), GUILayout.Width(k_ColumnSizeWidth));
                             HeEditorGUI.TypeName(GUILayoutUtility.GetRect(10, GUI.skin.label.CalcHeight(new GUIContent("Wg"), 32), GUILayout.ExpandWidth(true)), type.name);
                         }
                     }
@@ -218,14 +220,14 @@ public override void OnGUI()
                         GUILayout.Label("Total", GUILayout.Width(k_ColumnPercentageWidth));
                         GUILayout.Label(EditorUtility.FormatBytes(m_ManagedMemoryTotal), EditorStyles.boldLabel, GUILayout.Width(k_ColumnSizeWidth));
                         if (GUILayout.Button("Investigate"))
-                            window.OnGoto(new GotoCommand(new RichManagedObject(snapshot, 0)));
+                            window.OnGoto(new GotoCommand(new RichManagedObject(snapshot, PackedManagedObject.ArrayIndex.newObject(PInt._0))));
                     }
                 }
 
                 // Static Memory
                 using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.Width(k_CellWidth)))
                 {
-                    EditorGUILayout.LabelField(new GUIContent(string.Format("Top {0} Static Memory Usage", k_ListItemCount), HeEditorStyles.csStaticImage), EditorStyles.boldLabel);
+                    EditorGUILayout.LabelField(new GUIContent($"Top {k_ListItemCount} Static Memory Usage", HeEditorStyles.csStaticImage), EditorStyles.boldLabel);
                     GUILayout.Space(8);
 
                     for (var n = 0; n < Mathf.Min(k_ListItemCount, m_StaticMemory.Length); ++n)
@@ -235,8 +237,8 @@ public override void OnGUI()
 
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            GUILayout.Label(string.Format("{0:F2}%", (size / (float)m_StaticMemoryTotal) * 100), GUILayout.Width(k_ColumnPercentageWidth));
-                            GUILayout.Label(EditorUtility.FormatBytes(size), GUILayout.Width(k_ColumnSizeWidth));
+                            GUILayout.Label($"{(size / (float) m_StaticMemoryTotal) * 100:F2}%", GUILayout.Width(k_ColumnPercentageWidth));
+                            GUILayout.Label(EditorUtility.FormatBytes(size.ToLongClamped()), GUILayout.Width(k_ColumnSizeWidth));
                             HeEditorGUI.TypeName(GUILayoutUtility.GetRect(10, GUI.skin.label.CalcHeight(new GUIContent("Wg"), 32), GUILayout.ExpandWidth(true)), type.name);
                         }
                     }
@@ -268,7 +270,10 @@ public override void OnGUI()
                     using (new EditorGUILayout.HorizontalScope())
                     {
                         GUILayout.Label("Total", GUILayout.Width(k_ColumnPercentageWidth));
-                        GUILayout.Label(EditorUtility.FormatBytes(snapshot.virtualMachineInformation.pointerSize * snapshot.gcHandles.Length), EditorStyles.boldLabel, GUILayout.Width(k_ColumnSizeWidth));
+                        GUILayout.Label(EditorUtility.FormatBytes(
+                            snapshot.virtualMachineInformation.pointerSize.sizeInBytes() * snapshot.gcHandles.Length), 
+                            EditorStyles.boldLabel, GUILayout.Width(k_ColumnSizeWidth)
+                        );
                         if (GUILayout.Button("Investigate"))
                             window.OnGoto(new GotoCommand(new RichGCHandle(snapshot, 0)));
                     }
@@ -280,13 +285,13 @@ public override void OnGUI()
                     GUILayout.Label("Virtual Machine Information", EditorStyles.boldLabel);
                     GUILayout.Space(8);
 
-                    DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.pointerSize), "Pointer Size");
+                    DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.pointerSize.sizeInBytes()), "Pointer Size");
                     DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.objectHeaderSize), "Object Header Size");
                     DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.arrayHeaderSize), "Array Header Size");
                     DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.arrayBoundsOffsetInHeader), "Array Bounds Offset In Header");
                     DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.arraySizeOffsetInHeader), "Array Size Offset In Header");
                     DrawStats("", EditorUtility.FormatBytes(snapshot.virtualMachineInformation.allocationGranularity), "Allocation Granularity");
-                    DrawStats("", string.Format("{0}", snapshot.virtualMachineInformation.heapFormatVersion), "Heap Format Version");
+                    DrawStats("", $"{snapshot.virtualMachineInformation.heapFormatVersion}", "Heap Format Version");
                 }
             }
 
diff --git a/Editor/Scripts/PackedTypes/PackedConnection.cs b/Editor/Scripts/PackedTypes/PackedConnection.cs
index d881754..4339f0a 100644
--- a/Editor/Scripts/PackedTypes/PackedConnection.cs
+++ b/Editor/Scripts/PackedTypes/PackedConnection.cs
@@ -4,32 +4,92 @@
 //
 using System;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 
 namespace HeapExplorer
 {
-    // A pair of from and to indices describing what object keeps what other object alive.
+    /// <summary>
+    /// A pair of from and to indices describing what object keeps what other object alive.
+    /// </summary>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedConnection
+    public readonly struct PackedConnection
     {
+        /// <note>
+        /// The value must not get greater than 11, otherwise <see cref="Pair.ComputeConnectionKey"/> fails!
+        /// </note>
         public enum Kind : byte //System.Byte
         {
-            None = 0,
+            /// <summary><see cref="PackedMemorySnapshot.gcHandles"/></summary>
             GCHandle = 1,
+            
+            /// <summary><see cref="PackedMemorySnapshot.nativeObjects"/></summary>
             Native = 2,
-            Managed = 3, // managed connections are NOT in the snapshot, we add them ourselfs.
-            StaticField = 4, // static connections are NOT in the snapshot, we add them ourself.
+            
+            /// <summary><see cref="PackedMemorySnapshot.managedObjects"/></summary>
+            /// <note>Managed connections are NOT in the snapshot, we add them ourselves.</note>
+            Managed = 3, 
+            
+            /// <summary><see cref="PackedMemorySnapshot.managedStaticFields"/></summary>
+            /// <note>Static connections are NOT in the snapshot, we add them ourselves.</note>
+            StaticField = 4,
+        }
 
-            // Must not get greater than 11, otherwise ComputeConnectionKey() fails!
+        /// <summary>From part of the connection.</summary>
+        public readonly struct From {
+            public readonly Pair pair;
+            
+            /// <summary>
+            /// The field data for the reference, if the <see cref="Kind"/> is <see cref="Kind.Managed"/> or
+            /// <see cref="Kind.StaticField"/> (except for array types, for now). `None` otherwise.
+            /// </summary>
+            public readonly Option<PackedManagedField> field;
+
+            public From(Pair pair, Option<PackedManagedField> field) {
+                this.pair = pair;
+                this.field = field;
+            }
         }
+        
+        /// <summary>Named tuple.</summary>
+        public readonly struct Pair {
+            /// <summary>The connection kind, that is pointing to another object.</summary>
+            public readonly Kind kind;
+            
+            /// <summary>
+            /// An index into a snapshot array, depending on specified <see cref="kind"/>. If the kind would be
+            /// '<see cref="PackedConnection.Kind.Native"/>', then it must be an index into the
+            /// <see cref="PackedMemorySnapshot.nativeObjects"/> array.</summary>
+            public readonly int index;
+
+            public Pair(Kind kind, int index) {
+                if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), index, "negative index");
+                
+                this.kind = kind;
+                this.index = index;
+            }
 
-        public System.Int32 from; // Index into a gcHandles, nativeObjects.
-        public System.Int32 to; // Index into a gcHandles, nativeObjects.
-        public Kind fromKind;
-        public Kind toKind;
+            public override string ToString() => $"{nameof(Pair)}[{kind} @ {index}]";
+            
+            public ulong ComputeConnectionKey() {
+                var value = ((ulong)kind << 50) + (ulong)index;
+                return value;
+            }
+        }
+
+        /// <inheritdoc cref="From"/>
+        public readonly From from;
+        
+        /// <inheritdoc cref="Pair"/>
+        public readonly Pair to;
 
-        const System.Int32 k_Version = 2;
+        public PackedConnection(From from, Pair to) {
+            this.from = from;
+            this.to = to;
+        }
+
+        const int k_Version = 3;
 
         public static void Write(System.IO.BinaryWriter writer, PackedConnection[] value)
         {
@@ -38,10 +98,14 @@ public static void Write(System.IO.BinaryWriter writer, PackedConnection[] value
 
             for (int n = 0, nend = value.Length; n < nend; ++n)
             {
-                writer.Write((byte)value[n].fromKind);
-                writer.Write((byte)value[n].toKind);
-                writer.Write(value[n].from);
-                writer.Write(value[n].to);
+                writer.Write((byte)value[n].from.pair.kind);
+                writer.Write((byte)value[n].to.kind);
+                writer.Write(value[n].from.pair.index);
+                writer.Write(value[n].to.index);
+                writer.Write(value[n].from.field.isSome);
+                {if (value[n].from.field.valueOut(out var field)) {
+                    PackedManagedField.Write(writer, field);        
+                }}
             }
         }
 
@@ -62,32 +126,31 @@ public static void Read(System.IO.BinaryReader reader, out PackedConnection[] va
                 for (int n = 0, nend = value.Length; n < nend; ++n)
                 {
                     if ((n % onePercent) == 0)
-                        stateString = string.Format("Loading Object Connections\n{0}/{1}, {2:F0}% done", n + 1, length, ((n + 1) / (float)length) * 100);
-
-                    value[n].fromKind = (Kind)reader.ReadByte();
-                    value[n].toKind = (Kind)reader.ReadByte();
-                    value[n].from = reader.ReadInt32();
-                    value[n].to = reader.ReadInt32();
+                        stateString =
+                            $"Loading Object Connections\n{n + 1}/{length}, {((n + 1) / (float) length) * 100:F0}% done";
+
+                    var fromKind = (Kind)reader.ReadByte();
+                    var toKind = (Kind)reader.ReadByte();
+                    var fromIndex = reader.ReadInt32();
+                    var toIndex = reader.ReadInt32();
+
+                    Option<PackedManagedField> field;
+                    if (version >= 3) {
+                        var hasField = reader.ReadBoolean();
+                        field = hasField ? Option.Some(PackedManagedField.Read(reader)) : None._;
+                    }
+                    else {
+                        field = None._;
+                    }
+                    
+                    value[n] = new PackedConnection(
+                        from: new From(new Pair(fromKind, fromIndex), field),
+                        to: new Pair(toKind, toIndex)
+                    );
                 }
             }
-            else if (version >= 1)
-            {
-                var length = reader.ReadInt32();
-                //stateString = string.Format("Loading {0} Object Connections", length);
-                value = new PackedConnection[length];
-                if (length == 0)
-                    return;
-
-                var onePercent = Math.Max(1, value.Length / 100);
-                for (int n = 0, nend = value.Length; n < nend; ++n)
-                {
-                    if ((n % onePercent) == 0)
-                        stateString = string.Format("Loading Object Connections\n{0}/{1}, {2:F0}% done", n + 1, length, ((n + 1) / (float)length) * 100);
-
-                    // v1 didn't include fromKind and toKind which was a bug
-                    value[n].from = reader.ReadInt32();
-                    value[n].to = reader.ReadInt32();
-                }
+            else if (version >= 1) {
+                throw new Exception("Old file versions are not supported as they do not contain enough data.");
             }
         }
 
@@ -102,7 +165,7 @@ public static PackedConnection[] FromMemoryProfiler(UnityEditor.Profiling.Memory
             var nativeObjectsInstanceIds = new int[nativeObjects.instanceId.GetNumEntries()];
             nativeObjects.instanceId.GetEntries(0, nativeObjects.instanceId.GetNumEntries(), ref nativeObjectsInstanceIds);
 
-            // Create lookup table from instanceId to NativeObject arrayindex
+            // Create lookup table from instanceId to NativeObject array index
             var instanceIdToNativeObjectIndex = new Dictionary<int, int>(nativeObjectsInstanceIds.Length);
             for (var n=0; n< nativeObjectsInstanceIds.Length; ++n)
                 instanceIdToNativeObjectIndex.Add(nativeObjectsInstanceIds[n], n);
@@ -126,12 +189,10 @@ public static PackedConnection[] FromMemoryProfiler(UnityEditor.Profiling.Memory
                     continue;
                 }
 
-                var packed = new PackedConnection();
-                packed.from = n; // nativeObject index
-                packed.fromKind = Kind.Native;
-                packed.to = nativeObjectsGCHandleIndices[n]; // gcHandle index
-                packed.toKind = Kind.GCHandle;
-
+                var packed = new PackedConnection(
+                    from: new From(new Pair(Kind.Native, n /* nativeObject index */), field: None._),
+                    to: new Pair(Kind.GCHandle, nativeObjectsGCHandleIndices[n] /* gcHandle index */)
+                );
                 result.Add(packed);
             }
 
@@ -153,36 +214,34 @@ public static PackedConnection[] FromMemoryProfiler(UnityEditor.Profiling.Memory
 
             for (int n = 0, nend = sourceFrom.Length; n < nend; ++n)
             {
-                var packed = new PackedConnection();
-
-                if (!instanceIdToNativeObjectIndex.TryGetValue(sourceFrom[n], out packed.from))
+                if (!instanceIdToNativeObjectIndex.TryGetValue(sourceFrom[n], out var fromIndex))
                 {
                     invalidInstanceIDs++;
                     continue; // NativeObject InstanceID not found
                 }
 
-                if (!instanceIdToNativeObjectIndex.TryGetValue(sourceTo[n], out packed.to))
+                if (!instanceIdToNativeObjectIndex.TryGetValue(sourceTo[n], out var toIndex))
                 {
                     invalidInstanceIDs++;
                     continue; // NativeObject InstanceID not found
                 }
 
-                if (packed.from < 0 || packed.from >= nativeObjectsCount)
+                if (fromIndex < 0 || fromIndex >= nativeObjectsCount)
                 {
                     invalidIndices++;
                     continue; // invalid index into array
                 }
 
-                if (packed.to < 0 || packed.to >= nativeObjectsCount)
+                if (toIndex < 0 || toIndex >= nativeObjectsCount)
                 {
                     invalidIndices++;
                     continue; // invalid index into array
                 }
 
-                packed.fromKind = Kind.Native;
-                packed.toKind = Kind.Native;
-
-                result.Add(packed);
+                result.Add(new PackedConnection(
+                    from: new From(new Pair(Kind.Native, fromIndex), field: None._),
+                    to: new Pair(Kind.Native, toIndex)
+                ));
             }
 
             if (invalidIndices > 0)
diff --git a/Editor/Scripts/PackedTypes/PackedCoreTypes.cs b/Editor/Scripts/PackedTypes/PackedCoreTypes.cs
index 17c26e8..8c6ebd0 100644
--- a/Editor/Scripts/PackedTypes/PackedCoreTypes.cs
+++ b/Editor/Scripts/PackedTypes/PackedCoreTypes.cs
@@ -2,10 +2,10 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
+
 using System.Collections.Generic;
-using UnityEngine;
-using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -13,66 +13,279 @@ namespace HeapExplorer
     /// The PackedCoreTypes class contains indexes for various types to their
     /// type definition as found in the memory snapshot.
     /// </summary>
-    public class PackedCoreTypes
-    {
+    public class PackedCoreTypes {
         // Indexes in PackedMemorySnapshot.managedTypes array
-        public int systemEnum = -1;
-        public int systemByte = -1;
-        public int systemSByte = -1;
-        public int systemChar = -1;
-        public int systemBoolean = -1;
-        public int systemSingle = -1;
-        public int systemDouble = -1;
-        public int systemDecimal = -1;
-        public int systemInt16 = -1;
-        public int systemUInt16 = -1;
-        public int systemInt32 = -1;
-        public int systemUInt32 = -1;
-        public int systemInt64 = -1;
-        public int systemUInt64 = -1;
-        public int systemIntPtr = -1;
-        public int systemUIntPtr = -1;
-        public int systemString = -1;
-        public int systemValueType = -1;
-        public int systemReferenceType = -1;
-        public int systemObject = -1;
-        public int systemDelegate = -1;
-        public int systemMulticastDelegate = -1;
-
+        public readonly PInt systemEnum;
+        public readonly PInt systemByte;
+        public readonly PInt systemSByte;
+        public readonly PInt systemChar;
+        public readonly PInt systemBoolean;
+        public readonly PInt systemSingle;
+        public readonly PInt systemDouble;
+        public readonly PInt systemDecimal;
+        public readonly PInt systemInt16;
+        public readonly PInt systemUInt16;
+        public readonly PInt systemInt32;
+        public readonly PInt systemUInt32;
+        public readonly PInt systemInt64;
+        public readonly PInt systemUInt64;
+        public readonly PInt systemIntPtr;
+        public readonly PInt systemUIntPtr;
+        public readonly PInt systemString;
+        public readonly PInt systemValueType;
+        public readonly PInt systemObject;
+        public readonly PInt systemDelegate;
+        public readonly PInt systemMulticastDelegate;
+        
         // Indexes in PackedMemorySnapshot.managedTypes array
-        public int unityEngineObject = -1;
-        public int unityEngineGameObject = -1;
-        public int unityEngineTransform = -1;
-        public int unityEngineRectTransform = -1;
-        public int unityEngineMonoBehaviour = -1;
-        public int unityEngineMonoScript = -1;
-        public int unityEngineComponent = -1;
-        public int unityEngineScriptableObject = -1;
-        public int unityEngineAssetBundle = -1;
+        public readonly PInt unityEngineObject;
+        public readonly PInt unityEngineGameObject;
+        public readonly PInt unityEngineComponent;
 
         // Indexes in PackedMemorySnapshot.nativeTypes array
-        public int nativeObject = -1;
-        public int nativeGameObject = -1;
-        public int nativeMonoBehaviour = -1;
-        public int nativeMonoScript = -1;
-        public int nativeScriptableObject = -1;
-        public int nativeTransform = -1;
-        public int nativeComponent = -1;
-        public int nativeAssetBundle = -1;
+        public readonly PInt nativeGameObject;
+        public readonly PInt nativeMonoBehaviour;
+        public readonly PInt nativeMonoScript;
+        public readonly PInt nativeScriptableObject;
+        public readonly PInt nativeComponent;
+        public readonly PInt nativeAssetBundle;
+        
+        // These aren't actually used anywhere. Keeping as an old reference.
+        // Indexes in PackedMemorySnapshot.managedTypes array
+        // public readonly PInt unityEngineTransform;
+        // public readonly PInt unityEngineRectTransform;
+        // public readonly PInt unityEngineMonoBehaviour;
+        // public readonly PInt unityEngineScriptableObject;
+        // public readonly PInt unityEngineAssetBundle;
+        // // Indexes in PackedMemorySnapshot.nativeTypes array
+        // public readonly PInt nativeObject;
+        // public readonly PInt nativeTransform;
+        // public readonly PInt nativeTexture2D;
+        // public readonly PInt nativeTexture3D;
+        // public readonly PInt nativeAudioClip;
+        // public readonly PInt nativeAnimationClip;
+        // public readonly PInt nativeMesh;
+        // public readonly PInt nativeMaterial;
+        // public readonly PInt nativeSprite;
+        // public readonly PInt nativeShader;
+        // public readonly PInt nativeAnimatorController;
+        // public readonly PInt nativeCubemap;
+        // public readonly PInt nativeCubemapArray;
+        // public readonly PInt nativeFont;
 
-        // Indexes in PackedMemorySnapshot.nativeTypes array
-        public int nativeTexture2D = -1;
-        public int nativeTexture3D = -1;
-        public int nativeTextureArray = -1;
-        public int nativeAudioClip = -1;
-        public int nativeAnimationClip = -1;
-        public int nativeMesh = -1;
-        public int nativeMaterial = -1;
-        public int nativeSprite = -1;
-        public int nativeShader = -1;
-        public int nativeAnimatorController = -1;
-        public int nativeCubemap = -1;
-        public int nativeCubemapArray = -1;
-        public int nativeFont = -1;
+        public PackedCoreTypes(PInt systemEnum, PInt systemByte, PInt systemSByte, PInt systemChar, PInt systemBoolean, PInt systemSingle, PInt systemDouble, PInt systemDecimal, PInt systemInt16, PInt systemUInt16, PInt systemInt32, PInt systemUInt32, PInt systemInt64, PInt systemUInt64, PInt systemIntPtr, PInt systemUIntPtr, PInt systemString, PInt systemValueType, PInt systemObject, PInt systemDelegate, PInt systemMulticastDelegate, PInt unityEngineObject, PInt unityEngineGameObject, PInt unityEngineComponent, PInt nativeGameObject, PInt nativeMonoBehaviour, PInt nativeMonoScript, PInt nativeScriptableObject, PInt nativeComponent, PInt nativeAssetBundle) {
+            this.systemEnum = systemEnum;
+            this.systemByte = systemByte;
+            this.systemSByte = systemSByte;
+            this.systemChar = systemChar;
+            this.systemBoolean = systemBoolean;
+            this.systemSingle = systemSingle;
+            this.systemDouble = systemDouble;
+            this.systemDecimal = systemDecimal;
+            this.systemInt16 = systemInt16;
+            this.systemUInt16 = systemUInt16;
+            this.systemInt32 = systemInt32;
+            this.systemUInt32 = systemUInt32;
+            this.systemInt64 = systemInt64;
+            this.systemUInt64 = systemUInt64;
+            this.systemIntPtr = systemIntPtr;
+            this.systemUIntPtr = systemUIntPtr;
+            this.systemString = systemString;
+            this.systemValueType = systemValueType;
+            this.systemObject = systemObject;
+            this.systemDelegate = systemDelegate;
+            this.systemMulticastDelegate = systemMulticastDelegate;
+            this.unityEngineObject = unityEngineObject;
+            this.unityEngineGameObject = unityEngineGameObject;
+            this.unityEngineComponent = unityEngineComponent;
+            this.nativeGameObject = nativeGameObject;
+            this.nativeMonoBehaviour = nativeMonoBehaviour;
+            this.nativeMonoScript = nativeMonoScript;
+            this.nativeScriptableObject = nativeScriptableObject;
+            this.nativeComponent = nativeComponent;
+            this.nativeAssetBundle = nativeAssetBundle;
+        }
+
+        /// <returns>`Some` if one or more errors occured.</returns>
+        public static Option<string[]> tryInitialize(
+            PackedManagedType[] managedTypes, PackedNativeType[] nativeTypes, out PackedCoreTypes initialized
+        ) {
+            // Yeah, this is a bit of a pain, but we can't abstract this away without introducing simulated
+            // higher-kinded types (https://www.youtube.com/watch?v=5nxF-Gdu27I) here, so this will have to do.
+            
+            Option<PInt> systemEnum = None._;
+            Option<PInt> systemByte = None._;
+            Option<PInt> systemSByte = None._;
+            Option<PInt> systemChar = None._;
+            Option<PInt> systemBoolean = None._;
+            Option<PInt> systemSingle = None._;
+            Option<PInt> systemDouble = None._;
+            Option<PInt> systemDecimal = None._;
+            Option<PInt> systemInt16 = None._;
+            Option<PInt> systemUInt16 = None._;
+            Option<PInt> systemInt32 = None._;
+            Option<PInt> systemUInt32 = None._;
+            Option<PInt> systemInt64 = None._;
+            Option<PInt> systemUInt64 = None._;
+            Option<PInt> systemIntPtr = None._;
+            Option<PInt> systemUIntPtr = None._;
+            Option<PInt> systemString = None._;
+            Option<PInt> systemValueType = None._;
+            Option<PInt> systemObject = None._;
+            Option<PInt> systemDelegate = None._;
+            Option<PInt> systemMulticastDelegate = None._;
+            Option<PInt> unityEngineObject = None._;
+            Option<PInt> unityEngineGameObject = None._;
+            Option<PInt> unityEngineComponent = None._;
+            Option<PInt> nativeGameObject = None._;
+            Option<PInt> nativeMonoBehaviour = None._;
+            Option<PInt> nativeMonoScript = None._;
+            Option<PInt> nativeScriptableObject = None._;
+            Option<PInt> nativeComponent = None._;
+            Option<PInt> nativeAssetBundle = None._;
+
+            for (PInt n = PInt._0, nend = managedTypes.LengthP(); n < nend; ++n)
+            {
+                switch (managedTypes[n].name)
+                {
+                    ///////////////////////////////////////////////////////////////
+                    // Primitive types
+                    ///////////////////////////////////////////////////////////////
+                    case "System.Enum": systemEnum = Some(n); break;
+                    case "System.Byte": systemByte = Some(n); break;
+                    case "System.SByte": systemSByte = Some(n); break;
+                    case "System.Char": systemChar = Some(n); break;
+                    case "System.Boolean": systemBoolean = Some(n); break;
+                    case "System.Single": systemSingle = Some(n); break;
+                    case "System.Double": systemDouble = Some(n); break;
+                    case "System.Decimal": systemDecimal = Some(n); break;
+                    case "System.Int16": systemInt16 = Some(n); break;
+                    case "System.UInt16": systemUInt16 = Some(n); break;
+                    case "System.Int32": systemInt32 = Some(n); break;
+                    case "System.UInt32": systemUInt32 = Some(n); break;
+                    case "System.Int64": systemInt64 = Some(n); break;
+                    case "System.UInt64": systemUInt64 = Some(n); break;
+                    case "System.IntPtr": systemIntPtr = Some(n); break;
+                    case "System.UIntPtr": systemUIntPtr = Some(n); break;
+                    case "System.String": systemString = Some(n); break;
+                    case "System.ValueType": systemValueType = Some(n); break;
+                    case "System.Object": systemObject = Some(n); break;
+                    case "System.Delegate": systemDelegate = Some(n); break;
+                    case "System.MulticastDelegate": systemMulticastDelegate = Some(n); break;
+
+                    ///////////////////////////////////////////////////////////////
+                    // UnityEngine types
+                    //////////////////////////////////////////////////////////////
+                    case "UnityEngine.Object": unityEngineObject = Some(n); break;
+                    case "UnityEngine.GameObject": unityEngineGameObject = Some(n); break;
+                    // case "UnityEngine.Transform": unityEngineTransform = Some(n); break;
+                    // case "UnityEngine.RectTransform": unityEngineRectTransform = Some(n); break;
+                    // case "UnityEngine.MonoBehaviour": unityEngineMonoBehaviour = Some(n); break;
+                    case "UnityEngine.Component": unityEngineComponent = Some(n); break;
+                    // case "UnityEngine.ScriptableObject": unityEngineScriptableObject = Some(n); break;
+                    // case "UnityEngine.AssetBundle": unityEngineAssetBundle = Some(n); break;
+                }
+            }
+
+            for (PInt n = PInt._0, nend = nativeTypes.LengthP(); n < nend; ++n)
+            {
+                switch (nativeTypes[n].name)
+                {
+                    // case "Object": nativeObject = Some(n); break;
+                    case "GameObject": nativeGameObject = Some(n); break;
+                    case "MonoBehaviour": nativeMonoBehaviour = Some(n); break;
+                    case "ScriptableObject": nativeScriptableObject = Some(n); break;
+                    // case "Transform": nativeTransform = Some(n); break;
+                    case "MonoScript": nativeMonoScript = Some(n); break;
+                    case "Component": nativeComponent = Some(n); break;
+                    case "AssetBundle": nativeAssetBundle = Some(n); break;
+                    // case "Texture2D": nativeTexture2D = Some(n); break;
+                    // case "Texture3D": nativeTexture3D = Some(n); break;
+                    // case "TextureArray": nativeTextureArray = Some(n); break;
+                    // case "AudioClip": nativeAudioClip = Some(n); break;
+                    // case "AnimationClip": nativeAnimationClip = Some(n); break;
+                    // case "Mesh": nativeMesh = Some(n); break;
+                    // case "Material": nativeMaterial = Some(n); break;
+                    // case "Sprite": nativeSprite = Some(n); break;
+                    // case "Shader": nativeShader = Some(n); break;
+                    // case "AnimatorController": nativeAnimatorController = Some(n); break;
+                    // case "Cubemap": nativeCubemap = Some(n); break;
+                    // case "CubemapArray": nativeCubemapArray = Some(n); break;
+                    // case "Font": nativeFont = Some(n); break;
+                }
+            }
+
+            var errors = new List<string>();
+            if (!systemEnum.valueOut(out var systemEnumInitialized)) errors.Add($"Could not find '{nameof(systemEnum)}'");
+            if (!systemByte.valueOut(out var systemByteInitialized)) errors.Add($"Could not find '{nameof(systemByte)}'");
+            if (!systemSByte.valueOut(out var systemSByteInitialized)) errors.Add($"Could not find '{nameof(systemSByte)}'");
+            if (!systemChar.valueOut(out var systemCharInitialized)) errors.Add($"Could not find '{nameof(systemChar)}'");
+            if (!systemBoolean.valueOut(out var systemBooleanInitialized)) errors.Add($"Could not find '{nameof(systemBoolean)}'");
+            if (!systemSingle.valueOut(out var systemSingleInitialized)) errors.Add($"Could not find '{nameof(systemSingle)}'");
+            if (!systemDouble.valueOut(out var systemDoubleInitialized)) errors.Add($"Could not find '{nameof(systemDouble)}'");
+            if (!systemDecimal.valueOut(out var systemDecimalInitialized)) errors.Add($"Could not find '{nameof(systemDecimal)}'");
+            if (!systemInt16.valueOut(out var systemInt16Initialized)) errors.Add($"Could not find '{nameof(systemInt16)}'");
+            if (!systemUInt16.valueOut(out var systemUInt16Initialized)) errors.Add($"Could not find '{nameof(systemUInt16)}'");
+            if (!systemInt32.valueOut(out var systemInt32Initialized)) errors.Add($"Could not find '{nameof(systemInt32)}'");
+            if (!systemUInt32.valueOut(out var systemUInt32Initialized)) errors.Add($"Could not find '{nameof(systemUInt32)}'");
+            if (!systemInt64.valueOut(out var systemInt64Initialized)) errors.Add($"Could not find '{nameof(systemInt64)}'");
+            if (!systemUInt64.valueOut(out var systemUInt64Initialized)) errors.Add($"Could not find '{nameof(systemUInt64)}'");
+            if (!systemIntPtr.valueOut(out var systemIntPtrInitialized)) errors.Add($"Could not find '{nameof(systemIntPtr)}'");
+            if (!systemUIntPtr.valueOut(out var systemUIntPtrInitialized)) errors.Add($"Could not find '{nameof(systemUIntPtr)}'");
+            if (!systemString.valueOut(out var systemStringInitialized)) errors.Add($"Could not find '{nameof(systemString)}'");
+            if (!systemValueType.valueOut(out var systemValueTypeInitialized)) errors.Add($"Could not find '{nameof(systemValueType)}'");
+            if (!systemObject.valueOut(out var systemObjectInitialized)) errors.Add($"Could not find '{nameof(systemObject)}'");
+            if (!systemDelegate.valueOut(out var systemDelegateInitialized)) errors.Add($"Could not find '{nameof(systemDelegate)}'");
+            if (!systemMulticastDelegate.valueOut(out var systemMulticastDelegateInitialized)) errors.Add($"Could not find '{nameof(systemMulticastDelegate)}'");
+            if (!unityEngineObject.valueOut(out var unityEngineObjectInitialized)) errors.Add($"Could not find '{nameof(unityEngineObject)}'");
+            if (!unityEngineGameObject.valueOut(out var unityEngineGameObjectInitialized)) errors.Add($"Could not find '{nameof(unityEngineGameObject)}'");
+            if (!unityEngineComponent.valueOut(out var unityEngineComponentInitialized)) errors.Add($"Could not find '{nameof(unityEngineComponent)}'");
+            if (!nativeGameObject.valueOut(out var nativeGameObjectInitialized)) errors.Add($"Could not find '{nameof(nativeGameObject)}'");
+            if (!nativeMonoBehaviour.valueOut(out var nativeMonoBehaviourInitialized)) errors.Add($"Could not find '{nameof(nativeMonoBehaviour)}'");
+            if (!nativeMonoScript.valueOut(out var nativeMonoScriptInitialized)) errors.Add($"Could not find '{nameof(nativeMonoScript)}'");
+            if (!nativeScriptableObject.valueOut(out var nativeScriptableObjectInitialized)) errors.Add($"Could not find '{nameof(nativeScriptableObject)}'");
+            if (!nativeComponent.valueOut(out var nativeComponentInitialized)) errors.Add($"Could not find '{nameof(nativeComponent)}'");
+            if (!nativeAssetBundle.valueOut(out var nativeAssetBundleInitialized)) errors.Add($"Could not find '{nameof(nativeAssetBundle)}'");
+
+            if (errors.Count != 0) {
+                initialized = default;
+                return Some(errors.ToArray());
+            }
+            else {
+                initialized = new PackedCoreTypes(
+                    systemEnum: systemEnumInitialized,
+                    systemByte: systemByteInitialized,
+                    systemSByte: systemSByteInitialized,
+                    systemChar: systemCharInitialized,
+                    systemBoolean: systemBooleanInitialized,
+                    systemSingle: systemSingleInitialized,
+                    systemDouble: systemDoubleInitialized,
+                    systemDecimal: systemDecimalInitialized,
+                    systemInt16: systemInt16Initialized,
+                    systemUInt16: systemUInt16Initialized,
+                    systemInt32: systemInt32Initialized,
+                    systemUInt32: systemUInt32Initialized,
+                    systemInt64: systemInt64Initialized,
+                    systemUInt64: systemUInt64Initialized,
+                    systemIntPtr: systemIntPtrInitialized,
+                    systemUIntPtr: systemUIntPtrInitialized,
+                    systemString: systemStringInitialized,
+                    systemValueType: systemValueTypeInitialized,
+                    systemObject: systemObjectInitialized,
+                    systemDelegate: systemDelegateInitialized,
+                    systemMulticastDelegate: systemMulticastDelegateInitialized,
+                    unityEngineObject: unityEngineObjectInitialized,
+                    unityEngineGameObject: unityEngineGameObjectInitialized,
+                    unityEngineComponent: unityEngineComponentInitialized,
+                    nativeGameObject: nativeGameObjectInitialized,
+                    nativeMonoBehaviour: nativeMonoBehaviourInitialized,
+                    nativeMonoScript: nativeMonoScriptInitialized,
+                    nativeScriptableObject: nativeScriptableObjectInitialized,
+                    nativeComponent: nativeComponentInitialized,
+                    nativeAssetBundle: nativeAssetBundleInitialized
+                );
+                return None._;
+            }
+        }
     }
 }
diff --git a/Editor/Scripts/PackedTypes/PackedGCHandle.cs b/Editor/Scripts/PackedTypes/PackedGCHandle.cs
index 43e5b50..2e29cac 100644
--- a/Editor/Scripts/PackedTypes/PackedGCHandle.cs
+++ b/Editor/Scripts/PackedTypes/PackedGCHandle.cs
@@ -2,11 +2,9 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -21,15 +19,36 @@ namespace HeapExplorer
     /// </remarks>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedGCHandle
+    public readonly struct PackedGCHandle
     {
-        // The address of the managed object that the GC handle is referencing.
-        public System.UInt64 target;
+        /// <summary>
+        /// The address of the managed object that the GC handle is referencing.
+        /// </summary>
+        public readonly ulong target;
 
-        [NonSerialized] public System.Int32 gcHandlesArrayIndex;
-        [NonSerialized] public System.Int32 managedObjectsArrayIndex;
+        /// <summary>
+        /// Index into <see cref="PackedMemorySnapshot.gcHandles"/>.
+        /// </summary>
+        [NonSerialized] public readonly PInt gcHandlesArrayIndex;
+        
+        /// <inheritdoc cref="PackedManagedObject.ArrayIndex"/>
+        [NonSerialized] public readonly Option<PackedManagedObject.ArrayIndex> managedObjectsArrayIndex;
 
-        const System.Int32 k_Version = 1;
+        public PackedGCHandle(
+            ulong target, PInt gcHandlesArrayIndex, 
+            Option<PackedManagedObject.ArrayIndex> managedObjectsArrayIndex = default
+        ) {
+            this.target = target;
+            this.gcHandlesArrayIndex = gcHandlesArrayIndex;
+            this.managedObjectsArrayIndex = managedObjectsArrayIndex;
+        }
+
+        public PackedGCHandle withManagedObjectsArrayIndex(PackedManagedObject.ArrayIndex index) =>
+            new PackedGCHandle(
+                target: target, gcHandlesArrayIndex: gcHandlesArrayIndex, managedObjectsArrayIndex: Some(index)
+            );
+
+        const int k_Version = 1;
 
         public static void Write(System.IO.BinaryWriter writer, PackedGCHandle[] value)
         {
@@ -51,14 +70,13 @@ public static void Read(System.IO.BinaryReader reader, out PackedGCHandle[] valu
             if (version >= 1)
             {
                 var length = reader.ReadInt32();
-                stateString = string.Format("Loading {0} GC Handles", length);
+                stateString = $"Loading {length} GC Handles";
                 value = new PackedGCHandle[length];
 
-                for (int n = 0, nend = value.Length; n < nend; ++n)
+                for (PInt n = PInt._0, nend = value.LengthP(); n < nend; ++n)
                 {
-                    value[n].target = reader.ReadUInt64();
-                    value[n].gcHandlesArrayIndex = n;
-                    value[n].managedObjectsArrayIndex = -1;
+                    var target = reader.ReadUInt64();
+                    value[n] = new PackedGCHandle(target: target, gcHandlesArrayIndex: n);
                 }
             }
         }
@@ -71,14 +89,8 @@ public static PackedGCHandle[] FromMemoryProfiler(UnityEditor.Profiling.Memory.E
             var sourceTargets = new ulong[source.target.GetNumEntries()];
             source.target.GetEntries(0, source.target.GetNumEntries(), ref sourceTargets);
 
-            for (int n = 0, nend = value.Length; n < nend; ++n)
-            {
-                value[n] = new PackedGCHandle
-                {
-                    target = sourceTargets[n],
-                    gcHandlesArrayIndex = n,
-                    managedObjectsArrayIndex = -1,
-                };
+            for (PInt n = PInt._0, nend = value.LengthP(); n < nend; ++n) {
+                value[n] = new PackedGCHandle(target: sourceTargets[n], gcHandlesArrayIndex: n);
             }
             return value;
         }
diff --git a/Editor/Scripts/PackedTypes/PackedManagedField.cs b/Editor/Scripts/PackedTypes/PackedManagedField.cs
index 3f5c74f..0472af9 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedField.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedField.cs
@@ -2,33 +2,47 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
 
 namespace HeapExplorer
 {
-    // Description of a field of a managed type.
+    /// <summary>
+    /// Description of a field of a managed type.
+    /// </summary>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedManagedField
-    {
-        // Offset of this field.
-        public System.Int32 offset;
+    public struct PackedManagedField {
+        /// <summary>
+        /// Offset of this field.
+        /// </summary>
+        public readonly PInt offset;
 
-        // The typeindex into PackedMemorySnapshot.typeDescriptions of the type this field belongs to.
-        public System.Int32 managedTypesArrayIndex;
+        /// <summary>
+        /// The type index into <see cref="PackedMemorySnapshot.managedTypes"/> of the type this field belongs to.
+        /// </summary>
+        public readonly PInt managedTypesArrayIndex;
 
-        // Name of this field.
-        public System.String name;
+        /// <summary>
+        /// Name of this field.
+        /// </summary>
+        public string name;
 
-        // Is this field static?
-        public System.Boolean isStatic;
+        /// <summary>
+        /// Is this field static?
+        /// </summary>
+        public readonly bool isStatic;
 
         [NonSerialized] public bool isBackingField;
 
+        public PackedManagedField(PInt offset, PInt managedTypesArrayIndex, string name, bool isStatic) {
+            this.offset = offset;
+            this.managedTypesArrayIndex = managedTypesArrayIndex;
+            this.name = name;
+            this.isStatic = isStatic;
+            isBackingField = false;
+        }
+
         const System.Int32 k_Version = 1;
 
         public static void Write(System.IO.BinaryWriter writer, PackedManagedField[] value)
@@ -36,15 +50,18 @@ public static void Write(System.IO.BinaryWriter writer, PackedManagedField[] val
             writer.Write(k_Version);
             writer.Write(value.Length);
 
-            for (int n = 0, nend = value.Length; n < nend; ++n)
-            {
-                writer.Write(value[n].name);
-                writer.Write(value[n].offset);
-                writer.Write(value[n].managedTypesArrayIndex);
-                writer.Write(value[n].isStatic);
+            for (int n = 0, nend = value.Length; n < nend; ++n) {
+                Write(writer, value[n]);
             }
         }
 
+        public static void Write(System.IO.BinaryWriter writer, in PackedManagedField value) {
+            writer.Write(value.name);
+            writer.Write(value.offset.asInt);
+            writer.Write(value.managedTypesArrayIndex.asInt);
+            writer.Write(value.isStatic);
+        }
+
         public static void Read(System.IO.BinaryReader reader, out PackedManagedField[] value)
         {
             value = new PackedManagedField[0];
@@ -55,19 +72,28 @@ public static void Read(System.IO.BinaryReader reader, out PackedManagedField[]
                 var length = reader.ReadInt32();
                 value = new PackedManagedField[length];
 
-                for (int n = 0, nend = value.Length; n < nend; ++n)
-                {
-                    value[n].name = reader.ReadString();
-                    value[n].offset = reader.ReadInt32();
-                    value[n].managedTypesArrayIndex = reader.ReadInt32();
-                    value[n].isStatic = reader.ReadBoolean();
+                for (int n = 0, nend = value.Length; n < nend; ++n) {
+                    value[n] = Read(reader);
                 }
             }
         }
 
+        public static PackedManagedField Read(System.IO.BinaryReader reader) {
+            var name = reader.ReadString();
+            var offset = PInt.createOrThrow(reader.ReadInt32());
+            var managedTypesArrayIndex = PInt.createOrThrow(reader.ReadInt32());
+            var isStatic = reader.ReadBoolean();
+            return new PackedManagedField(
+                name: name,
+                offset: offset,
+                managedTypesArrayIndex: managedTypesArrayIndex,
+                isStatic: isStatic
+            );
+        }
+
         public override string ToString()
         {
-            var text = string.Format("name: {0}, offset: {1}, typeIndex: {2}, isStatic: {3}", name, offset, managedTypesArrayIndex, isStatic);
+            var text = $"name: {name}, offset: {offset}, typeIndex: {managedTypesArrayIndex}, isStatic: {isStatic}";
             return text;
         }
     }
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs b/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs
new file mode 100644
index 0000000..3fb66a6
--- /dev/null
+++ b/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs
@@ -0,0 +1,56 @@
+using System;
+using HeapExplorer.Utilities;
+
+namespace HeapExplorer {
+  public partial struct PackedManagedObject {
+    /// <summary>
+    /// Named tuple of <see cref="isStatic"/> and <see cref="index"/>.
+    /// </summary>
+    public readonly struct ArrayIndex : IEquatable<ArrayIndex> {
+      /// <summary>Is this a reference to a static field?</summary>
+      public readonly bool isStatic;
+
+      /// <summary>
+      /// An index into the <see cref="PackedMemorySnapshot.managedObjects"/> array or
+      /// <see cref="PackedMemorySnapshot.managedStaticFields"/> array that stores this managed
+      /// object.
+      /// </summary>
+      public readonly PInt index;
+
+      public ArrayIndex(bool isStatic, PInt index) {
+        this.isStatic = isStatic;
+        this.index = index;
+      }
+
+      /// <summary>Indexes into <see cref="PackedMemorySnapshot.managedStaticFields"/>.</summary>
+      public static ArrayIndex newStatic(PInt index) => new ArrayIndex(isStatic: true, index);
+
+      /// <summary>Indexes into <see cref="PackedMemorySnapshot.managedObjects"/>.</summary>
+      public static ArrayIndex newObject(PInt index) => new ArrayIndex(isStatic: false, index);
+
+      public override string ToString() {
+        var staticStr = isStatic ? ", for static field" : "";
+        return $"ManagedObjectIndex({index}{staticStr})";
+      }
+
+      public PackedConnection.Pair asPair =>
+        new PackedConnection.Pair(
+          isStatic ? PackedConnection.Kind.StaticField : PackedConnection.Kind.Managed,
+          index
+        );
+
+      #region Equality
+      public bool Equals(ArrayIndex other) => isStatic == other.isStatic && index == other.index;
+      public override bool Equals(object obj) => obj is ArrayIndex other && Equals(other);
+      public static bool operator ==(ArrayIndex left, ArrayIndex right) => left.Equals(right);
+      public static bool operator !=(ArrayIndex left, ArrayIndex right) => !left.Equals(right);
+
+      public override int GetHashCode() {
+        unchecked {
+          return (isStatic.GetHashCode() * 397) ^ index;
+        }
+      }
+      #endregion
+    }
+  }
+}
\ No newline at end of file
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs.meta b/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs.meta
new file mode 100644
index 0000000..f516722
--- /dev/null
+++ b/Editor/Scripts/PackedTypes/PackedManagedObject.ArrayIndex.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e4dab8b9088c4385807a8dd78622538c
+timeCreated: 1673950736
\ No newline at end of file
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObject.cs b/Editor/Scripts/PackedTypes/PackedManagedObject.cs
index 5819ea6..389a13f 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedObject.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedObject.cs
@@ -2,50 +2,84 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
-using System;
+
+using HeapExplorer.Utilities;
 
 namespace HeapExplorer
 {
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedManagedObject
+    public partial struct PackedManagedObject
     {
-        // The address of the managed object
-        public System.UInt64 address;
-
-        // If this object is a static field
-        public System.Byte[] staticBytes;
-
-        // The managed type of this managed object
-        public System.Int32 managedTypesArrayIndex;
-
-        // An index into the managedObjects array of the snapshot that stores this managed object
-        public System.Int32 managedObjectsArrayIndex;
-
-        // The index into the gcHandles array of the snapshot that is connected to this managed object, if any.
-        public System.Int32 gcHandlesArrayIndex;
-
-        // The index into the nativeObjects array of the snapshot that is connected to this managed object, if any.
-        public System.Int32 nativeObjectsArrayIndex;
-
-        // Size in bytes of this object.
-        // ValueType arrays = count * sizeof(element)
-        // ReferenceType arrays = count * sizeof(pointer)
-        // String = length * sizeof(wchar) + strlen("\0\0")
-        public System.Int32 size;
-
-        public static PackedManagedObject New()
-        {
-            return new PackedManagedObject()
-            {
-                managedTypesArrayIndex = -1,
-                managedObjectsArrayIndex = -1,
-                gcHandlesArrayIndex = -1,
-                nativeObjectsArrayIndex = -1,
-            };
+        /// <summary>
+        /// The address of the managed object
+        /// </summary>
+        public readonly ulong address;
+
+        /// <summary>
+        /// `Some` if this object is a static field.
+        /// </summary>
+        public Option<byte[]> staticBytes;
+
+        /// <summary>
+        /// An index into the <see cref="PackedMemorySnapshot.managedTypes"/> array that stores this managed type
+        /// </summary>
+        public PInt managedTypesArrayIndex;
+
+        /// <summary>
+        /// An index into the <see cref="PackedMemorySnapshot.managedObjects"/> array that stores this managed object
+        /// </summary>
+        public ArrayIndex managedObjectsArrayIndex;
+
+        /// <summary>
+        /// The index into the <see cref="PackedMemorySnapshot.gcHandles"/> array of the snapshot that is connected to
+        /// this managed object, if any.
+        /// </summary>
+        public Option<PInt> gcHandlesArrayIndex;
+
+        /// <summary>
+        /// The index into the <see cref="PackedMemorySnapshot.nativeObjects"/> array of the snapshot that is connected
+        /// to this managed object, if any.
+        /// </summary>
+        public Option<PInt> nativeObjectsArrayIndex;
+
+        /// <summary>
+        /// Size in bytes of this object. `None` if the size is unknown.<br/>
+        /// ValueType arrays = count * sizeof(element)<br/>
+        /// ReferenceType arrays = count * sizeof(pointer)<br/>
+        /// String = length * sizeof(wchar) + strlen("\0\0")
+        /// </summary>
+        public Option<uint> size;
+
+        public PackedManagedObject(
+            ulong address, Option<byte[]> staticBytes, PInt managedTypesArrayIndex, ArrayIndex managedObjectsArrayIndex, 
+            Option<PInt> gcHandlesArrayIndex, Option<PInt> nativeObjectsArrayIndex, Option<uint> size
+        ) {
+            this.address = address;
+            this.staticBytes = staticBytes;
+            this.managedTypesArrayIndex = managedTypesArrayIndex;
+            this.managedObjectsArrayIndex = managedObjectsArrayIndex;
+            this.gcHandlesArrayIndex = gcHandlesArrayIndex;
+            this.nativeObjectsArrayIndex = nativeObjectsArrayIndex;
+            this.size = size;
         }
+
+        public static PackedManagedObject New(
+            ulong address,
+            ArrayIndex managedObjectsArrayIndex,
+            PInt managedTypesArrayIndex,
+            Option<PInt> gcHandlesArrayIndex = default,
+            Option<PInt> nativeObjectsArrayIndex = default,
+            Option<uint> size = default,
+            Option<byte[]> staticBytes = default
+        ) =>
+            new PackedManagedObject(
+                address: address,
+                managedTypesArrayIndex: managedTypesArrayIndex,
+                managedObjectsArrayIndex: managedObjectsArrayIndex,
+                gcHandlesArrayIndex: gcHandlesArrayIndex,
+                nativeObjectsArrayIndex: nativeObjectsArrayIndex,
+                size: size, 
+                staticBytes: staticBytes
+            );
     }
 }
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
index 93e0aeb..ca1fc42 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
@@ -6,29 +6,50 @@
 //#define DEBUG_BREAK_ON_ADDRESS
 //#define ENABLE_PROFILING
 //#define ENABLE_PROFILER
-using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class PackedManagedObjectCrawler
     {
-        public static bool s_IgnoreNestedStructs = true;
-
         long m_TotalCrawled;
-        List<PackedManagedObject> m_Crawl = new List<PackedManagedObject>(1024 * 1024);
-        Dictionary<ulong, int> m_Seen = new Dictionary<ulong, int>(1024 * 1024);
-        List<PackedManagedObject> m_ManagedObjects = new List<PackedManagedObject>(1024 * 1024);
-        List<PackedManagedStaticField> m_StaticFields = new List<PackedManagedStaticField>(10 * 1024);
+
+        readonly struct PendingObject {
+            /// <summary>Object that we need to crawl.</summary>
+            public readonly PackedManagedObject obj;
+
+            /// <summary>
+            /// The field that has the reference to the <see cref="obj"/>. This can be `None` if the object is
+            /// referenced from the native side. 
+            /// </summary>
+            public readonly Option<PackedManagedField> sourceField;
+
+            public PendingObject(PackedManagedObject obj, Option<PackedManagedField> sourceField) {
+                this.obj = obj;
+                this.sourceField = sourceField;
+            }
+        }
+        
+        /// <summary>Stack of objects that still need crawling.</summary>
+        readonly Stack<PendingObject> m_Crawl = new Stack<PendingObject>(1024 * 1024);
+        
+        readonly Dictionary<ulong, PackedManagedObject.ArrayIndex> m_Seen = 
+            new Dictionary<ulong, PackedManagedObject.ArrayIndex>(1024 * 1024);
+        
         PackedMemorySnapshot m_Snapshot;
         PackedManagedField m_CachedPtr;
         MemoryReader m_MemoryReader;
 
 #if DEBUG_BREAK_ON_ADDRESS
-        ulong DebugBreakOnAddress = 0x2604C8AFEE0;
+        /// <summary>
+        /// Allows you to write an address here and then put breakpoints at everywhere where this value is used to
+        /// quickly break in debugger when we're dealing with an object with this particular memory address.
+        /// </summary>
+        const ulong DebugBreakOnAddress = 0x20C98ECE000;
 #endif
 
         [System.Diagnostics.Conditional("ENABLE_PROFILER")]
@@ -47,37 +68,37 @@ static void EndProfilerSample()
 #endif
         }
 
-        public void Crawl(PackedMemorySnapshot snapshot, List<ulong> substitudeAddresses)
+        public void Crawl(PackedMemorySnapshot snapshot, List<ulong> substituteAddresses)
         {
             m_TotalCrawled = 0;
             m_Snapshot = snapshot;
             m_MemoryReader = new MemoryReader(m_Snapshot);
             InitializeCachedPtr();
 
+            var managedObjects = new List<PackedManagedObject>(1024 * 1024);
+
             BeginProfilerSample("CrawlGCHandles");
-            CrawlGCHandles();
+            CrawlGCHandles(managedObjects);
             EndProfilerSample();
 
-            BeginProfilerSample("substitudeAddresses");
-            for (var n = 0; n < substitudeAddresses.Count; ++n)
+            BeginProfilerSample("substituteAddresses");
+            for (var n = 0; n < substituteAddresses.Count; ++n)
             {
-                var addr = substitudeAddresses[n];
+                var addr = substituteAddresses[n];
                 if (m_Seen.ContainsKey(addr))
                     continue;
-                TryAddManagedObject(addr);
+                TryAddManagedObject(addr, managedObjects);
             }
             EndProfilerSample();
-
+            
             BeginProfilerSample("CrawlStatic");
-            CrawlStatic();
-            m_Snapshot.managedStaticFields = m_StaticFields.ToArray();
+            (m_Snapshot.managedStaticFields, m_Snapshot.managedStaticTypes) = CrawlStatic(managedObjects);
             EndProfilerSample();
 
             BeginProfilerSample("CrawlManagedObjects");
-            CrawlManagedObjects();
-
-            m_Snapshot.managedObjects = m_ManagedObjects.ToArray();
-            UpdateProgress();
+            CrawlManagedObjects(managedObjects);
+            m_Snapshot.managedObjects = managedObjects.ToArray();
+            UpdateProgress(managedObjects);
             EndProfilerSample();
         }
 
@@ -87,324 +108,367 @@ void InitializeCachedPtr()
 
             // UnityEngine.Object types on the managed side have a m_CachedPtr field that
             // holds the native memory address of the corresponding native object of this managed object.
-            m_CachedPtr = new PackedManagedField();
+            const string FIELD_NAME = "m_CachedPtr";
 
             for (int n = 0, nend = unityEngineObject.fields.Length; n < nend; ++n)
             {
                 var field = unityEngineObject.fields[n];
-                if (field.name != "m_CachedPtr")
+                if (field.name != FIELD_NAME)
                     continue;
 
                 m_CachedPtr = field;
                 return;
             }
+            
+            throw new Exception(
+                $"HeapExplorer: Could not find '{FIELD_NAME}' field for Unity object type '{unityEngineObject.name}', "
+                + "this probably means the internal structure of Unity has changed and the tool needs updating."
+            );
         }
 
-        bool ContainsReferenceType(int typeIndex)
-        {
-            var baseType = m_Snapshot.managedTypes[typeIndex];
-            if (!baseType.isValueType)
-                return true;
-
-            var managedTypesLength = m_Snapshot.managedTypes.Length;
-            var instanceFields = baseType.instanceFields;
-
-            for (int n=0, nend = instanceFields.Length; n < nend; ++n)
-            {
-                var fieldTypeIndex = instanceFields[n].managedTypesArrayIndex;
-                if (fieldTypeIndex < 0 || fieldTypeIndex >= managedTypesLength)
-                {
-                    m_Snapshot.Error("'{0}' field '{1}' is out of bounds '{2}', ignoring.", baseType.name, n, fieldTypeIndex);
-                    continue;
-                }
-
-                var fieldType = m_Snapshot.managedTypes[instanceFields[n].managedTypesArrayIndex];
-                if (!fieldType.isValueType)
-                    return true;
-            }
-
-            return false;
-        }
-
-        void CrawlManagedObjects()
+        void CrawlManagedObjects(List<PackedManagedObject> managedObjects)
         {
             var virtualMachineInformation = m_Snapshot.virtualMachineInformation;
             var nestedStructsIgnored = 0;
 
+            var seenBaseTypes = new CycleTracker<int>();
             //var guard = 0;
-            while (m_Crawl.Count > 0)
-            {
+            while (m_Crawl.Count > 0) {
                 //if (++guard > 10000000)
                 //{
                 //    Debug.LogWarning("Loop guard kicked in");
                 //    break;
                 //}
-                if ((m_TotalCrawled % 1000) == 0)
-                {
-                    UpdateProgress();
-                    if (m_Snapshot.abortActiveStepRequested)
-                        break;
+                if (m_TotalCrawled % 1000 == 0) {
+                    UpdateProgress(managedObjects);
+                    if (m_Snapshot.abortActiveStepRequested) break;
                 }
 
-                var mo = m_Crawl[m_Crawl.Count - 1];
-                m_Crawl.RemoveAt(m_Crawl.Count - 1);
+                // Take an object that we want to crawl.
+                var obj = m_Crawl.Pop();
 
 #if DEBUG_BREAK_ON_ADDRESS
-                if (mo.address == DebugBreakOnAddress)
-                {
+                if (obj.obj.address == DebugBreakOnAddress) {
                     int a = 0;
                 }
 #endif
 
-                var loopGuard = 0;
-                var typeIndex = mo.managedTypesArrayIndex;
-
-                while (typeIndex != -1)
-                {
-                    if (++loopGuard > 264)
+                seenBaseTypes.markStartOfSearch();
+                
+                // Go through the type hierarchy, down to the base type.
+                var maybeTypeIndex = Some(obj.obj.managedTypesArrayIndex);
+                {while (maybeTypeIndex.valueOut(out var typeIndex)) {
+                    if (seenBaseTypes.markIteration(typeIndex)) {
+                        seenBaseTypes.reportCycle(
+                            $"{nameof(CrawlManagedObjects)}()", typeIndex,
+                            idx => m_Snapshot.managedTypes[idx].ToString()
+                        );
                         break;
+                    }
 
+                    var type = m_Snapshot.managedTypes[typeIndex];
                     AbstractMemoryReader memoryReader = m_MemoryReader;
-                    if (mo.staticBytes != null)
-                        memoryReader = new StaticMemoryReader(m_Snapshot, mo.staticBytes);
+                    {if (obj.obj.staticBytes.valueOut(out var staticBytes))
+                        memoryReader = new StaticMemoryReader(m_Snapshot, staticBytes);}
 
-                    var baseType = m_Snapshot.managedTypes[typeIndex];
-
-                    if (baseType.isArray)
-                    {
-                        if (baseType.baseOrElementTypeIndex < 0 || baseType.baseOrElementTypeIndex >= m_Snapshot.managedTypes.Length)
-                        {
-                            m_Snapshot.Error("'{0}.baseOrElementTypeIndex' = {1} at address '{2:X}', ignoring managed object.", baseType.name, baseType.baseOrElementTypeIndex, mo.address);
-                            break;
-                        }
+                    if (type.isArray) {
+                        handleArrayType(obj, type, memoryReader);
+                        break;
+                    }
+                    else {
+                        var shouldBreak = handleNonArrayType(obj.obj, type, memoryReader, typeIndex: typeIndex);
+                        if (shouldBreak) break;
+                        else maybeTypeIndex = type.baseOrElementTypeIndex;
+                    }
+                }}
+            }
 
-                        var elementType = m_Snapshot.managedTypes[baseType.baseOrElementTypeIndex];
-                        if (elementType.isValueType && elementType.isPrimitive)
-                            break; // don't crawl int[], byte[], etc
+            if (nestedStructsIgnored > 0) m_Snapshot.Warning(
+                $"HeapExplorer: {nestedStructsIgnored} nested structs ignored (Workaround for Unity bug Case 1104590)."
+            );
+            
+            void handleArrayType(
+                PendingObject pendingObject, PackedManagedType type, AbstractMemoryReader memoryReader
+            ) {
+                var mo = pendingObject.obj;
+                if (
+                    !type.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex) 
+                    || baseOrElementTypeIndex >= m_Snapshot.managedTypes.Length
+                ) {
+                    m_Snapshot.Error(
+                        $"HeapExplorer: '{type.name}.baseOrElementTypeIndex' = {baseOrElementTypeIndex} at address "
+                        + $"'{mo.address:X}', ignoring managed object."
+                    );
+                    return;
+                }
 
-                        if (elementType.isValueType && !ContainsReferenceType(elementType.managedTypesArrayIndex))
-                            break;
+                var elementType = m_Snapshot.managedTypes[baseOrElementTypeIndex];
+                if (elementType.isValueType && elementType.isPrimitive)
+                    return; // don't crawl int[], byte[], etc
 
-                        var dim0Length = mo.address > 0 ? memoryReader.ReadArrayLength(mo.address, baseType) : 0;
-                        //if (dim0Length > 1024 * 1024)
-                        if (dim0Length > (32*1024) * (32*1024))
-                        {
-                            m_Snapshot.Error("Array (rank={2}) found at address '{0:X} with '{1}' elements, that doesn't seem right.", mo.address, dim0Length, baseType.arrayRank);
-                            break;
-                        }
+                // If the value type does not contain any reference types nested in it there is no point in analysing
+                // the memory further.
+                if (elementType.isValueType && !m_Snapshot.isOrContainsReferenceType(elementType.managedTypesArrayIndex))
+                    return;
 
-                        for (var k = 0; k < dim0Length; ++k)
-                        {
-                            if ((m_TotalCrawled % 1000) == 0)
-                                UpdateProgress();
+                int dim0Length;
+                if (mo.address > 0) {
+                    if (!memoryReader.ReadArrayLength(mo.address, type).valueOut(out dim0Length)) {
+                        m_Snapshot.Error($"Can't determine array length for array at {mo.address:X}");
+                        return;
+                    }
+                }
+                else
+                    dim0Length = 0;
+                
+                //if (dim0Length > 1024 * 1024)
+                if (dim0Length > (32*1024) * (32*1024)) {
+                    m_Snapshot.Error(
+                        $"HeapExplorer: Array (rank={type.arrayRank}) found at address '{mo.address:X} with "
+                        + $"'{dim0Length}' elements, that doesn't seem right."
+                    );
+                    return;
+                }
 
-                            ulong elementAddr = 0;
+                for (var k = 0; k < dim0Length; ++k) {
+                    if (m_TotalCrawled % 1000 == 0)
+                        UpdateProgress(managedObjects);
 
-                            if (elementType.isArray)
-                            {
-                                elementAddr = memoryReader.ReadPointer(mo.address + (ulong)(k * virtualMachineInformation.pointerSize) + (ulong)virtualMachineInformation.arrayHeaderSize);
-                            }
-                            else if (elementType.isValueType)
-                            {
-                                elementAddr = mo.address + (ulong)(k * elementType.size) + (ulong)virtualMachineInformation.arrayHeaderSize - (ulong)virtualMachineInformation.objectHeaderSize;
-                            }
-                            else
-                            {
-                                elementAddr = memoryReader.ReadPointer(mo.address + (ulong)(k * virtualMachineInformation.pointerSize) + (ulong)virtualMachineInformation.arrayHeaderSize);
-                            }
+                    if (!determineElementAddress().valueOut(out var elementAddr)) continue;
 
 #if DEBUG_BREAK_ON_ADDRESS
-                            if (elementAddr == DebugBreakOnAddress)
-                            {
-                                int a = 0;
-                            }
+                    if (elementAddr == DebugBreakOnAddress) {
+                        int a = 0;
+                    }
 #endif
 
-                            if (elementAddr != 0)
-                            {
-                                int newObjectIndex;
-                                if (!m_Seen.TryGetValue(elementAddr, out newObjectIndex))
-                                {
-                                    var newObj = PackedManagedObject.New();
-                                    newObj.address = elementAddr;
-                                    newObj.managedTypesArrayIndex = elementType.managedTypesArrayIndex;
-                                    newObj.managedObjectsArrayIndex = m_ManagedObjects.Count;
-
-                                    if (elementType.isValueType)
-                                    {
-                                        newObj.managedObjectsArrayIndex = mo.managedObjectsArrayIndex;
-                                        newObj.staticBytes = mo.staticBytes;
-                                    }
-                                    else
-                                    {
-                                        newObj.managedTypesArrayIndex = m_Snapshot.FindManagedObjectTypeOfAddress(elementAddr);
-                                        if (newObj.managedTypesArrayIndex == -1)
-                                            newObj.managedTypesArrayIndex = elementType.managedTypesArrayIndex;
-
-                                        TryConnectNativeObject(ref newObj);
-                                    }
-                                    SetObjectSize(ref newObj, m_Snapshot.managedTypes[newObj.managedTypesArrayIndex]);
-
-                                    if (!elementType.isValueType)
-                                        m_ManagedObjects.Add(newObj);
-
-                                    m_Seen[newObj.address] = newObj.managedObjectsArrayIndex;
-                                    m_Crawl.Add(newObj);
-                                    m_TotalCrawled++;
-                                    newObjectIndex = newObj.managedObjectsArrayIndex;
-                                }
-
-                                // If we do not connect the Slot elements at Slot[] 0x1DB2A512EE0
-                                if (!elementType.isValueType)
-                                {
-                                    if (mo.managedObjectsArrayIndex >= 0)
-                                        m_Snapshot.AddConnection(PackedConnection.Kind.Managed, mo.managedObjectsArrayIndex, PackedConnection.Kind.Managed, newObjectIndex);
-                                    else
-                                        m_Snapshot.AddConnection(PackedConnection.Kind.StaticField, -mo.managedObjectsArrayIndex, PackedConnection.Kind.Managed, newObjectIndex);
-                                }
-                            }
+                    // Skip null references.
+                    if (elementAddr == 0) continue;
+                    
+                    if (!m_Seen.TryGetValue(elementAddr, out var newObjectIndex)) {
+                        var managedTypesArrayIndex = elementType.managedTypesArrayIndex;
+                        var managedObjectsArrayIndex = 
+                            PackedManagedObject.ArrayIndex.newObject(managedObjects.CountP());
+                        var newObj = PackedManagedObject.New(
+                            address: elementAddr,
+                            managedTypesArrayIndex: managedTypesArrayIndex,
+                            managedObjectsArrayIndex: managedObjectsArrayIndex
+                        );
+
+                        if (elementType.isValueType) {
+                            newObj.managedObjectsArrayIndex = mo.managedObjectsArrayIndex;
+                            newObj.staticBytes = mo.staticBytes;
                         }
+                        else {
+                            newObj.managedTypesArrayIndex = 
+                                m_Snapshot.FindManagedObjectTypeOfAddress(elementAddr)
+                                    .getOrElse(elementType.managedTypesArrayIndex);
 
-                        break;
+                            TryConnectNativeObject(ref newObj);
+                        }
+                        SetObjectSize(ref newObj, m_Snapshot.managedTypes[newObj.managedTypesArrayIndex]);
+
+                        if (elementType.isReferenceType)
+                            managedObjects.Add(newObj);
+
+                        m_Seen[newObj.address] = newObj.managedObjectsArrayIndex;
+                        m_Crawl.Push(new PendingObject(newObj, pendingObject.sourceField));
+                        m_TotalCrawled++;
+                        newObjectIndex = newObj.managedObjectsArrayIndex;
                     }
 
-                    for (var n = 0; n < baseType.fields.Length; ++n)
-                    {
-                        var field = baseType.fields[n];
-                        if (field.isStatic)
-                            continue;
+                    if (elementType.isReferenceType) {
+                        m_Snapshot.AddConnection(
+                            new PackedConnection.From(
+                                mo.managedObjectsArrayIndex.asPair,
+                                // Artūras Šlajus: I am not sure how to get the referencing field here.
+                                // TODO: fixme
+                                field: None._
+                            ),
+                            newObjectIndex.asPair
+                        );
+                    }
+                    
+                    // Determines the memory address for the `k`th element.
+                    Option<ulong> determineElementAddress() {
+                        // Artūras Šlajus: Not sure why these checks are done in this order but I am too scared to
+                        // switch the order.
+                        if (elementType.isArray) return readPtr();
+                        if (elementType.isValueType) return Some(
+                            mo.address
+                            + (ulong) (k * elementType.size)
+                            + virtualMachineInformation.arrayHeaderSize
+                            - virtualMachineInformation.objectHeaderSize
+                        );
+                        else return readPtr();
+
+                        Option<ulong> readPtr() {
+                            var ptr = mo.address
+                                      + (ulong) (k * virtualMachineInformation.pointerSize.sizeInBytes())
+                                      + virtualMachineInformation.arrayHeaderSize;
+                            var maybeAddress = memoryReader.ReadPointer(ptr);
+                            if (maybeAddress.isNone) {
+                                m_Snapshot.Error($"HeapExplorer: Can't read ptr={ptr:X} for k={k}, type='{elementType.name}'");
+                            }
 
-                        var fieldType = m_Snapshot.managedTypes[field.managedTypesArrayIndex];
+                            return maybeAddress;
+                        }
+                    }
+                }
+            }
 
-                        if (fieldType.isValueType)
-                        {
-                            if (fieldType.isPrimitive)
-                                continue;
+            // Returns true
+            bool handleNonArrayType(
+                PackedManagedObject obj, PackedManagedType type, AbstractMemoryReader memoryReader, PInt typeIndex
+            ) {
+                // Go through the fields in the type.
+                for (var n = 0; n < type.fields.Length; ++n) {
+                    var field = type.fields[n];
+                    
+                    // Skip static fields as they are not a part of the object.
+                    if (field.isStatic)
+                        continue;
 
-                            if (s_IgnoreNestedStructs && mo.managedTypesArrayIndex == fieldType.managedTypesArrayIndex)
-                            {
-                                nestedStructsIgnored++;
-                                continue;
-                            }
+                    var fieldType = m_Snapshot.managedTypes[field.managedTypesArrayIndex];
 
-                            var newObj = PackedManagedObject.New();
+                    if (fieldType.isValueType) handleValueTypeField(fieldType, field);
+                    else handleReferenceTypeField(fieldType, field);
+                }
 
-                            if (mo.staticBytes == null)
-                                newObj.address = mo.address + (uint)field.offset - (uint)virtualMachineInformation.objectHeaderSize;
-                            else
-                                newObj.address = mo.address + (uint)field.offset - (uint)virtualMachineInformation.objectHeaderSize;
+                return Some(typeIndex) == type.baseOrElementTypeIndex || type.isArray;
 
-                            newObj.managedObjectsArrayIndex = mo.managedObjectsArrayIndex;
-                            newObj.managedTypesArrayIndex = fieldType.managedTypesArrayIndex;
-                            newObj.staticBytes = mo.staticBytes;
-                            SetObjectSize(ref newObj, fieldType);
+                void handleValueTypeField(PackedManagedType fieldType, PackedManagedField field) {
+                    // Primitive values types do not contain any references that we would care about. 
+                    if (fieldType.isPrimitive)
+                        return;
 
-                            m_Crawl.Add(newObj); // Crawl, but do not add value types to the managedlist
-                            m_TotalCrawled++;
-                            continue;
-                        }
+                    // This shouldn't be possible, you can't put a value type into itself, as it would have
+                    // infinite size. But you know, things happen...
+                    var isNestedStruct = obj.managedTypesArrayIndex == fieldType.managedTypesArrayIndex;
+                    if (isNestedStruct) {
+                        nestedStructsIgnored++;
+                        return;
+                    }
 
-                        if (!fieldType.isValueType)
-                        {
-                            ulong addr = 0;
-                            if (mo.staticBytes == null)
-                                addr = memoryReader.ReadPointer(mo.address + (uint)field.offset);
-                            else
-                                addr = memoryReader.ReadPointer(mo.address + (uint)field.offset - (uint)virtualMachineInformation.objectHeaderSize);
+                    // If this type contains reference types, we need to crawl it further. However, we do not add value
+                    // types to the `managedObjects` list.
+                    if (m_Snapshot.isOrContainsReferenceType(fieldType.managedTypesArrayIndex)) {
+                        var address = obj.address + field.offset - virtualMachineInformation.objectHeaderSize;
+                        var newObj = PackedManagedObject.New(
+                            address: address,
+                            managedTypesArrayIndex: fieldType.managedTypesArrayIndex,
+                            managedObjectsArrayIndex: obj.managedObjectsArrayIndex,
+                            size: GetObjectSize(address, fieldType),
+                            staticBytes: obj.staticBytes
+                        );
+                        m_Crawl.Push(new PendingObject(newObj, Some(field))); 
+                    }
 
-                            if (addr == 0)
-                                continue;
+                    m_TotalCrawled++;
+                }
+
+                void handleReferenceTypeField(PackedManagedType fieldType, PackedManagedField field) {
+                    var ptr =
+                        obj.staticBytes.isNone
+                        ? obj.address + (uint)field.offset
+                        : obj.address + (uint)field.offset - (uint)virtualMachineInformation.objectHeaderSize;
+
+                    if (!memoryReader.ReadPointer(ptr).valueOut(out var addr)) {
+                        Debug.LogError($"HeapExplorer: Can't read ptr={ptr:X} for fieldType='{fieldType.name}'");
+                        return;
+                    }
+
+                    // Ignore null pointers.
+                    if (addr == 0)
+                        return;
 
 #if DEBUG_BREAK_ON_ADDRESS
-                            if (addr == DebugBreakOnAddress)
-                            {
-                                int a = 0;
-                            }
+                    if (addr == DebugBreakOnAddress) {
+                        int a = 0;
+                    }
 #endif
 
-                            int newObjIndex;
-                            if (!m_Seen.TryGetValue(addr, out newObjIndex))
-                            {
-                                var newObj = PackedManagedObject.New();
-                                newObj.address = addr;
-                                newObj.managedObjectsArrayIndex = m_ManagedObjects.Count;
-                                newObj.managedTypesArrayIndex = m_Snapshot.FindManagedObjectTypeOfAddress(addr);
-                                if (newObj.managedTypesArrayIndex == -1)
-                                    newObj.managedTypesArrayIndex = fieldType.managedTypesArrayIndex;
-
-                                SetObjectSize(ref newObj, m_Snapshot.managedTypes[newObj.managedTypesArrayIndex]);
-                                TryConnectNativeObject(ref newObj);
-
-                                m_Seen[newObj.address] = newObj.managedObjectsArrayIndex;
-                                m_ManagedObjects.Add(newObj);
-                                m_Crawl.Add(newObj);
-                                m_TotalCrawled++;
-                                newObjIndex = newObj.managedObjectsArrayIndex;
-                            }
+                    if (!m_Seen.TryGetValue(addr, out var newObjIndex)) {
+                        var maybeManagedTypesArrayIndex = m_Snapshot.FindManagedObjectTypeOfAddress(addr);
+                        var managedTypesArrayIndex =
+                            maybeManagedTypesArrayIndex.getOrElse(fieldType.managedTypesArrayIndex);
+                        
+                        var newObj = PackedManagedObject.New(
+                            address: addr,
+                            managedObjectsArrayIndex: PackedManagedObject.ArrayIndex.newObject(managedObjects.CountP()),
+                            managedTypesArrayIndex: managedTypesArrayIndex,
+                            size: GetObjectSize(addr, m_Snapshot.managedTypes[managedTypesArrayIndex])
+                        );
 
-                            if (mo.managedObjectsArrayIndex >= 0)
-                                m_Snapshot.AddConnection(PackedConnection.Kind.Managed, mo.managedObjectsArrayIndex, PackedConnection.Kind.Managed, newObjIndex);
-                            else
-                                m_Snapshot.AddConnection(PackedConnection.Kind.StaticField, -mo.managedObjectsArrayIndex, PackedConnection.Kind.Managed, newObjIndex);
+                        TryConnectNativeObject(ref newObj);
 
-                            continue;
-                        }
+                        m_Seen[newObj.address] = newObj.managedObjectsArrayIndex;
+                        managedObjects.Add(newObj);
+                        m_Crawl.Push(new PendingObject(newObj, Some(field)));
+                        m_TotalCrawled++;
+                        
+                        newObjIndex = newObj.managedObjectsArrayIndex;
                     }
 
-                    if (typeIndex == baseType.baseOrElementTypeIndex || baseType.isArray)
-                        break;
-
-                    typeIndex = baseType.baseOrElementTypeIndex;
+                    m_Snapshot.AddConnection(
+                        new PackedConnection.From(obj.managedObjectsArrayIndex.asPair, Some(field)),
+                        newObjIndex.asPair
+                    );
                 }
             }
-
-            if (nestedStructsIgnored > 0)
-                m_Snapshot.Warning("HeapExplorer: {0} nested structs ignored (Workaround for Unity bug Case 1104590).", nestedStructsIgnored);
         }
 
-        void CrawlStatic()
-        {
-            var crawlStatic = new List<int>();
+        (PackedManagedStaticField[] staticFields, int[] managedStaticTypes) CrawlStatic(
+            List<PackedManagedObject> managedObjects
+        ) {
+            var crawlStatic = new Stack<int>();
             var managedTypes = m_Snapshot.managedTypes;
+            var staticFields = new List<PackedManagedStaticField>(10 * 1024);
             var staticManagedTypes = new List<int>(1024);
+            collectStaticFields(staticFields, staticManagedTypes);
+
+            void collectStaticFields(List<PackedManagedStaticField> staticFields, List<int> staticManagedTypes) {
+                // Unity BUG: (Case 984330) PackedMemorySnapshot: Type contains staticFieldBytes, but has no static fields
+                for (int n = 0, nend = managedTypes.Length; n < nend; ++n) {
+                    // Some static classes have no staticFieldBytes. As I understand this, the staticFieldBytes
+                    // are only filled if that static class has been initialized (its cctor called), otherwise it's zero.
+                    //
+                    // This is normal behaviour.
+                    if (managedTypes[n].staticFieldBytes == null || managedTypes[n].staticFieldBytes.Length == 0) {
+                        // Debug.LogFormat(
+                        //     "HeapExplorer: managed type '{0}' does not have static fields.", managedTypes[n].name
+                        // );
+                        continue;
+                    }
 
-            // Unity BUG: (Case 984330) PackedMemorySnapshot: Type contains staticFieldBytes, but has no static field
-            for (int n = 0, nend = managedTypes.Length; n < nend; ++n)
-            {
-                var type = managedTypes[n];
-
-                // Some static classes have no staticFieldBytes. As I understand this, the staticFieldBytes
-                // are only filled if that static class has been initialized (its cctor called), otherwise it's zero.
-                if (type.staticFieldBytes == null || type.staticFieldBytes.Length == 0)
-                    continue;
+                    //var hasStaticField = false;
+                    for (
+                        PInt fieldIndex = PInt._0, fieldIndexEnd = managedTypes[n].fields.LengthP(); 
+                        fieldIndex < fieldIndexEnd;
+                        ++fieldIndex
+                    ) {
+                        if (!managedTypes[n].fields[fieldIndex].isStatic)
+                            continue;
 
-                //var hasStaticField = false;
-                for (int j = 0, jend = type.fields.Length; j < jend; ++j)
-                {
-                    if (!type.fields[j].isStatic)
-                        continue;
+                        //var field = managedTypes[n].fields[j];
+                        //var fieldType = managedTypes[field.managedTypesArrayIndex];
+                        //hasStaticField = true;
 
-                    //var field = type.fields[j];
-                    //var fieldType = managedTypes[field.managedTypesArrayIndex];
-                    //hasStaticField = true;
+                        var item = new PackedManagedStaticField(
+                            managedTypesArrayIndex: managedTypes[n].managedTypesArrayIndex,
+                            fieldIndex: fieldIndex,
+                            staticFieldsArrayIndex: staticFields.CountP()
+                        );
+                        staticFields.Add(item);
 
-                    var item = new PackedManagedStaticField
-                    {
-                        managedTypesArrayIndex = type.managedTypesArrayIndex,
-                        fieldIndex = j,
-                        staticFieldsArrayIndex = m_StaticFields.Count,
-                    };
-                    m_StaticFields.Add(item);
+                        crawlStatic.Push(item.staticFieldsArrayIndex);
+                    }
 
-                    crawlStatic.Add(item.staticFieldsArrayIndex);
+                    //if (hasStaticField)
+                    staticManagedTypes.Add(managedTypes[n].managedTypesArrayIndex);
                 }
-
-                //if (hasStaticField)
-                    staticManagedTypes.Add(type.managedTypesArrayIndex);
             }
-
-            m_Snapshot.managedStaticTypes = staticManagedTypes.ToArray();
-
+ 
             //var loopGuard = 0;
             while (crawlStatic.Count > 0)
             {
@@ -417,13 +481,13 @@ void CrawlStatic()
                 m_TotalCrawled++;
                 if ((m_TotalCrawled % 1000) == 0)
                 {
-                    UpdateProgress();
+                    UpdateProgress(managedObjects);
                     if (m_Snapshot.abortActiveStepRequested)
                         break;
                 }
 
-                var staticField = m_StaticFields[crawlStatic[crawlStatic.Count - 1]];
-                crawlStatic.RemoveAt(crawlStatic.Count - 1);
+                var staticFieldIndex = crawlStatic.Pop();
+                var staticField = staticFields[staticFieldIndex];
 
                 var staticClass = m_Snapshot.managedTypes[staticField.managedTypesArrayIndex];
                 var field = staticClass.fields[staticField.fieldIndex];
@@ -435,79 +499,99 @@ void CrawlStatic()
                     if (staticClass.staticFieldBytes == null || staticClass.staticFieldBytes.Length == 0)
                         continue;
 
-                    var newObj = PackedManagedObject.New();
-                    newObj.address = (ulong)field.offset;
-                    // BUG: TODO: If staticFieldsArrayIndex=0, then it's detected as managedObject rather than staticField?
-                    newObj.managedObjectsArrayIndex = -staticField.staticFieldsArrayIndex;
-                    newObj.managedTypesArrayIndex = fieldType.managedTypesArrayIndex;
-                    newObj.staticBytes = staticClass.staticFieldBytes;
+                    var newObj = PackedManagedObject.New(
+                        address: field.offset,
+                        PackedManagedObject.ArrayIndex.newStatic(staticField.staticFieldsArrayIndex),
+                        managedTypesArrayIndex: fieldType.managedTypesArrayIndex 
+                    );
+                    newObj.staticBytes = Some(staticClass.staticFieldBytes);
                     SetObjectSize(ref newObj, fieldType);
 
-                    m_Crawl.Add(newObj);
+                    m_Crawl.Push(new PendingObject(newObj, Some(field)));
                     m_TotalCrawled++;
-                    continue;
                 }
-
                 // If it's a reference type, it simply points to a ManagedObject on the heap and all
                 // we need to do it to create a new ManagedObject and add it to the list to crawl.
-                if (!fieldType.isValueType)
+                else
                 {
-                    var addr = staticReader.ReadPointer((uint)field.offset);
+                    if (!staticReader.ReadPointer((uint) field.offset).valueOut(out var addr)) {
+                        m_Snapshot.Error($"Can't do `staticReader.ReadPointer(field.offset={field.offset})`");
+                        continue;
+                    }
                     if (addr == 0)
                         continue;
 
 #if DEBUG_BREAK_ON_ADDRESS
-                    if (addr == DebugBreakOnAddress)
-                    {
+                    if (addr == DebugBreakOnAddress) {
                         int a = 0;
                     }
 #endif
 
-                    int newObjIndex;
-                    if (!m_Seen.TryGetValue(addr, out newObjIndex))
+                    if (!m_Seen.TryGetValue(addr, out var newObjIndex))
                     {
-                        var newObj = PackedManagedObject.New();
-                        newObj.address = addr;
-                        newObj.managedObjectsArrayIndex = m_ManagedObjects.Count;
-
-                        // The static field could be a basetype, such as UnityEngine.Object, but actually point to a Texture2D.
+                        // The static field could be a basetype, such as `UnityEngine.Object`, but actually point to a `Texture2D`.
                         // Therefore it's important to find the type of the specified address, rather than using the field type.
-                        newObj.managedTypesArrayIndex = m_Snapshot.FindManagedObjectTypeOfAddress(addr);
-                        if (newObj.managedTypesArrayIndex == -1)
-                            newObj.managedTypesArrayIndex = fieldType.managedTypesArrayIndex;
+                        var maybeManagedTypesArrayIndex = m_Snapshot.FindManagedObjectTypeOfAddress(addr);
+                        var managedTypesArrayIndex = 
+                            maybeManagedTypesArrayIndex.getOrElse(fieldType.managedTypesArrayIndex);
+
+                        var newObj = PackedManagedObject.New(
+                            address: addr,
+                            managedObjectsArrayIndex: PackedManagedObject.ArrayIndex.newObject(
+                                managedObjects.CountP()
+                            ),
+                            managedTypesArrayIndex: managedTypesArrayIndex
+                        );
 
                         // Check if the object has a GCHandle
-                        var gcHandleIndex = m_Snapshot.FindGCHandleOfTargetAddress(addr);
-                        if (gcHandleIndex != -1)
-                        {
-                            newObj.gcHandlesArrayIndex = gcHandleIndex;
-                            m_Snapshot.gcHandles[gcHandleIndex].managedObjectsArrayIndex = newObj.managedObjectsArrayIndex;
-
-                            m_Snapshot.AddConnection(PackedConnection.Kind.GCHandle, gcHandleIndex, PackedConnection.Kind.Managed, newObj.managedObjectsArrayIndex);
-                        }
+                        var maybeGcHandleIndex = m_Snapshot.FindGCHandleOfTargetAddress(addr);
+                        {if (maybeGcHandleIndex.valueOut(out var gcHandleIndex)) {
+                            newObj.gcHandlesArrayIndex = Some(gcHandleIndex);
+                            m_Snapshot.gcHandles[gcHandleIndex] =
+                                m_Snapshot.gcHandles[gcHandleIndex]
+                                .withManagedObjectsArrayIndex(newObj.managedObjectsArrayIndex);
+
+                            m_Snapshot.AddConnection(
+                                new PackedConnection.From(
+                                    new PackedConnection.Pair(PackedConnection.Kind.GCHandle, gcHandleIndex),
+                                    field: None._
+                                ),
+                                newObj.managedObjectsArrayIndex.asPair
+                            );
+                        }}
                         SetObjectSize(ref newObj, managedTypes[newObj.managedTypesArrayIndex]);
                         TryConnectNativeObject(ref newObj);
 
-                        m_ManagedObjects.Add(newObj);
+                        managedObjects.Add(newObj);
                         m_Seen[newObj.address] = newObj.managedObjectsArrayIndex;
-                        m_Crawl.Add(newObj);
+                        m_Crawl.Push(new PendingObject(newObj, Some(field)));
                         m_TotalCrawled++;
                         newObjIndex = newObj.managedObjectsArrayIndex;
                     }
 
-                    m_Snapshot.AddConnection(PackedConnection.Kind.StaticField, staticField.staticFieldsArrayIndex, PackedConnection.Kind.Managed, newObjIndex);
-
-                    continue;
+                    m_Snapshot.AddConnection(
+                        new PackedConnection.From(
+                            new PackedConnection.Pair(PackedConnection.Kind.StaticField, staticField.staticFieldsArrayIndex),
+                            Some(field)
+                        ),
+                        newObjIndex.asPair
+                    );
                 }
-
             }
+
+            return (staticFields.ToArray(), staticManagedTypes.ToArray());
         }
 
-        int TryAddManagedObject(ulong address)
+        /// <summary>
+        /// Creates and stores a <see cref="PackedManagedObject"/> in <see cref="m_ManagedObjects"/>.
+        /// </summary>
+        /// <param name="address"></param>
+        /// <returns>The index into <see cref="m_ManagedObjects"/> array.</returns>
+        Option<PInt> TryAddManagedObject(ulong address, List<PackedManagedObject> managedObjects)
         {
             // Try to find the ManagedObject of the current GCHandle
-            var typeIndex = m_Snapshot.FindManagedObjectTypeOfAddress(address);
-            if (typeIndex == -1)
+            var maybeTypeIndex = m_Snapshot.FindManagedObjectTypeOfAddress(address);
+            if (!maybeTypeIndex.valueOut(out var typeIndex))
             {
                 #region Unity Bug
                 // Unity BUG: (Case 977003) PackedMemorySnapshot: Unable to resolve typeDescription of GCHandle.target
@@ -519,85 +603,101 @@ int TryAddManagedObject(ulong address)
                 // [/quote]
                 #endregion
                 m_Snapshot.Warning("HeapExplorer: Cannot find GCHandle target '{0:X}' (Unity bug Case 977003).", address);
-                return -1;
+                return None._;
             }
 
-            var managedObj = new PackedManagedObject
-            {
-                address = address,
-                managedTypesArrayIndex = typeIndex,
-                managedObjectsArrayIndex = m_ManagedObjects.Count,
-                gcHandlesArrayIndex = -1,
-                nativeObjectsArrayIndex = -1
-            };
+            var index = managedObjects.CountP();
+            var managedObj = PackedManagedObject.New(
+                address: address,
+                managedTypesArrayIndex: typeIndex,
+                managedObjectsArrayIndex: PackedManagedObject.ArrayIndex.newObject(index)
+            );
 
             // If the ManagedObject is the representation of a NativeObject, connect the two
             TryConnectNativeObject(ref managedObj);
             SetObjectSize(ref managedObj, m_Snapshot.managedTypes[managedObj.managedTypesArrayIndex]);
 
             m_Seen[managedObj.address] = managedObj.managedObjectsArrayIndex;
-            m_ManagedObjects.Add(managedObj);
-            m_Crawl.Add(managedObj);
+            managedObjects.Add(managedObj);
+            m_Crawl.Push(new PendingObject(managedObj, sourceField: None._));
 
-            return managedObj.managedObjectsArrayIndex;
+            return Some(index);
         }
 
-        void CrawlGCHandles()
+        void CrawlGCHandles(List<PackedManagedObject> managedObjects)
         {
             var gcHandles = m_Snapshot.gcHandles;
 
-            for (int n=0, nend = gcHandles.Length; n < nend; ++n)
+            for (int n=0, nend = gcHandles.Length; n < nend; ++n) 
             {
-                if (gcHandles[n].target == 0)
+                var gcHandle = gcHandles[n];
+                if (gcHandle.target == 0)
                 {
-                    m_Snapshot.Warning("HeapExplorer: Cannot find GCHandle target '{0:X}' (Unity bug Case 977003).", gcHandles[n].target);
+                    m_Snapshot.Warning("HeapExplorer: Cannot find GCHandle target '{0:X}' (Unity bug Case 977003).", gcHandle.target);
                     continue;
                 }
 
 #if DEBUG_BREAK_ON_ADDRESS
-                if (gcHandles[n].target == DebugBreakOnAddress)
-                {
+                if (gcHandle.target == DebugBreakOnAddress) {
                     int a = 0;
                 }
 #endif
 
-                var managedObjectIndex = TryAddManagedObject(gcHandles[n].target);
-                if (managedObjectIndex == -1)
+                var maybeManagedObjectIndex = TryAddManagedObject(gcHandle.target, managedObjects);
+                if (!maybeManagedObjectIndex.valueOut(out var managedObjectIndex)) {
+                    Debug.LogWarning($"HeapExplorer: Can't find managed object for GC handle {gcHandle.target:X}, skipping!");
                     continue;
+                }
 
-                var managedObj = m_ManagedObjects[managedObjectIndex];
-                managedObj.gcHandlesArrayIndex = gcHandles[n].gcHandlesArrayIndex;
-                m_ManagedObjects[managedObjectIndex] = managedObj;
+                var managedObj = managedObjects[managedObjectIndex];
+                managedObj.gcHandlesArrayIndex = Some(gcHandle.gcHandlesArrayIndex);
+                managedObjects[managedObjectIndex] = managedObj;
 
                 // Connect GCHandle to ManagedObject
-                m_Snapshot.AddConnection(PackedConnection.Kind.GCHandle, gcHandles[n].gcHandlesArrayIndex, PackedConnection.Kind.Managed, managedObj.managedObjectsArrayIndex);
+                m_Snapshot.AddConnection(
+                    new PackedConnection.From(
+                        new PackedConnection.Pair(PackedConnection.Kind.GCHandle, gcHandle.gcHandlesArrayIndex),
+                        field: None._
+                    ),
+                    managedObj.managedObjectsArrayIndex.asPair
+                );
 
                 // Update the GCHandle with the index to its managed object
-                gcHandles[n].managedObjectsArrayIndex = managedObj.managedObjectsArrayIndex;
+                gcHandle = gcHandle.withManagedObjectsArrayIndex(managedObj.managedObjectsArrayIndex);
+                gcHandles[n] = gcHandle;
 
                 m_TotalCrawled++;
 
                 if ((m_TotalCrawled % 1000) == 0)
-                    UpdateProgress();
+                    UpdateProgress(managedObjects);
             }
         }
 
-        void UpdateProgress()
+        void UpdateProgress(List<PackedManagedObject> managedObjects)
         {
-            m_Snapshot.busyString = string.Format("Analyzing Managed Objects\n{0} crawled, {1} extracted", m_TotalCrawled, m_ManagedObjects.Count);
+            m_Snapshot.busyString =
+                $"Analyzing Managed Objects\n{m_TotalCrawled} crawled, {managedObjects.Count} extracted";
         }
 
         void SetObjectSize(ref PackedManagedObject managedObj, PackedManagedType type)
         {
-            if (managedObj.size > 0)
+            if (managedObj.size.isSome)
                 return; // size is set already
 
-            managedObj.size = m_MemoryReader.ReadObjectSize(managedObj.address, type);
+            managedObj.size = GetObjectSize(managedObj.address, type);
+        }
+
+        Option<uint> GetObjectSize(ulong address, PackedManagedType type) {
+            var maybeSize = m_MemoryReader.ReadObjectSize(address, type);
+            if (maybeSize.isNone) {
+                Debug.LogError($"HeapExplorer: Can't read object size for managed object of type '{type.name}' at 0x{address:X}");
+            }
+            return maybeSize.map(size => size.asUInt);
         }
 
         void TryConnectNativeObject(ref PackedManagedObject managedObj)
         {
-            if (managedObj.nativeObjectsArrayIndex >= 0)
+            if (managedObj.nativeObjectsArrayIndex.isSome)
                 return; // connected already
 
             // If it's not derived from UnityEngine.Object, it does not have the m_CachedPtr field and we can skip it
@@ -611,30 +711,48 @@ void TryConnectNativeObject(ref PackedManagedObject managedObj)
 
             BeginProfilerSample("ReadPointer");
             // Read the m_cachePtr value
-            var nativeObjectAddress = m_MemoryReader.ReadPointer(managedObj.address + (uint)m_CachedPtr.offset);
+            var nativeObjectAddressPtr = managedObj.address + (uint) m_CachedPtr.offset;
+            if (
+                !m_MemoryReader.ReadPointer(nativeObjectAddressPtr).valueOut(out var nativeObjectAddress)
+            ) {
+                Debug.LogError(
+                    $"HeapExplorer: Can't read {nameof(m_CachedPtr)} from a managed object at ptr={nativeObjectAddressPtr:X}"
+                );
+                return;
+            }
             EndProfilerSample();
+            // If the native object address is 0 then we have a managed object without the native side, which happens
+            // when you have a leaked managed object.
             if (nativeObjectAddress == 0)
                 return;
 
             // Try to find the corresponding native object
             BeginProfilerSample("FindNativeObjectOfAddress");
-            var nativeObjectArrayIndex = m_Snapshot.FindNativeObjectOfAddress(nativeObjectAddress);
-            EndProfilerSample();
-            if (nativeObjectArrayIndex < 0)
-                return;
-
-            // Connect ManagedObject <> NativeObject
-            managedObj.nativeObjectsArrayIndex = nativeObjectArrayIndex;
-            m_Snapshot.nativeObjects[managedObj.nativeObjectsArrayIndex].managedObjectsArrayIndex = managedObj.managedObjectsArrayIndex;
-
-            // Connect the ManagedType <> NativeType
-            m_Snapshot.managedTypes[managedObj.managedTypesArrayIndex].nativeTypeArrayIndex = m_Snapshot.nativeObjects[managedObj.nativeObjectsArrayIndex].nativeTypesArrayIndex;
-            m_Snapshot.nativeTypes[m_Snapshot.nativeObjects[managedObj.nativeObjectsArrayIndex].nativeTypesArrayIndex].managedTypeArrayIndex = m_Snapshot.managedTypes[managedObj.managedTypesArrayIndex].managedTypesArrayIndex;
-
-            BeginProfilerSample("AddConnection");
-            // Add a Connection from ManagedObject to NativeObject (m_CachePtr)
-            m_Snapshot.AddConnection(PackedConnection.Kind.Managed, managedObj.managedObjectsArrayIndex, PackedConnection.Kind.Native, managedObj.nativeObjectsArrayIndex);
+            var maybeNativeObjectArrayIndex = m_Snapshot.FindNativeObjectOfAddress(nativeObjectAddress);
             EndProfilerSample();
+            {if (maybeNativeObjectArrayIndex.valueOut(out var nativeObjectArrayIndex)) {
+                // Connect ManagedObject <> NativeObject
+                managedObj.nativeObjectsArrayIndex = Some(nativeObjectArrayIndex);
+                m_Snapshot.nativeObjects[nativeObjectArrayIndex].managedObjectsArrayIndex = Some(managedObj.managedObjectsArrayIndex);
+
+                // Connect the ManagedType <> NativeType
+                var nativeTypesArrayIndex = m_Snapshot.nativeObjects[nativeObjectArrayIndex].nativeTypesArrayIndex;
+                m_Snapshot.managedTypes[managedObj.managedTypesArrayIndex].nativeTypeArrayIndex = 
+                    Some(nativeTypesArrayIndex);
+                m_Snapshot.nativeTypes[nativeTypesArrayIndex].managedTypeArrayIndex = 
+                    Some(m_Snapshot.managedTypes[managedObj.managedTypesArrayIndex].managedTypesArrayIndex);
+                
+                BeginProfilerSample("AddConnection");
+                // Add a Connection from ManagedObject to NativeObject (m_CachePtr)
+                m_Snapshot.AddConnection(
+                    new PackedConnection.From(
+                        managedObj.managedObjectsArrayIndex.asPair,
+                        Some(m_CachedPtr)
+                    ),
+                    new PackedConnection.Pair(PackedConnection.Kind.Native, nativeObjectArrayIndex)
+                );
+                EndProfilerSample();
+            }}
         }
     }
 }
diff --git a/Editor/Scripts/PackedTypes/PackedManagedStaticField.cs b/Editor/Scripts/PackedTypes/PackedManagedStaticField.cs
index b1eaf9d..593eeb8 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedStaticField.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedStaticField.cs
@@ -3,22 +3,35 @@
 // https://github.com/pschraut/UnityHeapExplorer/
 //
 using System;
-using System.Collections.Generic;
-using UnityEngine;
+using HeapExplorer.Utilities;
 
-namespace HeapExplorer
-{
+namespace HeapExplorer {
+    /// <summary>
+    /// Similar to <see cref="PackedManagedField"/> but can only represent static fields and thus has the
+    /// <see cref="staticFieldsArrayIndex"/> field.
+    /// </summary>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedManagedStaticField
-    {
-        // The index into PackedMemorySnapshot.typeDescriptions of the type this field belongs to.
-        public System.Int32 managedTypesArrayIndex;
+    public readonly struct PackedManagedStaticField {
+        /// <summary>
+        /// The index into <see cref="PackedMemorySnapshot.managedTypes"/> of the type this field belongs to.
+        /// </summary>
+        public readonly PInt managedTypesArrayIndex;
 
-        // The index into the typeDescription.fields array
-        public System.Int32 fieldIndex;
+        /// <summary>
+        /// The index into the <see cref="PackedManagedType.fields"/> array
+        /// </summary>
+        public readonly PInt fieldIndex;
 
-        // The index into the PackedMemorySnapshot.staticFields array
-        public System.Int32 staticFieldsArrayIndex;
+        /// <summary>
+        /// The index into the <see cref="PackedMemorySnapshot.managedStaticFields"/> array
+        /// </summary>
+        public readonly PInt staticFieldsArrayIndex;
+
+        public PackedManagedStaticField(PInt managedTypesArrayIndex, PInt fieldIndex, PInt staticFieldsArrayIndex) {
+            this.managedTypesArrayIndex = managedTypesArrayIndex;
+            this.fieldIndex = fieldIndex;
+            this.staticFieldsArrayIndex = staticFieldsArrayIndex;
+        }
     }
 }
diff --git a/Editor/Scripts/PackedTypes/PackedManagedType.cs b/Editor/Scripts/PackedTypes/PackedManagedType.cs
index efea9aa..ebbdc54 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedType.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedType.cs
@@ -2,86 +2,162 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
 using UnityEngine;
-using UnityEditor;
 using System;
+using System.Collections.Generic;
+using System.Linq;
+using HeapExplorer.Utilities;
 using UnityEditor.Profiling.Memory.Experimental;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
-    // Description of a managed type.
+    /// <summary>
+    /// Description of a managed type.
+    /// </summary>
+    /// <note>
+    /// This needs to be a class, not a struct because we cache <see cref="instanceFields"/> and
+    /// <see cref="staticFields"/>.
+    /// </note>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedManagedType
+    public class PackedManagedType : PackedMemorySnapshot.TypeForSubclassSearch
     {
-        public static readonly PackedManagedType invalid = new PackedManagedType
-        {
-            managedTypesArrayIndex = -1,
-            nativeTypeArrayIndex = -1,
-            baseOrElementTypeIndex = -1
-        };
+        /// <summary>Is this type a value type? (if it's not a value type, it's a reference type)</summary>
+        public readonly bool isValueType;
 
-        // Is this type a value type? (if it's not a value type, it's a reference type)
-        public System.Boolean isValueType;
+        /// <summary>Is this type a reference type? (if it's not a reference type, it's a value type)</summary>
+        public bool isReferenceType => !isValueType;
 
-        // Is this type an array?
-        public System.Boolean isArray;
+        /// <summary>Is this type an array?</summary>
+        public readonly bool isArray;
 
-        // If this is an arrayType, this will return the rank of the array. (1 for a 1-dimensional array, 2 for a 2-dimensional array, etc)
-        public System.Int32 arrayRank;
+        /// <summary>
+        /// If this is an arrayType, this will return the rank of the array. (1 for a 1-dimensional array, 2 for a
+        /// 2-dimensional array, etc)
+        /// </summary>
+        public readonly PInt arrayRank;
 
-        // Name of this type.
-        public System.String name;
+        /// <summary>
+        /// Name of this type.
+        /// </summary>
+        public readonly string name;
 
-        // Name of the assembly this type was loaded from.
-        public System.String assembly;
+        /// <summary>
+        /// Name of the assembly this type was loaded from.
+        /// </summary>
+        public readonly string assembly;
 
-        // An array containing descriptions of all fields of this type.
-        public PackedManagedField[] fields;
+        /// <summary>
+        /// An array containing descriptions of all fields of this type.
+        /// </summary>
+        public readonly PackedManagedField[] fields;
 
-        // The actual contents of the bytes that store this types static fields, at the point of time when the snapshot was taken.
-        public System.Byte[] staticFieldBytes;
+        /// <summary>
+        /// The actual contents of the bytes that store this types static fields, at the point of time when the
+        /// snapshot was taken.
+        /// </summary>
+        public readonly byte[] staticFieldBytes;
 
-        // The base type for this type, pointed to by an index into PackedMemorySnapshot.typeDescriptions.
-        public System.Int32 baseOrElementTypeIndex;
+        /// <summary>
+        /// The base or element type for this type, pointed to by an index into <see cref="PackedMemorySnapshot.managedTypes"/>.
+        /// <para/>
+        /// ???: Not sure about this - this is either a reference to the base type or <see cref="managedTypesArrayIndex"/>?
+        /// But it is `None` when it's -1? WTF is going on here, Unity! 
+        /// </summary>
+        public readonly Option<PInt> baseOrElementTypeIndex;
 
-        // Size in bytes of an instance of this type. If this type is an arraytype, this describes the amount of bytes a single element in the array will take up.
-        public System.Int32 size;
+        public Option<PInt> baseTypeIndex => 
+            baseOrElementTypeIndex.valueOut(out var idx)
+            ? idx == managedTypesArrayIndex ? None._ : Some(idx)
+            : None._;
 
-        // The address in memory that contains the description of this type inside the virtual machine.
-        // This can be used to match managed objects in the heap to their corresponding TypeDescription, as the first pointer of a managed object points to its type description.
-        public System.UInt64 typeInfoAddress;
+        /// <summary>
+        /// Size in bytes of an instance of this type. If this type is an array type, this describes the amount of
+        /// bytes a single element in the array will take up.
+        /// </summary>
+        public readonly PInt size;
+
+        /// <summary>
+        /// The address in memory that contains the description of this type inside the virtual machine.
+        /// <para/>
+        /// This can be used to match managed objects in the heap to their corresponding TypeDescription, as the first
+        /// pointer of a managed object points to its type description.
+        /// </summary>
+        public readonly ulong typeInfoAddress;
 
-        // The typeIndex of this type. This index is an index into the PackedMemorySnapshot.typeDescriptions array.
-        public System.Int32 managedTypesArrayIndex;
+        /// <summary>
+        /// This index is an index into the <see cref="PackedMemorySnapshot.managedTypes"/> array.
+        /// </summary>
+        public readonly PInt managedTypesArrayIndex;
 
-        // if this managed type has a native counterpart
+        /// <summary>
+        /// Index into <see cref="PackedMemorySnapshot.nativeTypes"/> if this managed type has a native counterpart or
+        /// `None` otherwise.
+        /// </summary>
         [NonSerialized]
-        public System.Int32 nativeTypeArrayIndex;
+        public Option<PInt> nativeTypeArrayIndex;
 
-        // Number of all objects of this type.
+        /// <summary>
+        /// Number of all objects of this type.
+        /// </summary>
         [NonSerialized]
-        public System.Int32 totalObjectCount;
+        public PInt totalObjectCount;
 
-        // The size of all objects of this type.
+        /// <summary>
+        /// The size of all objects of this type.
+        /// </summary>
         [NonSerialized]
-        public System.Int64 totalObjectSize;
+        public ulong totalObjectSize;
 
-        // gets whether the type derived from UnityEngine.Object
+        /// <summary>
+        /// Whether the type derived from <see cref="UnityEngine.Object"/>.
+        /// </summary>
         [NonSerialized]
-        public System.Boolean isUnityEngineObject;
+        public bool isUnityEngineObject;
 
-        // gets whether the type contains any field of ReferenceType
+        /// <summary>
+        /// Whether the type contains any field of ReferenceType
+        /// </summary>
         [NonSerialized]
-        public System.Boolean containsFieldOfReferenceType;
+        public bool containsFieldOfReferenceType;
 
-        // gets whether this or a base class contains any field of a ReferenceType
+        /// <summary>
+        /// Whether this or a base class contains any field of a ReferenceType.
+        /// </summary>
         [NonSerialized]
-        public System.Boolean containsFieldOfReferenceTypeInInheritenceChain;
+        public bool containsFieldOfReferenceTypeInInheritanceChain;
+
+        public PackedManagedType(
+            bool isValueType, bool isArray, PInt arrayRank, string name, string assembly, PackedManagedField[] fields, 
+            byte[] staticFieldBytes, Option<PInt> baseOrElementTypeIndex, PInt size, ulong typeInfoAddress, 
+            PInt managedTypesArrayIndex
+        ) {
+            this.isValueType = isValueType;
+            this.isArray = isArray;
+            this.arrayRank = arrayRank;
+            this.name = name;
+            this.assembly = assembly;
+            this.fields = fields;
+            this.staticFieldBytes = staticFieldBytes;
+            this.baseOrElementTypeIndex = baseOrElementTypeIndex;
+            this.size = size;
+            this.typeInfoAddress = typeInfoAddress;
+            this.managedTypesArrayIndex = managedTypesArrayIndex;
+        }
+
+        /// <inheritdoc/>
+        string PackedMemorySnapshot.TypeForSubclassSearch.name => name;
 
-        // An array containing descriptions of all instance fields of this type.
+        /// <inheritdoc/>
+        PInt PackedMemorySnapshot.TypeForSubclassSearch.typeArrayIndex => managedTypesArrayIndex;
+
+        /// <inheritdoc/>
+        Option<PInt> PackedMemorySnapshot.TypeForSubclassSearch.baseTypeArrayIndex => baseOrElementTypeIndex;
+        
+        /// <summary>
+        /// An array containing descriptions of all instance fields of this type.
+        /// </summary>
         public PackedManagedField[] instanceFields
         {
             get
@@ -116,7 +192,10 @@ public PackedManagedField[] instanceFields
         }
         [NonSerialized] PackedManagedField[] m_InstanceFields;
 
-        // An array containing descriptions of all static fields of this type, NOT including static fields of base type.
+        /// <summary>
+        /// An array containing descriptions of all static fields of this type, NOT including static fields of base
+        /// type.
+        /// </summary>
         public PackedManagedField[] staticFields
         {
             get
@@ -188,9 +267,9 @@ public bool isPrimitive
                     case "System.ValueType":
                     case "System.ReferenceType":
                         return true;
+                    default:
+                        return false;
                 }
-
-                return false;
             }
         }
 
@@ -209,9 +288,9 @@ public bool isPointer
                     case "System.IntPtr":
                     case "System.UIntPtr":
                         return true;
+                    default:
+                        return false;
                 }
-
-                return false;
             }
         }
 
@@ -222,7 +301,7 @@ public bool isDerivedReferenceType
                 if (isValueType)
                     return false;
 
-                if (baseOrElementTypeIndex == -1)
+                if (!this.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
                     return false;
 
                 if (baseOrElementTypeIndex == managedTypesArrayIndex)
@@ -232,15 +311,18 @@ public bool isDerivedReferenceType
             }
         }
 
-        // An enum derives from System.Enum, which derives from System.ValueType.
+        /// <summary>
+        /// <para/>
+        /// An enum derives from <see cref="System.Enum"/>, which derives from <see cref="System.ValueType"/>.
+        /// </summary>
         public bool isDerivedValueType
         {
             get
             {
-                if (!isValueType)
+                if (isReferenceType)
                     return false;
 
-                if (baseOrElementTypeIndex == -1)
+                if (!this.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
                     return false;
 
                 if (baseOrElementTypeIndex == managedTypesArrayIndex)
@@ -261,12 +343,11 @@ public bool TryGetField(string name, out PackedManagedField field)
                 }
             }
 
-            field = new PackedManagedField();
-            field.managedTypesArrayIndex = -1;
+            field = default;
             return false;
         }
 
-        const System.Int32 k_Version = 1;
+        const int k_Version = 1;
 
         public static void Write(System.IO.BinaryWriter writer, PackedManagedType[] value)
         {
@@ -281,9 +362,9 @@ public static void Write(System.IO.BinaryWriter writer, PackedManagedType[] valu
                 writer.Write(value[n].name);
                 writer.Write(value[n].assembly);
 
-                writer.Write((System.Int32)value[n].staticFieldBytes.Length);
+                writer.Write(value[n].staticFieldBytes.Length);
                 writer.Write(value[n].staticFieldBytes);
-                writer.Write(value[n].baseOrElementTypeIndex);
+                writer.Write(value[n].baseOrElementTypeIndex.fold(-1, _ => _));
                 writer.Write(value[n].size);
                 writer.Write(value[n].typeInfoAddress);
                 writer.Write(value[n].managedTypesArrayIndex);
@@ -301,40 +382,56 @@ public static void Read(System.IO.BinaryReader reader, out PackedManagedType[] v
             if (version >= 1)
             {
                 var length = reader.ReadInt32();
-                stateString = string.Format("Loading {0} Managed Types", length);
+                stateString = $"Loading {length} Managed Types";
                 value = new PackedManagedType[length];
 
                 for (int n = 0, nend = value.Length; n < nend; ++n)
                 {
-                    value[n].isValueType = reader.ReadBoolean();
-                    value[n].isArray = reader.ReadBoolean();
-                    value[n].arrayRank = reader.ReadInt32();
-                    value[n].name = reader.ReadString();
-                    value[n].assembly = reader.ReadString();
-
-                    var count = reader.ReadInt32();
-                    value[n].staticFieldBytes = reader.ReadBytes(count);
-                    value[n].baseOrElementTypeIndex = reader.ReadInt32();
-                    value[n].size = reader.ReadInt32();
-                    value[n].typeInfoAddress = reader.ReadUInt64();
-                    value[n].managedTypesArrayIndex = reader.ReadInt32();
-
-                    PackedManagedField.Read(reader, out value[n].fields);
-
+                    var isValueType = reader.ReadBoolean();
+                    var isArray = reader.ReadBoolean();
+                    var arrayRank = reader.ReadInt32();
+                    
+                    var name = reader.ReadString();
                     // Types without namespace have a preceding period, which we remove here
                     // https://issuetracker.unity3d.com/issues/packedmemorysnapshot-leading-period-symbol-in-typename
-                    if (value[n].name != null && value[n].name.Length > 0 && value[n].name[0] == '.')
-                        value[n].name = value[n].name.Substring(1);
+                    if (name != null && name.Length > 0 && name[0] == '.')
+                        name = value[n].name.Substring(1);
+                    
+                    var assembly = reader.ReadString();
 
-                    value[n].nativeTypeArrayIndex = -1;
+                    var count = reader.ReadInt32();
+                    var staticFieldBytes = reader.ReadBytes(count);
+                    var rawBaseOrElementTypeIndex = reader.ReadInt32();
+                    var baseOrElementTypeIndex = 
+                        rawBaseOrElementTypeIndex == -1 ? None._ : Some(PInt.createOrThrow(rawBaseOrElementTypeIndex));
+                    var size = reader.ReadInt32();
+                    var typeInfoAddress = reader.ReadUInt64();
+                    var managedTypesArrayIndex = reader.ReadInt32();
+
+                    PackedManagedField.Read(reader, out var fields);
+
+                    value[n] = new PackedManagedType(
+                        isValueType: isValueType,
+                        isArray: isArray,
+                        arrayRank: PInt.createOrThrow(arrayRank),
+                        name: name,
+                        assembly: assembly,
+                        staticFieldBytes: staticFieldBytes,
+                        baseOrElementTypeIndex: baseOrElementTypeIndex,
+                        size: PInt.createOrThrow(size),
+                        typeInfoAddress: typeInfoAddress,
+                        managedTypesArrayIndex: PInt.createOrThrow(managedTypesArrayIndex),
+                        fields: fields
+                    );
                 }
             }
         }
 
-        public static PackedManagedType[] FromMemoryProfiler(UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot)
-        {
+        public static PackedManagedType[] FromMemoryProfiler(
+            UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
+        ) {
             var source = snapshot.typeDescriptions;
-            var value = new PackedManagedType[source.GetNumEntries()];
+            var managedTypes = new PackedManagedType[source.GetNumEntries()];
 
             var sourceAssembly = new string[source.assembly.GetNumEntries()];
             source.assembly.GetEntries(0, source.assembly.GetNumEntries(), ref sourceAssembly);
@@ -378,97 +475,176 @@ public static PackedManagedType[] FromMemoryProfiler(UnityEditor.Profiling.Memor
             var fieldTypeIndex = new int[desc.typeIndex.GetNumEntries()];
             desc.typeIndex.GetEntries(0, desc.typeIndex.GetNumEntries(), ref fieldTypeIndex);
 
-            var sourceFieldDescriptions = new PackedManagedField[desc.GetNumEntries()];
-            for (int n=0, nend = sourceFieldDescriptions.Length; n < nend; ++n)
-            {
-                sourceFieldDescriptions[n].name = fieldName[n];
-                sourceFieldDescriptions[n].isStatic = fieldStatic[n];
-                sourceFieldDescriptions[n].offset = fieldOffset[n];
-                sourceFieldDescriptions[n].managedTypesArrayIndex = fieldTypeIndex[n];
+            var sourceFieldDescriptions = new Option<PackedManagedField>[desc.GetNumEntries()];
+            for (int n=0, nend=sourceFieldDescriptions.Length; n < nend; ++n) {
+                var name = fieldName[n];
+                var isStatic = fieldStatic[n];
+                var rawOffset = fieldOffset[n];
+                var maybeOffset = PInt.create(rawOffset);
+                var rawManagedTypesArrayIndex = fieldTypeIndex[n];
+                var maybeManagedTypesArrayIndex = PInt.create(rawManagedTypesArrayIndex);
+
+                if (
+                    maybeOffset.valueOut(out var offset) 
+                    && maybeManagedTypesArrayIndex.valueOut(out var managedTypesArrayIndex)
+                ) {
+                    sourceFieldDescriptions[n] = Some(new PackedManagedField(
+                        name: name, isStatic: isStatic, offset: offset, managedTypesArrayIndex: managedTypesArrayIndex
+                    ));
+                }
             }
 
-            for (int n = 0, nend = value.Length; n < nend; ++n)
-            {
-                value[n] = new PackedManagedType
-                {
-                    isValueType = (sourceFlags[n] & TypeFlags.kValueType) != 0,
-                    isArray = (sourceFlags[n] & TypeFlags.kArray) != 0,
-                    arrayRank = (int)(sourceFlags[n] & TypeFlags.kArrayRankMask)>>16,
-                    name = sourceName[n],
-                    assembly = sourceAssembly[n],
-                    staticFieldBytes = sourceStaticFieldBytes[n],
-                    baseOrElementTypeIndex = sourceBaseOrElementTypeIndex[n],
-                    size = sourceSize[n],
-                    typeInfoAddress = sourceTypeInfoAddress[n],
-                    managedTypesArrayIndex = sourceTypeIndex[n],
-
-                    nativeTypeArrayIndex = -1,
-                };
-
-                value[n].fields = new PackedManagedField[sourceFieldIndices[n].Length];
-                for (var j=0; j< sourceFieldIndices[n].Length; ++j)
-                {
-                    var i = sourceFieldIndices[n][j];
-                    value[n].fields[j].name = sourceFieldDescriptions[i].name;
-                    value[n].fields[j].offset = sourceFieldDescriptions[i].offset;
-                    value[n].fields[j].isStatic = sourceFieldDescriptions[i].isStatic;
-                    value[n].fields[j].managedTypesArrayIndex = sourceFieldDescriptions[i].managedTypesArrayIndex;
+            // A cache for the temporary fields as we don't know how many of them are valid.
+            var fieldsList = new List<PackedManagedField>();
+            for (int n = 0, nend = managedTypes.Length; n < nend; ++n) {
+                var baseOrElementTypeIndex = sourceBaseOrElementTypeIndex[n];
+                var sourceFieldIndicesForValue = sourceFieldIndices[n];
+
+                // Assign fields.
+                fieldsList.Clear();
+                for (var j=0; j < sourceFieldIndicesForValue.Length; ++j) {
+                    var i = sourceFieldIndicesForValue[j];
+                    var maybeField = sourceFieldDescriptions[i];
+                    
+                    // Skip invalid fields.
+                    if (maybeField.valueOut(out var field)) {
+                        fieldsList.Add(new PackedManagedField(
+                            name: field.name,
+                            offset: field.offset,
+                            isStatic: field.isStatic,
+                            managedTypesArrayIndex: field.managedTypesArrayIndex
+                        ));
+                    }
                 }
+                var fields = fieldsList.ToArray();
 
+                var name = sourceName[n];
                 // namespace-less types have a preceding dot, which we remove here
-                if (value[n].name != null && value[n].name.Length > 0 && value[n].name[0] == '.')
-                    value[n].name = value[n].name.Substring(1);
+                if (name != null && name.Length > 0 && name[0] == '.') name = name.Substring(1);
+                
+                managedTypes[n] = new PackedManagedType(
+                    isValueType: (sourceFlags[n] & TypeFlags.kValueType) != 0,
+                    isArray: (sourceFlags[n] & TypeFlags.kArray) != 0,
+                    arrayRank: PInt.createOrThrow((int)(sourceFlags[n] & TypeFlags.kArrayRankMask)>>16),
+                    name: name,
+                    assembly: sourceAssembly[n],
+                    staticFieldBytes: sourceStaticFieldBytes[n],
+                    baseOrElementTypeIndex: 
+                        baseOrElementTypeIndex == -1 ? None._ : Some(PInt.createOrThrow(baseOrElementTypeIndex)),
+                    size: PInt.createOrThrow(sourceSize[n]),
+                    typeInfoAddress: sourceTypeInfoAddress[n],
+                    managedTypesArrayIndex: PInt.createOrThrow(sourceTypeIndex[n]),
+                    fields: fields
+                );
+            }
 
+            var importFailureIndexes = 
+                sourceFieldDescriptions
+                .Select((opt, idx) => (opt, idx))
+                .Where(tpl => tpl.opt.isNone)
+                .Select(tpl => tpl.idx)
+                .ToArray();
+            if (importFailureIndexes.Length > 0) {
+                // Offset will be -1 if the field is a static field with `[ThreadStatic]` attached to it.
+                bool isThreadStatic(int idx) => fieldStatic[idx] && fieldOffset[idx] == -1;
+                
+                var threadStatics = importFailureIndexes.Where(isThreadStatic).ToArray();
+                reportFailures(
+                    "Detected following fields as [ThreadStatic] static fields. We do not know how to determine the "
+                    + $"memory location of these fields, thus we can not crawl them. Take that in mind",
+                    threadStatics
+                );
+                var others = importFailureIndexes.Where(idx => !isThreadStatic(idx)).ToArray();
+                reportFailures(
+                    "Failed to import fields from the Unity memory snapshot due to invalid values, this seems "
+                    + "like a Unity bug", 
+                    others
+                );
+
+                void reportFailures(string description, int[] failureIndexes) {
+                    if (failureIndexes.Length == 0) return;
+                
+                    // Group failures to not overwhelm Unity console window.
+                    var groupedFailures = failureIndexes.OrderBy(_ => _).groupedIn(PInt.createOrThrow(100)).ToArray();
+
+                    for (int idx = 0, idxEnd = groupedFailures.Length; idx < idxEnd; idx++) {
+                        var group = groupedFailures[idx];
+                        var str = string.Join("\n\n", group.Select(idx => {
+                            var managedTypesArrayIndex = fieldTypeIndex[idx];
+                            var typeName = managedTypes[managedTypesArrayIndex].name;
+                            var typeAssembly = managedTypes[managedTypesArrayIndex].assembly;
+                            return $"Field[index={idx}, name={fieldName[idx]}, static={fieldStatic[idx]}, "
+                                   + $"offset={fieldOffset[idx]}, managedTypesArrayIndex={managedTypesArrayIndex}"
+                                   + "]\n"
+                                   + $"@ [assembly '{typeAssembly}'] [type '{typeName}']";
+                        }));
+                    
+                        Debug.LogWarning($"HeapExplorer: {description}:\n{str}");
+                    }
+                }
             }
-            return value;
+            
+            return managedTypes;
         }
 
         public override string ToString()
         {
-            var text = string.Format("name: {0}, isValueType: {1}, isArray: {2}, size: {3}", name, isValueType, isArray, size);
+            var text = $"name: {name}, isValueType: {isValueType}, isArray: {isArray}, size: {size}";
             return text;
         }
     }
 
     public static class PackedManagedTypeUtility
     {
-        public static string GetInheritanceAsString(PackedMemorySnapshot snapshot, int managedTypesArrayIndex)
+        public static string GetInheritanceAsString(PackedMemorySnapshot snapshot, PInt managedTypesArrayIndex)
         {
             var sb = new System.Text.StringBuilder(128);
             GetInheritanceAsString(snapshot, managedTypesArrayIndex, sb);
             return sb.ToString();
         }
 
-        public static void GetInheritanceAsString(PackedMemorySnapshot snapshot, int managedTypesArrayIndex, System.Text.StringBuilder target)
-        {
+        public static void GetInheritanceAsString(
+            PackedMemorySnapshot snapshot, PInt managedTypesArrayIndex, System.Text.StringBuilder target
+        ) {
             var depth = 0;
-            var loopguard = 0;
-
-            while (managedTypesArrayIndex != -1)
-            {
+            var cycleTracker = new CycleTracker<int>();
+
+            var maybeCurrentManagedTypesArrayIndex = Some(managedTypesArrayIndex);
+            cycleTracker.markStartOfSearch();
+            {while (maybeCurrentManagedTypesArrayIndex.valueOut(out var currentManagedTypesArrayIndex)) {
+                if (cycleTracker.markIteration(currentManagedTypesArrayIndex)) {
+                    cycleTracker.reportCycle(
+                        $"{nameof(GetInheritanceAsString)}()", currentManagedTypesArrayIndex,
+                        idx => snapshot.managedTypes[idx].ToString()
+                    );
+                    break;
+                }
+                
                 for (var n = 0; n < depth; ++n)
                     target.Append("  ");
 
-                target.AppendFormat("{0}\n", snapshot.managedTypes[managedTypesArrayIndex].name);
+                target.AppendFormat("{0}\n", snapshot.managedTypes[currentManagedTypesArrayIndex].name);
                 depth++;
 
-                managedTypesArrayIndex = snapshot.managedTypes[managedTypesArrayIndex].baseOrElementTypeIndex;
-                if (++loopguard > 64)
-                    break;
-            }
+                maybeCurrentManagedTypesArrayIndex = 
+                    snapshot.managedTypes[currentManagedTypesArrayIndex].baseOrElementTypeIndex;
+            }}
         }
 
         /// <summary>
         /// Gets whether any type in its inheritance chain has an instance field.
         /// </summary>
-        public static bool HasTypeOrBaseAnyField(PackedMemorySnapshot snapshot, PackedManagedType type, bool checkInstance, bool checkStatic)
-        {
-            var loopguard = 0;
+        public static bool HasTypeOrBaseAnyField(
+            PackedMemorySnapshot snapshot, PackedManagedType type, bool checkInstance, bool checkStatic
+        ) {
+            var cycleTracker = new CycleTracker<int>();
             do
             {
-                if (++loopguard > 64)
-                {
-                    Debug.LogError("loopguard kicked in");
+                if (cycleTracker.markIteration(type.managedTypesArrayIndex)) {
+                    cycleTracker.reportCycle(
+                        $"{nameof(HasTypeOrBaseAnyField)}()", type.managedTypesArrayIndex,
+                        idx => snapshot.managedTypes[idx].ToString()
+                    );
                     break;
                 }
 
@@ -478,10 +654,10 @@ public static bool HasTypeOrBaseAnyField(PackedMemorySnapshot snapshot, PackedMa
                 if (checkStatic && type.staticFields.Length > 0)
                     return true;
 
-                if (type.baseOrElementTypeIndex != -1)
-                    type = snapshot.managedTypes[type.baseOrElementTypeIndex];
+                {if (type.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
+                    type = snapshot.managedTypes[baseOrElementTypeIndex];}
 
-            } while (type.baseOrElementTypeIndex != -1 && type.managedTypesArrayIndex != type.baseOrElementTypeIndex);
+            } while (type.baseOrElementTypeIndex.isSome && Some(type.managedTypesArrayIndex) != type.baseOrElementTypeIndex);
 
             return false;
         }
@@ -491,10 +667,10 @@ public static bool HasTypeOrBaseAnyField(PackedMemorySnapshot snapshot, PackedMa
         /// </summary>
         public static bool HasTypeOrBaseAnyInstanceField(PackedMemorySnapshot snapshot, PackedManagedType type)
         {
-            var loopguard = 0;
+            var loopGuard = 0;
             do
             {
-                if (++loopguard > 64)
+                if (++loopGuard > 64)
                 {
                     Debug.LogError("loopguard kicked in");
                     break;
@@ -503,27 +679,22 @@ public static bool HasTypeOrBaseAnyInstanceField(PackedMemorySnapshot snapshot,
                 if (type.instanceFields.Length > 0)
                     return true;
 
-                if (type.baseOrElementTypeIndex != -1)
-                    type = snapshot.managedTypes[type.baseOrElementTypeIndex];
+                {if (type.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
+                    type = snapshot.managedTypes[baseOrElementTypeIndex];}
 
-            } while (type.baseOrElementTypeIndex != -1 && type.managedTypesArrayIndex != type.baseOrElementTypeIndex);
+            } while (type.baseOrElementTypeIndex.isSome && Some(type.managedTypesArrayIndex) != type.baseOrElementTypeIndex);
 
             return false;
         }
 
         public static bool HasTypeOrBaseAnyInstanceField(PackedMemorySnapshot snapshot, PackedManagedType type, out PackedManagedType fieldType)
         {
-            fieldType = new PackedManagedType();
-            fieldType.managedTypesArrayIndex = -1;
-            fieldType.nativeTypeArrayIndex = -1;
-            fieldType.baseOrElementTypeIndex = -1;
-
-            var loopguard = 0;
+            var loopGuard = 0;
             do
             {
-                if (++loopguard > 64)
+                if (++loopGuard > 64)
                 {
-                    Debug.LogError("loopguard kicked in");
+                    Debug.LogError("HeapExplorer: loopguard kicked in");
                     break;
                 }
 
@@ -533,27 +704,23 @@ public static bool HasTypeOrBaseAnyInstanceField(PackedMemorySnapshot snapshot,
                     return true;
                 }
 
-                if (type.baseOrElementTypeIndex != -1)
-                    type = snapshot.managedTypes[type.baseOrElementTypeIndex];
+                if (type.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
+                    type = snapshot.managedTypes[baseOrElementTypeIndex];
 
-            } while (type.baseOrElementTypeIndex != -1 && type.managedTypesArrayIndex != type.baseOrElementTypeIndex);
+            } while (type.baseOrElementTypeIndex.isSome && Some(type.managedTypesArrayIndex) != type.baseOrElementTypeIndex);
 
+            fieldType = default;
             return false;
         }
 
         public static bool HasTypeOrBaseAnyStaticField(PackedMemorySnapshot snapshot, PackedManagedType type, out PackedManagedType fieldType)
         {
-            fieldType = new PackedManagedType();
-            fieldType.managedTypesArrayIndex = -1;
-            fieldType.nativeTypeArrayIndex = -1;
-            fieldType.baseOrElementTypeIndex = -1;
-
-            var loopguard = 0;
+            var loopGuard = 0;
             do
             {
-                if (++loopguard > 64)
+                if (++loopGuard > 64)
                 {
-                    Debug.LogError("loopguard kicked in");
+                    Debug.LogError("HeapExplorer: loopguard kicked in");
                     break;
                 }
 
@@ -563,11 +730,12 @@ public static bool HasTypeOrBaseAnyStaticField(PackedMemorySnapshot snapshot, Pa
                     return true;
                 }
 
-                if (type.baseOrElementTypeIndex != -1)
-                    type = snapshot.managedTypes[type.baseOrElementTypeIndex];
+                {if (type.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
+                    type = snapshot.managedTypes[baseOrElementTypeIndex];}
 
-            } while (type.baseOrElementTypeIndex != -1 && type.managedTypesArrayIndex != type.baseOrElementTypeIndex);
+            } while (type.baseOrElementTypeIndex.isSome && Some(type.managedTypesArrayIndex) != type.baseOrElementTypeIndex);
 
+            fieldType = default;
             return false;
         }
     }
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySection.cs b/Editor/Scripts/PackedTypes/PackedMemorySection.cs
index b0d693f..8382438 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySection.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySection.cs
@@ -10,18 +10,35 @@
 
 namespace HeapExplorer
 {
-    // A dump of a piece of memory from the player that's being profiled.
+    /// <summary>
+    /// A dump of a piece of memory from the player that's being profiled.
+    /// </summary>
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
     public struct PackedMemorySection
     {
-        // The actual bytes of the memory dump.
-        public System.Byte[] bytes;
-
-        // The start address of this piece of memory.
+        /// <summary>
+        /// The actual bytes of the memory dump.
+        /// </summary>
+        public byte[] bytes;
+
+        /// <summary>
+        /// The start address of this piece of memory. Inclusive.
+        /// </summary>
         public System.UInt64 startAddress;
 
-        // The index into the snapshot.managedHeapSections array
+        /// <summary>
+        /// The end address of this piece of memory. Exclusive.
+        /// </summary>
+        public ulong endAddress {
+            get {
+                return startAddress + size;
+            }
+        }
+
+        /// <summary>
+        /// The index into the <see cref="PackedMemorySnapshot.managedHeapSections"/> array
+        /// </summary>
         [System.NonSerialized]
         public int arrayIndex;
 
@@ -37,6 +54,13 @@ public ulong size
 
         const System.Int32 k_Version = 1;
 
+        /// <summary>
+        /// Does this memory section contain the provided memory address?
+        /// </summary>
+        public bool containsAddress(ulong address) {
+            return address >= startAddress && address < endAddress;
+        }
+        
         public static void Write(System.IO.BinaryWriter writer, PackedMemorySection[] value)
         {
             writer.Write(k_Version);
@@ -65,7 +89,8 @@ public static void Read(System.IO.BinaryReader reader, out PackedMemorySection[]
                 for (int n = 0, nend = value.Length; n < nend; ++n)
                 {
                     if ((n % onePercent) == 0)
-                        stateString = string.Format("Loading Managed Heap Sections\n{0}/{1}, {2:F0}% done", n + 1, length, ((n + 1) / (float)length) * 100);
+                        stateString =
+                            $"Loading Managed Heap Sections\n{n + 1}/{length}, {((n + 1) / (float) length) * 100:F0}% done";
 
                     var count = reader.ReadInt32();
                     value[n].bytes = reader.ReadBytes(count);
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
index 0f05431..07b6e55 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
@@ -2,11 +2,10 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
 using UnityEngine;
-using UnityEditor;
 using System;
+using System.Collections.Generic;
+using HeapExplorer.Utilities;
 
 namespace HeapExplorer
 {
@@ -15,69 +14,94 @@ public partial class PackedMemorySnapshot
     {
         public PackedMemorySnapshotHeader header = new PackedMemorySnapshotHeader();
 
-        // Descriptions of all the C++ unity types the profiled player knows about.
+        /// <summary>Descriptions of all the C++ unity types the profiled player knows about.</summary>
         public PackedNativeType[] nativeTypes = new PackedNativeType[0];
 
-        // All native C++ objects that were loaded at time of the snapshot.
+        /// <summary>All native C++ objects that were loaded at time of the snapshot.</summary>
         public PackedNativeUnityEngineObject[] nativeObjects = new PackedNativeUnityEngineObject[0];
 
-        // All GC handles in use in the memorysnapshot.
+        /// <summary>All GC handles in use in the memory snapshot.</summary>
         public PackedGCHandle[] gcHandles = new PackedGCHandle[0];
 
-        // The unmodified connections array of "from -> to" pairs that describe which things are keeping which other things alive.
-        // connections 0..gcHandles.Length-1 represent connections FROM gchandles
-        // connections gcHandles.Length..connections.Length-1 represent connections FROM native
+        /// <summary>
+        /// The unmodified connections array of "from -> to" pairs that describe which things are keeping which other things alive.
+        /// <para/>
+        /// connections 0..gcHandles.Length-1 represent connections FROM gchandles
+        /// <para/>
+        /// connections gcHandles.Length..connections.Length-1 represent connections FROM native
+        /// </summary>
         public PackedConnection[] connections = new PackedConnection[0];
 
-        // Array of actual managed heap memory sections. These are sorted by address after snapshot initialization.
+        /// <summary>
+        /// Array of actual managed heap memory sections. These are sorted by address after snapshot initialization.
+        /// </summary>
         public PackedMemorySection[] managedHeapSections = new PackedMemorySection[0];
 
-        // Descriptions of all the managed types that were known to the virtual machine when the snapshot was taken.
+        /// <summary>
+        /// Descriptions of all the managed types that were known to the virtual machine when the snapshot was taken.
+        /// </summary>
         public PackedManagedType[] managedTypes = new PackedManagedType[0];
 
-        // Information about the virtual machine running executing the managade code inside the player.
-        public PackedVirtualMachineInformation virtualMachineInformation = new PackedVirtualMachineInformation();
+        /// <summary>
+        /// Information about the virtual machine running executing the managed code inside the player.
+        /// </summary>
+        public PackedVirtualMachineInformation virtualMachineInformation;
 
+        /// <summary>
+        /// Allows you to update the Unity progress bar with given <see cref="stepName"/>.
+        /// </summary>
+        public delegate void UpdateUnityUI(string stepName, int stepIndex, int totalSteps);
+        
         /// <summary>
         /// Converts an Unity PackedMemorySnapshot to our own format.
         /// </summary>
-        public static PackedMemorySnapshot FromMemoryProfiler(MemorySnapshotProcessingArgs args)
-        {
+        public static PackedMemorySnapshot FromMemoryProfiler(
+            MemorySnapshotProcessingArgs args
+        ) {
             var source = args.source;
 
             var value = new PackedMemorySnapshot();
-            try
-            {
+            try {
+                const int TOTAL_STEPS = 9;
+                
+                args.maybeUpdateUI?.Invoke("Verifying memory profiler snapshot", 0, TOTAL_STEPS);
                 VerifyMemoryProfilerSnapshot(source);
 
                 value.busyString = "Loading Header";
+                args.maybeUpdateUI?.Invoke(value.busyString, 1, TOTAL_STEPS);
                 value.header = PackedMemorySnapshotHeader.FromMemoryProfiler();
 
-                value.busyString = string.Format("Loading {0} Native Types", source.nativeTypes.GetNumEntries());
+                value.busyString = $"Loading {source.nativeTypes.GetNumEntries()} Native Types";
+                args.maybeUpdateUI?.Invoke(value.busyString, 2, TOTAL_STEPS);
                 value.nativeTypes = PackedNativeType.FromMemoryProfiler(source);
 
-                value.busyString = string.Format("Loading {0} Native Objects", source.nativeObjects.GetNumEntries());
+                value.busyString = $"Loading {source.nativeObjects.GetNumEntries()} Native Objects";
+                args.maybeUpdateUI?.Invoke(value.busyString, 3, TOTAL_STEPS);
                 value.nativeObjects = PackedNativeUnityEngineObject.FromMemoryProfiler(source);
 
-                value.busyString = string.Format("Loading {0} GC Handles", source.gcHandles.GetNumEntries());
+                value.busyString = $"Loading {source.gcHandles.GetNumEntries()} GC Handles";
+                args.maybeUpdateUI?.Invoke(value.busyString, 4, TOTAL_STEPS);
                 value.gcHandles = PackedGCHandle.FromMemoryProfiler(source);
 
-                value.busyString = string.Format("Loading {0} Object Connections", source.connections.GetNumEntries());
+                value.busyString = $"Loading {source.connections.GetNumEntries()} Object Connections";
+                args.maybeUpdateUI?.Invoke(value.busyString, 5, TOTAL_STEPS);
                 value.connections = PackedConnection.FromMemoryProfiler(source);
 
-                value.busyString = string.Format("Loading {0} Managed Heap Sections", source.managedHeapSections.GetNumEntries());
+                value.busyString = $"Loading {source.managedHeapSections.GetNumEntries()} Managed Heap Sections";
+                args.maybeUpdateUI?.Invoke(value.busyString, 6, TOTAL_STEPS);
                 value.managedHeapSections = PackedMemorySection.FromMemoryProfiler(source);
 
-                value.busyString = string.Format("Loading {0} Managed Types", source.typeDescriptions.GetNumEntries());
+                value.busyString = $"Loading {source.typeDescriptions.GetNumEntries()} Managed Types";
+                args.maybeUpdateUI?.Invoke(value.busyString, 7, TOTAL_STEPS);
                 value.managedTypes = PackedManagedType.FromMemoryProfiler(source);
 
                 value.busyString = "Loading VM Information";
+                args.maybeUpdateUI?.Invoke(value.busyString, 8, TOTAL_STEPS);
                 value.virtualMachineInformation = PackedVirtualMachineInformation.FromMemoryProfiler(source);
             }
-            catch (System.Exception e)
+            catch (Exception e)
             {
                 Debug.LogException(e);
-                value = null;
                 throw;
             }
             return value;
@@ -164,11 +188,81 @@ public void SaveToFile(string filePath)
                 }
             }
         }
+
+        /// <summary>
+        /// Cache for <see cref="isOrContainsReferenceType"/>
+        /// </summary>
+        Dictionary<int, bool> _containsReferenceTypeCache = new Dictionary<int, bool>();
+
+        /// <summary>
+        /// Returns true if the type index for <see cref="managedTypes"/> is a reference type or contains a reference
+        /// type as any of its fields. Value types are checked recursively, so this returns true for:
+        /// 
+        /// <code><![CDATA[
+        /// struct Foo {
+        ///   Bar bar;
+        /// }
+        ///
+        /// struct Bar {
+        ///   string str;
+        /// }
+        /// ]]></code> 
+        /// </summary>
+        /// TODO: test
+        public bool isOrContainsReferenceType(int managedTypeIndex) =>
+            _containsReferenceTypeCache.getOrUpdate(
+                managedTypeIndex, 
+                this, 
+                (managedTypeIndex, self) => {
+                    var type = self.managedTypes[managedTypeIndex];
+                    if (type.isReferenceType)
+                        return true;
+                    
+                    // Primitive types do not contain reference types in them, but contain self-references for structs,
+                    // making us go into recursion forever.
+                    if (type.isPrimitive)
+                        return false;
+
+                    var managedTypesLength = self.managedTypes.Length;
+                    var instanceFields = type.instanceFields;
+
+                    for (int n = 0, nend = instanceFields.Length; n < nend; ++n) {
+                        var fieldTypeIndex = instanceFields[n].managedTypesArrayIndex;
+                        if (fieldTypeIndex < 0 || fieldTypeIndex >= managedTypesLength) {
+                            self.Error(
+                                $"HeapExplorer: '{type.name}' field '{n}' is out of bounds '{fieldTypeIndex}', ignoring."
+                            );
+                            continue;
+                        }
+
+                        if (fieldTypeIndex == managedTypeIndex) {
+                            self.Error(
+                                $"HeapExplorer: '{type.name}' field '{instanceFields[n].name}' is a value type that "
+                                + "contains itself, that should be impossible!."
+                            );
+                            continue;
+                        }
+                        
+                        if (self.isOrContainsReferenceType(fieldTypeIndex))
+                            return true;
+                    }
+
+                    return false;
+                }
+            );
     }
 
     // Specifies how an Unity MemorySnapshot must be converted to HeapExplorer format.
-    public class MemorySnapshotProcessingArgs
-    {
-        public UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot source;
+    public class MemorySnapshotProcessingArgs {
+        public readonly UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot source;
+        public PackedMemorySnapshot.UpdateUnityUI maybeUpdateUI;
+
+        public MemorySnapshotProcessingArgs(
+            UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot source, 
+            PackedMemorySnapshot.UpdateUnityUI maybeUpdateUI = null
+        ) {
+            this.source = source;
+            this.maybeUpdateUI = maybeUpdateUI;
+        }
     }
 }
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
index d7d2b68..ccf38ae 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
@@ -4,12 +4,13 @@
 //
 
 //#define ENABLE_PROFILING
-using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
-using UnityEditor;
 using System;
+using System.Linq;
 using System.Threading;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -35,7 +36,7 @@ public partial class PackedMemorySnapshot
         public PackedManagedStaticField[] managedStaticFields = new PackedManagedStaticField[0];
 
         /// <summary>
-        /// Indices into the managedTypes array of static types.
+        /// Indices into the <see cref="managedTypes"/> array of static types.
         /// </summary>
         [NonSerialized]
         public int[] managedStaticTypes = new int[0];
@@ -50,7 +51,10 @@ public partial class PackedMemorySnapshot
         /// CoreTypes is a helper class that contains indices to frequently used classes, such as MonoBehaviour.
         /// </summary>
         [NonSerialized]
-        public PackedCoreTypes coreTypes = new PackedCoreTypes();
+        Option<PackedCoreTypes> _coreTypes = None._;
+        
+        /// <inheritdoc cref="_coreTypes"/>
+        public PackedCoreTypes coreTypes => _coreTypes.getOrThrow("core types not initialized");
 
         /// <summary>
         /// Write to busyString while processing, such as loading a memory snapshot, causes heap explorer
@@ -80,11 +84,11 @@ public bool abortActiveStepRequested
             set;
         }
 
-        [NonSerialized] Dictionary<UInt64, int> m_FindManagedObjectOfNativeObjectLUT;
-        [NonSerialized] Dictionary<UInt64, int> m_FindManagedTypeOfTypeInfoAddressLUT;
-        [NonSerialized] Dictionary<UInt64, int> m_FindNativeObjectOfAddressLUT;
-        [NonSerialized] Dictionary<UInt64, int> m_FindManagedObjectOfAddressLUT;
-        [NonSerialized] Dictionary<ulong, int> m_FindGCHandleOfTargetAddressLUT;
+        [NonSerialized] Dictionary<UInt64, PInt> m_FindManagedObjectOfNativeObjectLUT;
+        [NonSerialized] Dictionary<UInt64, PInt> m_FindManagedTypeOfTypeInfoAddressLUT;
+        [NonSerialized] Dictionary<UInt64, PInt> m_FindNativeObjectOfAddressLUT;
+        [NonSerialized] Dictionary<UInt64, PackedManagedObject.ArrayIndex> m_FindManagedObjectOfAddressLUT;
+        [NonSerialized] Dictionary<ulong, PInt> m_FindGCHandleOfTargetAddressLUT;
         [NonSerialized] Dictionary<UInt64, List<PackedConnection>> m_ConnectionsFrom = new Dictionary<ulong, List<PackedConnection>>(1024 * 32);
         [NonSerialized] Dictionary<UInt64, List<PackedConnection>> m_ConnectionsTo = new Dictionary<ulong, List<PackedConnection>>(1024 * 32);
 
@@ -123,44 +127,37 @@ public void FindManagedStaticFieldsOfType(PackedManagedType type, List<int> targ
         /// Find the managed object type at the specified address.
         /// </summary>
         /// <param name="address">The managed object memory address.</param>
-        /// <returns>An index into the snapshot.managedTypes array on success, -1 otherwise.</returns>
-        public int FindManagedObjectTypeOfAddress(System.UInt64 address)
-        {
+        /// <returns>
+        /// An index into the <see cref="PackedMemorySnapshot.managedTypes"/> array on success, `None` otherwise.
+        /// </returns>
+        public Option<PInt> FindManagedObjectTypeOfAddress(ulong address) {
             // IL2CPP has the class pointer as the first member of the object.
-            int typeIndex = FindManagedTypeOfTypeInfoAddress(address);
-            if (typeIndex != -1)
-                return typeIndex;
+            {if (FindManagedTypeOfTypeInfoAddress(address).valueOut(out var il2cppAddress))
+                return Some(il2cppAddress);}
 
             // Mono has a vtable pointer as the first member of the object.
             // The first member of the vtable is the class pointer.
-            var heapIndex = FindHeapOfAddress(address);
-            if (heapIndex == -1)
-                return -1;
+            if (!FindHeapOfAddress(address).valueOut(out var heapIndex)) return None._;
 
             var vtable = managedHeapSections[heapIndex];
-            var offset = (int)(address - vtable.startAddress);
-            var vtableClassPointer = virtualMachineInformation.pointerSize == 8 ? BitConverter.ToUInt64(vtable.bytes, offset) : BitConverter.ToUInt32(vtable.bytes, offset);
-            if (vtableClassPointer == 0)
-                return -1;
+            var vtableClassPointerOffset = (int) (address - vtable.startAddress);
+            var vtableClassPointer =
+                virtualMachineInformation.pointerSize.readPointer(vtable.bytes, vtableClassPointerOffset);
+            if (vtableClassPointer == 0) return None._;
 
             // Mono has a vtable pointer as the first member of the object.
             // The first member of the vtable is the class pointer.
-            heapIndex = FindHeapOfAddress(vtableClassPointer);
-            if (heapIndex == -1)
-            {
-                heapIndex = FindManagedTypeOfTypeInfoAddress(vtableClassPointer);
+            if (!FindHeapOfAddress(vtableClassPointer).valueOut(out heapIndex)) {
+                var maybeHeapIndex = FindManagedTypeOfTypeInfoAddress(vtableClassPointer);
                 //Error("Cannot find memory segment for vtableClassPointer pointing at address '{0:X}'.", vtableClassPointer);
-                return heapIndex;
+                return maybeHeapIndex;
             }
 
-            offset = (int)(vtableClassPointer - managedHeapSections[heapIndex].startAddress);
-
-            if (virtualMachineInformation.pointerSize == 8)
-                typeIndex = FindManagedTypeOfTypeInfoAddress(BitConverter.ToUInt64(managedHeapSections[heapIndex].bytes, offset));
-            else if (virtualMachineInformation.pointerSize == 4)
-                typeIndex = FindManagedTypeOfTypeInfoAddress(BitConverter.ToUInt32(managedHeapSections[heapIndex].bytes, offset));
-
-            return typeIndex;
+            var typeInfoAddressOffset = (int) (vtableClassPointer - managedHeapSections[heapIndex].startAddress);
+            var typeInfoAddressBytes = managedHeapSections[heapIndex].bytes;
+            var typeInfoAddress = 
+                virtualMachineInformation.pointerSize.readPointer(typeInfoAddressBytes, typeInfoAddressOffset);
+            return FindManagedTypeOfTypeInfoAddress(typeInfoAddress);
         }
 
         /// <summary>
@@ -168,276 +165,272 @@ public int FindManagedObjectTypeOfAddress(System.UInt64 address)
         /// </summary>
         /// <param name="nativeObjectAddress">The native object address.</param>
         /// <returns>An index into the snapshot.managedObjects array on success, -1 otherwise.</returns>
-        public int FindManagedObjectOfNativeObject(UInt64 nativeObjectAddress)
+        public int? FindManagedObjectOfNativeObject(UInt64 nativeObjectAddress)
         {
             if (nativeObjectAddress == 0)
-                return -1;
+                throw new ArgumentException("address should not be 0", nameof(nativeObjectAddress));
 
             if (m_FindManagedObjectOfNativeObjectLUT == null)
             {
-                m_FindManagedObjectOfNativeObjectLUT = new Dictionary<ulong, int>(managedObjects.Length);
-                for (int n = 0, nend = managedObjects.Length; n < nend; ++n)
+                m_FindManagedObjectOfNativeObjectLUT = new Dictionary<ulong, PInt>(managedObjects.Length);
+                for (PInt n = PInt._0, nend = managedObjects.LengthP(); n < nend; ++n)
                 {
-                    if (managedObjects[n].nativeObjectsArrayIndex >= 0)
+                    if (managedObjects[n].nativeObjectsArrayIndex.valueOut(out var nativeObjectsArrayIndex))
                     {
-                        var address = (ulong)nativeObjects[managedObjects[n].nativeObjectsArrayIndex].nativeObjectAddress;
+                        var address = nativeObjects[nativeObjectsArrayIndex].nativeObjectAddress;
                         m_FindManagedObjectOfNativeObjectLUT[address] = n;
                     }
                 }
             }
 
-            int index;
-            if (m_FindManagedObjectOfNativeObjectLUT.TryGetValue(nativeObjectAddress, out index))
+            if (m_FindManagedObjectOfNativeObjectLUT.TryGetValue(nativeObjectAddress, out var index))
                 return index;
 
-            return -1;
+            return null;
         }
 
         /// <summary>
         /// Find the managed object at the specified address.
         /// </summary>
         /// <param name="managedObjectAddress">The managed object address.</param>
-        /// <returns>An index into the snapshot.managedObjects array on success, -1 otherwise.</returns>
-        public int FindManagedObjectOfAddress(UInt64 managedObjectAddress)
-        {
+        /// <returns>An index into the snapshot.managedObjects array on success, `None` otherwise.</returns>
+        public Option<PackedManagedObject.ArrayIndex> FindManagedObjectOfAddress(UInt64 managedObjectAddress) {
             if (managedObjectAddress == 0)
-                return -1;
+                return Utils.zeroAddressAccessError<PackedManagedObject.ArrayIndex>(nameof(managedObjectAddress));
 
             if (m_FindManagedObjectOfAddressLUT == null)
             {
-                m_FindManagedObjectOfAddressLUT = new Dictionary<ulong, int>(managedObjects.Length);
-                for (int n = 0, nend = managedObjects.Length; n < nend; ++n)
-                    m_FindManagedObjectOfAddressLUT[managedObjects[n].address] = n;
+                m_FindManagedObjectOfAddressLUT = new Dictionary<ulong, PackedManagedObject.ArrayIndex>(managedObjects.Length);
+                for (PInt n = PInt._0, nend = managedObjects.LengthP(); n < nend; ++n) {
+                    m_FindManagedObjectOfAddressLUT[managedObjects[n].address] = PackedManagedObject.ArrayIndex.newObject(n);
+                }
             }
 
-            int index;
-            if (m_FindManagedObjectOfAddressLUT.TryGetValue(managedObjectAddress, out index))
-                return index;
-
-            return -1;
+            return m_FindManagedObjectOfAddressLUT.get(managedObjectAddress);
         }
 
         /// <summary>
         /// Find the native object at the specified address.
         /// </summary>
         /// <param name="nativeObjectAddress">The native object address.</param>
-        /// <returns>An index into the snapshot.nativeObjects array on success, -1 otherwise.</returns>
-        public int FindNativeObjectOfAddress(UInt64 nativeObjectAddress)
+        /// <returns>An index into the <see cref="nativeObjects"/> array on success, `None` otherwise.</returns>
+        public Option<PInt> FindNativeObjectOfAddress(UInt64 nativeObjectAddress)
         {
-            if (nativeObjectAddress == 0)
-                return -1;
+            if (nativeObjectAddress == 0) throw new ArgumentException(
+                "address can't be 0", nameof(nativeObjectAddress)
+            );
 
             if (m_FindNativeObjectOfAddressLUT == null)
             {
-                m_FindNativeObjectOfAddressLUT = new Dictionary<ulong, int>(nativeObjects.Length);
-                for (int n = 0, nend = nativeObjects.Length; n < nend; ++n)
-                    m_FindNativeObjectOfAddressLUT[(ulong)nativeObjects[n].nativeObjectAddress] = n;
+                m_FindNativeObjectOfAddressLUT = new Dictionary<ulong, PInt>(nativeObjects.Length);
+                for (PInt n = PInt._0, nend = nativeObjects.LengthP(); n < nend; ++n)
+                    m_FindNativeObjectOfAddressLUT[nativeObjects[n].nativeObjectAddress] = n;
             }
 
-            int index;
-            if (m_FindNativeObjectOfAddressLUT.TryGetValue(nativeObjectAddress, out index))
-                return index;
-
-            return -1;
+            return m_FindNativeObjectOfAddressLUT.get(nativeObjectAddress);
         }
 
         /// <summary>
         /// Find the GCHandle at the specified address.
         /// </summary>
         /// <param name="targetAddress">The corresponding managed object address.</param>
-        /// <returns>An index into the snapshot.gcHandles array on success, -1 otherwise.</returns>
-        public int FindGCHandleOfTargetAddress(UInt64 targetAddress)
+        /// <returns>An index into the <see cref="gcHandles"/> array on success, `None` otherwise.</returns>
+        public Option<PInt> FindGCHandleOfTargetAddress(UInt64 targetAddress)
         {
             if (targetAddress == 0)
-                return -1;
+                throw new ArgumentException("address should not be 0", nameof(targetAddress));
 
             if (m_FindGCHandleOfTargetAddressLUT == null)
             {
-                m_FindGCHandleOfTargetAddressLUT = new Dictionary<ulong, int>(gcHandles.Length);
+                m_FindGCHandleOfTargetAddressLUT = new Dictionary<ulong, PInt>(gcHandles.Length);
                 for (int n = 0, nend = gcHandles.Length; n < nend; ++n)
                     m_FindGCHandleOfTargetAddressLUT[gcHandles[n].target] = gcHandles[n].gcHandlesArrayIndex;
             }
 
-            int index;
-            if (m_FindGCHandleOfTargetAddressLUT.TryGetValue(targetAddress, out index))
-                return index;
-
-            return -1;
+            return m_FindGCHandleOfTargetAddressLUT.get(targetAddress);
         }
 
         /// <summary>
         /// Find the managed type of the address where the TypeInfo is stored.
         /// </summary>
         /// <param name="typeInfoAddress">The type info address.</param>
-        /// <returns>An index into the snapshot.managedTypes array on success, -1 otherwise.</returns>
-        public int FindManagedTypeOfTypeInfoAddress(UInt64 typeInfoAddress)
+        /// <returns>An index into the <see cref="managedTypes"/> array on success, `None` otherwise.</returns>
+        public Option<PInt> FindManagedTypeOfTypeInfoAddress(UInt64 typeInfoAddress)
         {
-            if (typeInfoAddress == 0)
-                return -1;
+            if (typeInfoAddress == 0) return Utils.zeroAddressAccessError<PInt>(nameof(typeInfoAddress));
 
+            // Initialize the Look Up Table if it's not initialized.
             if (m_FindManagedTypeOfTypeInfoAddressLUT == null)
             {
-                m_FindManagedTypeOfTypeInfoAddressLUT = new Dictionary<ulong, int>(managedTypes.Length);
+                m_FindManagedTypeOfTypeInfoAddressLUT = new Dictionary<ulong, PInt>(managedTypes.Length);
                 for (int n = 0, nend = managedTypes.Length; n < nend; ++n)
                     m_FindManagedTypeOfTypeInfoAddressLUT[managedTypes[n].typeInfoAddress] = managedTypes[n].managedTypesArrayIndex;
             }
 
-            int index;
-            if (m_FindManagedTypeOfTypeInfoAddressLUT.TryGetValue(typeInfoAddress, out index))
-                return index;
-
-            return -1;
+            return m_FindManagedTypeOfTypeInfoAddressLUT.get(typeInfoAddress);
         }
 
         /// <summary>
         /// Find the managed heap section of the specified address.
         /// </summary>
         /// <param name="address">The memory address.</param>
-        /// <returns>An index into the snapshot.managedHeapSections array on success, -1 otherwise.</returns>
-        public int FindHeapOfAddress(UInt64 address)
-        {
-            var first = 0;
-            var last = managedHeapSections.Length - 1;
+        /// <returns>An index into the <see cref="managedHeapSections"/> array on success, `None` otherwise.</returns>
+        public Option<int> FindHeapOfAddress(UInt64 address) {
+            return binarySearch() 
+                   // Debug code - try linear search to see if algorithm is broken.
+                   || linearSearch();
+
+            Option<int> binarySearch() {
+                var first = 0;
+                var last = managedHeapSections.Length - 1;
+
+                while (first <= last) {
+                    var mid = (first + last) >> 1;
+                    var section = managedHeapSections[mid];
+
+                    if (section.containsAddress(address))
+                        return Some(mid);
+                    else if (address < section.startAddress)
+                        last = mid - 1;
+                    else if (address > section.startAddress)
+                        first = mid + 1;
+                }
 
-            while (first <= last)
-            {
-                var mid = (first + last) >> 1;
-                var section = managedHeapSections[mid];
-                var end = section.startAddress + (ulong)section.bytes.Length;
-
-                if (address >= section.startAddress && address < end)
-                    return mid;
-                else if (address < section.startAddress)
-                    last = mid - 1;
-                else if (address > section.startAddress)
-                    first = mid + 1;
+                return None._;
             }
 
-            return -1;
+            Option<int> linearSearch() {
+                var sectionCount = managedHeapSections.Length;
+                for (var index = 0; index < sectionCount; index++) {
+                    if (managedHeapSections[index].containsAddress(address)) {
+                        Debug.LogWarning(
+                            $"HeapExplorer: FindHeapOfAddress(0x{address:X}) - binary search has failed, but linear search "
+                            + "succeeded, this indicated a bug in the algorithm."
+                        );
+                        return Some(index);
+                    }
+                }
+
+                return None._;
+            }
         }
 
         /// <summary>
         /// Add a connection between two objects, such as a connection from a native object to its managed counter-part.
         /// </summary>
-        /// <param name="fromKind">The connection kind, that is pointing to another object.</param>
-        /// <param name="fromIndex">An index into a snapshot array, depending on specified fromKind. If the kind would be 'Native', then it must be an index into the snapshot.nativeObjects array.</param>
-        /// <param name="toKind">The connection kind, to which the 'from' object is pointing to.</param>
-        /// <param name="toIndex">An index into a snapshot array, depending on the specified toKind. If the kind would be 'Native', then it must be an index into the snapshot.nativeObjects array.</param>
-        public void AddConnection(PackedConnection.Kind fromKind, int fromIndex, PackedConnection.Kind toKind, int toIndex)
-        {
-            var connection = new PackedConnection
-            {
-                fromKind = fromKind,
-                from = fromIndex,
-                toKind = toKind,
-                to = toIndex
-            };
+        public void AddConnection(PackedConnection.From from, PackedConnection.Pair to) {
+            var connection = new PackedConnection(from, to);
 
-            if (connection.fromKind != PackedConnection.Kind.None && connection.from != -1)
-            {
-                var key = ComputeConnectionKey(connection.fromKind, connection.from);
-
-                List<PackedConnection> list;
-                if (!m_ConnectionsFrom.TryGetValue(key, out list))
-                    m_ConnectionsFrom[key] = list = new List<PackedConnection>(1); // Capacity=1 to reduce memory usage on HUGE memory snapshots
-
-                list.Add(connection);
-            }
-
-            if (connection.toKind != PackedConnection.Kind.None && connection.to != -1)
-            {
-                if (connection.to < 0)
-                    connection.to = -connection.to;
-
-                var key = ComputeConnectionKey(connection.toKind, connection.to);
-
-                List<PackedConnection> list;
-                if (!m_ConnectionsTo.TryGetValue(key, out list))
-                    m_ConnectionsTo[key] = list = new List<PackedConnection>(1); // Capacity=1 to reduce memory usage on HUGE memory snapshots
+            addTo(from.pair.ComputeConnectionKey(), m_ConnectionsFrom);
+            addTo(to.ComputeConnectionKey(), m_ConnectionsTo);
 
+            void addTo<K>(K key, Dictionary<K, List<PackedConnection>> dict) {
+                var list = dict.getOrUpdate(key, _ => 
+                    // Capacity=1 to reduce memory usage on HUGE memory snapshots
+                    new List<PackedConnection>(1)
+                );
                 list.Add(connection);
             }
         }
 
-        ulong ComputeConnectionKey(PackedConnection.Kind kind, int index)
-        {
-            var value = (((ulong)kind << 50) + (ulong)index);
-            return value;
-        }
-
-        void GetConnectionsInternal(PackedConnection.Kind kind, int index, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            var key = ComputeConnectionKey(kind, index);
+        void GetConnectionsInternal<TReferences, TReferencedBy>(
+            PackedConnection.Pair pair, List<TReferences> references, List<TReferencedBy> referencedBy,
+            Func<PackedConnection, TReferences> convertReferences,
+            Func<PackedConnection, TReferencedBy> convertReferencedBy
+        ) {
+            var key = pair.ComputeConnectionKey();
 
             if (references != null)
             {
-                List<PackedConnection> refs;
-                if (m_ConnectionsFrom.TryGetValue(key, out refs))
-                    references.AddRange(refs);
+                if (m_ConnectionsFrom.TryGetValue(key, out var refs))
+                    references.AddRange(refs.Select(convertReferences));
             }
 
             if (referencedBy != null)
             {
-                List<PackedConnection> refsBy;
-                if (m_ConnectionsTo.TryGetValue(key, out refsBy))
-                    referencedBy.AddRange(refsBy);
+                if (m_ConnectionsTo.TryGetValue(key, out var refsBy))
+                    referencedBy.AddRange(refsBy.Select(convertReferencedBy));
             }
         }
 
-        public void GetConnectionsCount(PackedConnection.Kind kind, int index, out int referencesCount, out int referencedByCount)
+        public void GetConnectionsCount(PackedConnection.Pair pair, out int referencesCount, out int referencedByCount)
         {
             referencesCount = 0;
             referencedByCount = 0;
 
-            var key = ComputeConnectionKey(kind, index);
+            var key = pair.ComputeConnectionKey();
 
-            List<PackedConnection> refs;
-            if (m_ConnectionsFrom.TryGetValue(key, out refs))
+            if (m_ConnectionsFrom.TryGetValue(key, out var refs))
                 referencesCount = refs.Count;
 
-            List<PackedConnection> refBy;
-            if (m_ConnectionsTo.TryGetValue(key, out refBy))
+            if (m_ConnectionsTo.TryGetValue(key, out var refBy))
                 referencedByCount = refBy.Count;
         }
 
-        public void GetConnections(PackedManagedStaticField staticField, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            var index = staticField.staticFieldsArrayIndex;
-            if (index == -1)
-                return;
-
-            GetConnectionsInternal(PackedConnection.Kind.StaticField, index, references, referencedBy);
-        }
-
-        public void GetConnections(PackedNativeUnityEngineObject nativeObj, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            var index = nativeObj.nativeObjectsArrayIndex;
-            if (index == -1)
-                return;
-
-            GetConnectionsInternal(PackedConnection.Kind.Native, index, references, referencedBy);
-        }
-
-        public void GetConnections(PackedGCHandle gcHandle, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            var index = gcHandle.gcHandlesArrayIndex;
-            if (index == -1)
-                return;
-
-            GetConnectionsInternal(PackedConnection.Kind.GCHandle, index, references, referencedBy);
-        }
-
-        public void GetConnections(PackedManagedObject managedObject, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            var index = managedObject.managedObjectsArrayIndex;
-            if (index == -1)
-                return;
-
-            GetConnectionsInternal(PackedConnection.Kind.Managed, index, references, referencedBy);
-        }
-
-        public void GetConnections(PackedMemorySection memorySection, List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
+        public void GetConnections<TReferences, TReferencedBy>(
+            PackedManagedStaticField staticField, List<TReferences> references, List<TReferencedBy> referencedBy,
+            Func<PackedConnection, TReferences> convertReferences,
+            Func<PackedConnection, TReferencedBy> convertReferencedBy
+        ) {
+            GetConnectionsInternal(
+                new PackedConnection.Pair(PackedConnection.Kind.StaticField, staticField.staticFieldsArrayIndex), 
+                references, referencedBy, convertReferences, convertReferencedBy
+            );
+        }
+        
+        public void GetConnections(
+            PackedManagedStaticField staticField, List<PackedConnection> references, List<PackedConnection> referencedBy
+        ) => GetConnections(staticField, references, referencedBy, _ => _, _ => _);
+
+        public void GetConnections<TReferences, TReferencedBy>(
+            PackedNativeUnityEngineObject nativeObj, List<TReferences> references, List<TReferencedBy> referencedBy,
+            Func<PackedConnection, TReferences> convertReferences,
+            Func<PackedConnection, TReferencedBy> convertReferencedBy
+        ) {
+            GetConnectionsInternal(
+                new PackedConnection.Pair(PackedConnection.Kind.Native, nativeObj.nativeObjectsArrayIndex),
+                references, referencedBy, convertReferences, convertReferencedBy
+            );
+        }
+
+        public void GetConnections(
+            PackedNativeUnityEngineObject nativeObj, List<PackedConnection> references, List<PackedConnection> referencedBy
+        ) => GetConnections(nativeObj, references, referencedBy, _ => _, _ => _);
+
+        public void GetConnections<TReferences, TReferencedBy>(
+            PackedGCHandle gcHandle, List<TReferences> references, List<TReferencedBy> referencedBy,
+            Func<PackedConnection, TReferences> convertReferences,
+            Func<PackedConnection, TReferencedBy> convertReferencedBy
+        ) {
+            GetConnectionsInternal(
+                new PackedConnection.Pair(PackedConnection.Kind.GCHandle, gcHandle.gcHandlesArrayIndex), 
+                references, referencedBy, convertReferences, convertReferencedBy
+            );
+        }
+
+        public void GetConnections(
+            PackedGCHandle gcHandle, List<PackedConnection> references, List<PackedConnection> referencedBy
+        ) => GetConnections(gcHandle, references, referencedBy, _ => _, _ => _);
+
+        public void GetConnections<TReferences, TReferencedBy>(
+            PackedManagedObject managedObject, List<TReferences> references, List<TReferencedBy> referencedBy,
+            Func<PackedConnection, TReferences> convertReferences,
+            Func<PackedConnection, TReferencedBy> convertReferencedBy
+        ) {
+            GetConnectionsInternal(
+                managedObject.managedObjectsArrayIndex.asPair, 
+                references, referencedBy, convertReferences, convertReferencedBy
+            );
+        }
+
+        public void GetConnections(
+            PackedManagedObject managedObject, List<PackedConnection> references, List<PackedConnection> referencedBy
+        ) => GetConnections(managedObject, references, referencedBy, _ => _, _ => _);
+
+        public void GetConnections<TTarget>(
+            PackedMemorySection memorySection, List<TTarget> referencesTo,
+            Func<PackedConnection.Pair, TTarget> convert
+        ) {
             if (memorySection.bytes == null || memorySection.bytes.Length == 0)
                 return;
 
@@ -448,10 +441,14 @@ public void GetConnections(PackedMemorySection memorySection, List<PackedConnect
             {
                 var mo = managedObjects[n];
                 if (mo.address >= startAddress && mo.address < endAddress)
-                    references.Add(new PackedConnection() { toKind = PackedConnection.Kind.Managed, to = n });
+                    referencesTo.Add(convert(new PackedConnection.Pair(PackedConnection.Kind.Managed, n)));
             }
         }
 
+        public void GetConnections(
+            PackedMemorySection memorySection, List<PackedConnection.Pair> referencesTo
+        ) => GetConnections(memorySection, referencesTo, _ => _);
+
         public void GetConnectionsCount(PackedMemorySection memorySection, out int referencesCount)
         {
             referencesCount = 0;
@@ -470,30 +467,28 @@ public void GetConnectionsCount(PackedMemorySection memorySection, out int refer
         }
 
         // TODO: this costs 500ms in wolf4
-        public int FindNativeMonoScriptType(int nativeObjectIndex, out string monoScriptName)
+        public Option<(PInt index, string monoScriptName)> FindNativeMonoScriptType(int nativeObjectIndex)
         {
-            monoScriptName = "";
-
-            var key = ComputeConnectionKey(PackedConnection.Kind.Native, nativeObjectIndex);
+            var key = new PackedConnection.Pair(PackedConnection.Kind.Native, nativeObjectIndex).ComputeConnectionKey();
 
-            List<PackedConnection> list;
-            if (m_ConnectionsFrom.TryGetValue(key, out list))
+            if (!m_ConnectionsFrom.TryGetValue(key, out var list)) return None._;
+            for (int n = 0, nend = list.Count; n < nend; ++n)
             {
-                for (int n = 0, nend = list.Count; n < nend; ++n)
-                {
-                    var connection = list[n];
+                var connection = list[n];
 
-                    // Check if the connection points to a MonoScript
-                    var isPointingToMonoScript = connection.toKind == PackedConnection.Kind.Native && nativeObjects[connection.to].nativeTypesArrayIndex == coreTypes.nativeMonoScript;
-                    if (!isPointingToMonoScript)
-                        continue;
+                // Check if the connection points to a MonoScript
+                var isPointingToMonoScript = 
+                    connection.to.kind == PackedConnection.Kind.Native 
+                    && nativeObjects[connection.to.index].nativeTypesArrayIndex == coreTypes.nativeMonoScript;
+                if (!isPointingToMonoScript)
+                    continue;
 
-                    monoScriptName = nativeObjects[connection.to].name;
-                    return nativeObjects[connection.to].nativeTypesArrayIndex;
-                }
+                var index = nativeObjects[connection.to.index].nativeTypesArrayIndex;
+                var monoScriptName = nativeObjects[connection.to.index].name;
+                return Some((index, monoScriptName));
             }
 
-            return -1;
+            return None._;
         }
 
         public bool IsEnum(PackedManagedType type)
@@ -501,64 +496,72 @@ public bool IsEnum(PackedManagedType type)
             if (!type.isValueType)
                 return false;
 
-            if (type.baseOrElementTypeIndex == -1)
+            if (type.baseOrElementTypeIndex.isNone)
                 return false;
 
-            if (type.baseOrElementTypeIndex != coreTypes.systemEnum)
+            if (type.baseOrElementTypeIndex != Some(coreTypes.systemEnum))
                 return false;
 
             return true;
         }
 
-        public bool IsSubclassOf(PackedNativeType type, int baseTypeIndex)
-        {
-            if (type.nativeTypeArrayIndex == baseTypeIndex)
+        /// <summary>
+        /// Interface for <see cref="PackedMemorySnapshot.IsSubclassOf{T}(HeapExplorer.PackedNativeType,int)"/>.
+        /// </summary>
+        public interface TypeForSubclassSearch 
+        {
+            /// <summary>The type name.</summary>
+            string name { get; }
+            
+            /// <summary>Index of the type in the array.</summary>
+            PInt typeArrayIndex { get; }
+            
+            /// <summary>Index of the base type in the array or `None` if this has no base type.</summary>
+            Option<PInt> baseTypeArrayIndex { get; }            
+        }
+        
+        public bool IsSubclassOf<T>(T type, T[] array, PInt baseTypeIndex) where T : TypeForSubclassSearch
+        {
+            var currentType = type;
+            
+            if (currentType.typeArrayIndex == baseTypeIndex)
                 return true;
 
-            if (baseTypeIndex < 0 || type.nativeTypeArrayIndex < 0)
+            if (baseTypeIndex < 0 || currentType.typeArrayIndex < 0)
                 return false;
 
-            var guard = 0;
-            while (type.nativeBaseTypeArrayIndex != -1)
-            {
-                // safety code in case of an infite loop
-                if (++guard > 64)
-                    break;
+            var cycleTracker = new CycleTracker<PInt>();
+            cycleTracker.markStartOfSearch();
+            {while (currentType.baseTypeArrayIndex.valueOut(out var baseTypeArrayIndex)) {
+                if (cycleTracker.markIteration(baseTypeArrayIndex)) {
+                    cycleTracker.reportCycle(
+                        $"{nameof(IsSubclassOf)}()", baseTypeArrayIndex,
+                        idx => array[idx].ToString()
+                    );
+                    return false;
+                }
 
-                if (type.nativeBaseTypeArrayIndex == baseTypeIndex)
+                if (baseTypeArrayIndex == baseTypeIndex)
                     return true;
 
                 // get type of the base class
-                type = nativeTypes[type.nativeBaseTypeArrayIndex];
-            }
+                currentType = array[baseTypeArrayIndex];
+            }}
 
             return false;
         }
 
-        public bool IsSubclassOf(PackedManagedType type, int baseTypeIndex)
-        {
-            if (type.managedTypesArrayIndex == baseTypeIndex)
-                return true;
-
-            if (type.managedTypesArrayIndex < 0 || baseTypeIndex < 0)
-                return false;
-
-            var guard = 0;
-            while (type.baseOrElementTypeIndex != -1)
-            {
-                // safety code in case of an infite loop
-                if (++guard > 64)
-                    return false;
+        public bool IsSubclassOf(PackedNativeType type, Option<PInt> maybeBaseTypeIndex) => 
+            maybeBaseTypeIndex.valueOut(out var baseTypeIndex) && IsSubclassOf(type, baseTypeIndex);
 
-                if (type.managedTypesArrayIndex == baseTypeIndex)
-                    return true;
+        public bool IsSubclassOf(PackedNativeType type, PInt baseTypeIndex) => 
+            IsSubclassOf(type, nativeTypes, baseTypeIndex);
 
-                // get type of the base class
-                type = managedTypes[type.baseOrElementTypeIndex];
-            }
+        public bool IsSubclassOf(PackedManagedType type, Option<PInt> maybeBaseTypeIndex) => 
+            maybeBaseTypeIndex.valueOut(out var baseTypeIndex) && IsSubclassOf(type, baseTypeIndex);
 
-            return false;
-        }
+        public bool IsSubclassOf(PackedManagedType type, PInt baseTypeIndex) => 
+            IsSubclassOf(type, managedTypes, baseTypeIndex);
 
         #region Serialization
 
@@ -588,7 +591,10 @@ void InitializeConnectionsToMonoScripts()
             for (int n = 0, nend = connections.Length; n < nend; ++n)
             {
                 // Check if the connection points to a MonoScript
-                var isPointingToMonoScript = connections[n].toKind == PackedConnection.Kind.Native && nativeObjects[connections[n].to].nativeTypesArrayIndex == nativeMonoScriptTypeIndex;
+                var isPointingToMonoScript = 
+                    connections[n].to.kind == PackedConnection.Kind.Native 
+                    && nativeObjects[connections[n].to.index].nativeTypesArrayIndex == nativeMonoScriptTypeIndex;
+                
                 if (isPointingToMonoScript)
                     list.Add(n);
             }
@@ -608,7 +614,7 @@ void InitializeConnections()
                 if ((n % (nend / 100)) == 0)
                 {
                     var progress = ((n + 1.0f) / nend) * 100;
-                    busyString = string.Format("Analyzing Object Connections\n{0}/{1}, {2:F0}% done", n + 1, connections.Length, progress);
+                    busyString = $"Analyzing Object Connections\n{n + 1}/{connections.Length}, {progress:F0}% done";
 
                     if (abortActiveStepRequested)
                         break;
@@ -616,7 +622,7 @@ void InitializeConnections()
 
                 var connection = connections[n];
 
-                AddConnection(connection.fromKind, connection.from, connection.toKind, connection.to);
+                AddConnection(connection.from, connection.to);
 
                 //if (connection.fromKind == PackedConnection.Kind.Native || nativeObjects[connection.from].nativeObjectAddress == 0x8E9D4FD0)
                 //    fromCount++;
@@ -644,7 +650,7 @@ void InitializeConnections_OldMemoryProfilingAPI()
                 if ((n % (nend / 100)) == 0)
                 {
                     var progress = ((n + 1.0f) / nend) * 100;
-                    busyString = string.Format("Analyzing Object Connections\n{0}/{1}, {2:F0}% done", n + 1, connections.Length, progress);
+                    busyString = $"Analyzing Object Connections\n{n + 1}/{connections.Length}, {progress:F0}% done";
 
                     if (abortActiveStepRequested)
                         break;
@@ -652,21 +658,19 @@ void InitializeConnections_OldMemoryProfilingAPI()
 
                 var connection = connections[n];
 
-                connection.fromKind = PackedConnection.Kind.GCHandle;
-                if (connection.from >= nativeStart && connection.from < nativeEnd)
-                {
-                    connection.from -= nativeStart;
-                    connection.fromKind = PackedConnection.Kind.Native;
-                }
-
-                connection.toKind = PackedConnection.Kind.GCHandle;
-                if (connection.to >= nativeStart && connection.to < nativeEnd)
-                {
-                    connection.to -= nativeStart;
-                    connection.toKind = PackedConnection.Kind.Native;
-                }
+                // Remap the indexes.
+                var newFrom = new PackedConnection.From(
+                    connection.from.pair.index >= nativeStart && connection.from.pair.index < nativeEnd
+                        ? new PackedConnection.Pair(PackedConnection.Kind.Native, connection.from.pair.index - nativeStart)
+                        : new PackedConnection.Pair(PackedConnection.Kind.GCHandle, connection.from.pair.index),
+                    connection.from.field
+                );
+                var newTo =
+                    connection.to.index >= nativeStart && connection.to.index < nativeEnd
+                        ? new PackedConnection.Pair(PackedConnection.Kind.Native, connection.to.index - nativeStart)
+                        : new PackedConnection.Pair(PackedConnection.Kind.GCHandle, connection.to.index);
 
-                AddConnection(connection.fromKind, connection.from, connection.toKind, connection.to);
+                AddConnection(newFrom, newTo);
 
                 //if (connection.fromKind == PackedConnection.Kind.Native || nativeObjects[connection.from].nativeObjectAddress == 0x8E9D4FD0)
                 //    fromCount++;
@@ -682,10 +686,7 @@ void InitializeManagedHeapSections()
             busyString = "Initializing Managed Heap Sections";
 
             // sort sections by address. This allows us to use binary search algorithms.
-            Array.Sort(managedHeapSections, delegate (PackedMemorySection x, PackedMemorySection y)
-            {
-                return x.startAddress.CompareTo(y.startAddress);
-            });
+            Array.Sort(managedHeapSections, (x, y) => x.startAddress.CompareTo(y.startAddress));
 
             for (var n = 0; n < managedHeapSections.Length; ++n)
                 managedHeapSections[n].arrayIndex = n;
@@ -709,7 +710,7 @@ void InitializeManagedFields()
                 for (int k = 0, kend = type.fields.Length; k < kend; ++k)
                 {
                     var name = type.fields[k].name;
-                    if (name != null && name[0] == '<')
+                    if (!string.IsNullOrEmpty(name) && name[0] == '<')
                     {
                         var index = name.LastIndexOf(">k__BackingField", StringComparison.Ordinal);
                         if (index != -1)
@@ -730,27 +731,31 @@ void InitializeManagedTypes()
             {
                 managedTypes[n].isUnityEngineObject = IsSubclassOf(managedTypes[n], coreTypes.unityEngineObject);
                 managedTypes[n].containsFieldOfReferenceType = ContainsFieldOfReferenceType(managedTypes[n]);
-                managedTypes[n].containsFieldOfReferenceTypeInInheritenceChain = ContainsFieldOfReferenceTypeInInheritenceChain(managedTypes[n]);
+                managedTypes[n].containsFieldOfReferenceTypeInInheritanceChain = ContainsFieldOfReferenceTypeInInheritanceChain(managedTypes[n]);
             }
         }
 
-        bool ContainsFieldOfReferenceTypeInInheritenceChain(PackedManagedType type)
+        bool ContainsFieldOfReferenceTypeInInheritanceChain(PackedManagedType type)
         {
-            var loopGuard = 0;
-            var typeIndex = type.managedTypesArrayIndex;
-            while (typeIndex >= 0 && typeIndex < managedTypes.Length)
-            {
-                if (++loopGuard > 64)
-                {
+            var currentType = type;
+            var cycleTracker = new CycleTracker<int>();
+            var maybeTypeIndex = Some(currentType.managedTypesArrayIndex);
+            cycleTracker.markStartOfSearch();
+            {while (maybeTypeIndex.valueOut(out var typeIndex)) {
+                if (cycleTracker.markIteration(typeIndex)) {
+                    cycleTracker.reportCycle(
+                        $"{nameof(ContainsFieldOfReferenceTypeInInheritanceChain)}({type.name})", typeIndex,
+                        idx => managedTypes[idx].ToString()
+                    );
                     break;
                 }
 
-                type = managedTypes[typeIndex];
-                if (ContainsFieldOfReferenceType(type))
+                currentType = managedTypes[typeIndex];
+                if (ContainsFieldOfReferenceType(currentType))
                     return true;
 
-                typeIndex = type.baseOrElementTypeIndex;
-            }
+                maybeTypeIndex = currentType.baseOrElementTypeIndex;
+            }}
 
             return false;
         }
@@ -780,261 +785,45 @@ bool ContainsFieldOfReferenceType(PackedManagedType type)
         void InitializeCoreTypes()
         {
             busyString = "Initializing Core Types";
-
-            for (int n = 0, nend = managedTypes.Length; n < nend; ++n)
-            {
-                switch (managedTypes[n].name)
-                {
-                    ///////////////////////////////////////////////////////////////
-                    // Primitive types
-                    ///////////////////////////////////////////////////////////////
-
-                    case "System.Enum":
-                        {
-                            coreTypes.systemEnum = n;
-                            break;
-                        }
-
-                    case "System.Byte":
-                        {
-                            coreTypes.systemByte = n;
-                            break;
-                        }
-
-                    case "System.SByte":
-                        {
-                            coreTypes.systemSByte = n;
-                            break;
-                        }
-
-                    case "System.Char":
-                        {
-                            coreTypes.systemChar = n;
-                            break;
-                        }
-
-                    case "System.Boolean":
-                        {
-                            coreTypes.systemBoolean = n;
-                            break;
-                        }
-
-                    case "System.Single":
-                        {
-                            coreTypes.systemSingle = n;
-                            break;
-                        }
-
-                    case "System.Double":
-                        {
-                            coreTypes.systemDouble = n;
-                            break;
-                        }
-
-                    case "System.Decimal":
-                        {
-                            coreTypes.systemDecimal = n;
-                            break;
-                        }
-
-                    case "System.Int16":
-                        {
-                            coreTypes.systemInt16 = n;
-                            break;
-                        }
-
-                    case "System.UInt16":
-                        {
-                            coreTypes.systemUInt16 = n;
-                            break;
-                        }
-
-                    case "System.Int32":
-                        {
-                            coreTypes.systemInt32 = n;
-                            break;
-                        }
-
-                    case "System.UInt32":
-                        {
-                            coreTypes.systemUInt32 = n;
-                            break;
-                        }
-
-                    case "System.Int64":
-                        {
-                            coreTypes.systemInt64 = n;
-                            break;
-                        }
-
-                    case "System.UInt64":
-                        {
-                            coreTypes.systemUInt64 = n;
-                            break;
-                        }
-
-                    case "System.IntPtr":
-                        {
-                            coreTypes.systemIntPtr = n;
-                            break;
-                        }
-
-                    case "System.UIntPtr":
-                        {
-                            coreTypes.systemUIntPtr = n;
-                            break;
-                        }
-
-                    case "System.String":
-                        {
-                            coreTypes.systemString = n;
-                            break;
-                        }
-
-                    case "System.ValueType":
-                        {
-                            coreTypes.systemValueType = n;
-                            break;
-                        }
-
-                    case "System.ReferenceType":
-                        {
-                            coreTypes.systemReferenceType = n;
-                            break;
-                        }
-
-                    case "System.Object":
-                        {
-                            coreTypes.systemObject = n;
-                            break;
-                        }
-
-                    case "System.Delegate":
-                        {
-                            coreTypes.systemDelegate = n;
-                            break;
-                        }
-
-                    case "System.MulticastDelegate":
-                        {
-                            coreTypes.systemMulticastDelegate = n;
-                            break;
-                        }
-
-                    ///////////////////////////////////////////////////////////////
-                    // UnityEngine types
-                    ///////////////////////////////////////////////////////////////
-                    case "UnityEngine.Object":
-                        {
-                            coreTypes.unityEngineObject = n;
-                            break;
-                        }
-
-                    case "UnityEngine.GameObject":
-                        {
-                            coreTypes.unityEngineGameObject = n;
-                            break;
-                        }
-
-                    case "UnityEngine.Transform":
-                        {
-                            coreTypes.unityEngineTransform = n;
-                            break;
-                        }
-
-                    case "UnityEngine.RectTransform":
-                        {
-                            coreTypes.unityEngineRectTransform = n;
-                            break;
-                        }
-
-                    case "UnityEngine.MonoBehaviour":
-                        {
-                            coreTypes.unityEngineMonoBehaviour = n;
-                            break;
-                        }
-
-                    case "UnityEngine.Component":
-                        {
-                            coreTypes.unityEngineComponent = n;
-                            break;
-                        }
-
-                    case "UnityEngine.ScriptableObject":
-                        {
-                            coreTypes.unityEngineScriptableObject = n;
-                            break;
-                        }
-
-                    case "UnityEngine.AssetBundle":
-                        {
-                            coreTypes.unityEngineScriptableObject = n;
-                            break;
-                        }
-                }
-            }
-
-            for (int n = 0, nend = nativeTypes.Length; n < nend; ++n)
-            {
-                switch (nativeTypes[n].name)
-                {
-                    case "Object": coreTypes.nativeObject = n; break;
-                    case "GameObject": coreTypes.nativeGameObject = n; break;
-                    case "MonoBehaviour": coreTypes.nativeMonoBehaviour = n; break;
-                    case "ScriptableObject": coreTypes.nativeScriptableObject = n; break;
-                    case "Transform": coreTypes.nativeTransform = n; break;
-                    case "MonoScript": coreTypes.nativeMonoScript = n; break;
-                    case "Component": coreTypes.nativeComponent = n; break;
-                    case "AssetBundle": coreTypes.nativeAssetBundle = n; break;
-                    case "Texture2D": coreTypes.nativeTexture2D = n; break;
-                    case "Texture3D": coreTypes.nativeTexture3D = n; break;
-                    case "TextureArray": coreTypes.nativeTextureArray = n; break;
-                    case "AudioClip": coreTypes.nativeAudioClip = n; break;
-                    case "AnimationClip": coreTypes.nativeAnimationClip = n; break;
-                    case "Mesh": coreTypes.nativeMesh = n; break;
-                    case "Material": coreTypes.nativeMaterial = n; break;
-                    case "Sprite": coreTypes.nativeSprite = n; break;
-                    case "Shader": coreTypes.nativeShader = n; break;
-                    case "AnimatorController": coreTypes.nativeAnimatorController = n; break;
-                    case "Cubemap": coreTypes.nativeCubemap = n; break;
-                    case "CubemapArray": coreTypes.nativeCubemapArray = n; break;
-                    case "Font": coreTypes.nativeFont = n; break;
-                }
+            if (PackedCoreTypes.tryInitialize(managedTypes, nativeTypes, out var coreTypes).valueOut(out var errors)) {
+                var errorsStr = string.Join("\n", errors);
+                throw new Exception($"Can't initialize core types, this should never happen! Errors:\n{errorsStr}");
             }
+            _coreTypes = Some(coreTypes);
         }
 
         void InitializeNativeTypes()
         {
             busyString = "Processing Native Types";
 
-            for (int n = 0, nend = nativeObjects.Length; n < nend; ++n)
+            for (PInt n = PInt._0, nend = nativeObjects.LengthP(); n < nend; ++n)
             {
                 var nativeTypesArrayIndex = nativeObjects[n].nativeTypesArrayIndex;
                 if (nativeTypesArrayIndex < 0)
                     continue;
 
-                nativeTypes[nativeTypesArrayIndex].totalObjectCount += 1;
+                nativeTypes[nativeTypesArrayIndex].totalObjectCount += PInt._1;
                 nativeTypes[nativeTypesArrayIndex].totalObjectSize += nativeObjects[n].size;
             }
 
             busyString = "Processing Managed Types";
 
-            for (int n = 0, nend = managedObjects.Length; n < nend; ++n)
+            for (PInt n = PInt._0, nend = managedObjects.LengthP(); n < nend; ++n)
             {
                 var typeArrayIndex = managedObjects[n].managedTypesArrayIndex;
                 if (typeArrayIndex < 0)
                     continue;
 
-                managedTypes[typeArrayIndex].totalObjectCount += 1;
-                managedTypes[typeArrayIndex].totalObjectSize += managedObjects[n].size;
+                managedTypes[typeArrayIndex].totalObjectCount += PInt._1;
+                managedTypes[typeArrayIndex].totalObjectSize += managedObjects[n].size.getOrElse(0);
             }
         }
 
-        List<ulong> InitializeManagedObjectSubstitudes(string snapshotPath)
+        List<ulong> InitializeManagedObjectSubstitutes(string snapshotPath)
         {
-            busyString = "Substitude ManagedObjects";
+            busyString = "Substitute ManagedObjects";
 
-            var substitudeManagedObjects = new List<ulong>();
+            var substituteManagedObjects = new List<ulong>();
             if (!string.IsNullOrEmpty(snapshotPath) && System.IO.File.Exists(snapshotPath))
             {
                 var p = snapshotPath + ".ManagedObjects.txt";
@@ -1053,7 +842,7 @@ List<ulong> InitializeManagedObjectSubstitudes(string snapshotPath)
                             ulong value;
                             ulong.TryParse(line, System.Globalization.NumberStyles.HexNumber, null, out value);
                             if (value != 0)
-                                substitudeManagedObjects.Add(value);
+                                substituteManagedObjects.Add(value);
                             else
                                 Error("Could not parse '{0}' as hex number.", line);
                         }
@@ -1062,7 +851,7 @@ List<ulong> InitializeManagedObjectSubstitudes(string snapshotPath)
                             ulong value;
                             ulong.TryParse(line, System.Globalization.NumberStyles.Number, null, out value);
                             if (value != 0)
-                                substitudeManagedObjects.Add(value);
+                                substituteManagedObjects.Add(value);
                             else
                                 Error("Could not parse '{0}' as integer number. If this is a Hex number, prefix it with '0x'.", line);
                         }
@@ -1070,7 +859,7 @@ List<ulong> InitializeManagedObjectSubstitudes(string snapshotPath)
                 }
             }
 
-            return substitudeManagedObjects;
+            return substituteManagedObjects;
         }
 
         public void Initialize(string snapshotPath = null)
@@ -1112,14 +901,14 @@ public void Initialize(string snapshotPath = null)
                 abortActiveStepRequested = false;
                 EndProfilerSample();
 
-                BeginProfilerSample("substitudeManagedObjects");
-                var substitudeManagedObjects = InitializeManagedObjectSubstitudes(snapshotPath);
+                BeginProfilerSample("substituteManagedObjects");
+                var substituteManagedObjects = InitializeManagedObjectSubstitutes(snapshotPath);
                 EndProfilerSample();
 
                 BeginProfilerSample("InitializeManagedObjects");
                 busyString = "Analyzing ManagedObjects";
                 var crawler = new PackedManagedObjectCrawler();
-                crawler.Crawl(this, substitudeManagedObjects);
+                crawler.Crawl(this, substituteManagedObjects);
                 abortActiveStepRequested = false;
                 EndProfilerSample();
 
@@ -1127,9 +916,9 @@ public void Initialize(string snapshotPath = null)
                 abortActiveStepRequested = false;
 
                 busyString = "Finalizing";
-                System.Threading.Thread.Sleep(30);
+                Thread.Sleep(30);
             }
-            catch (System.Exception e)
+            catch (Exception e)
             {
                 Error(e.ToString());
                 throw;
diff --git a/Editor/Scripts/PackedTypes/PackedNativeType.cs b/Editor/Scripts/PackedTypes/PackedNativeType.cs
index 3c3a134..0288dc0 100644
--- a/Editor/Scripts/PackedTypes/PackedNativeType.cs
+++ b/Editor/Scripts/PackedTypes/PackedNativeType.cs
@@ -2,47 +2,49 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     [Serializable]
     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential, Pack = 1)]
-    public struct PackedNativeType
+    public struct PackedNativeType : PackedMemorySnapshot.TypeForSubclassSearch
     {
-        // The index used to obtain the native C++ base class description from the PackedMemorySnapshot.nativeTypes array.
-        public System.Int32 nativeBaseTypeArrayIndex;
+        /// <summary>
+        /// The index used to obtain the native C++ base class description from the
+        /// <see cref="PackedMemorySnapshot.nativeTypes"/> array or `None` if this has no base type.
+        /// </summary>
+        public Option<PInt> nativeBaseTypeArrayIndex;
 
         [NonSerialized]
-        public System.Int32 nativeTypeArrayIndex;
+        public PInt nativeTypeArrayIndex;
 
         [NonSerialized]
-        public System.Int32 managedTypeArrayIndex;
+        public Option<PInt> managedTypeArrayIndex;
 
         // Number of all objects of this type.
         [NonSerialized]
-        public System.Int32 totalObjectCount;
+        public PInt totalObjectCount;
 
         // The size of all objects of this type.
         [NonSerialized]
-        public System.Int64 totalObjectSize;
+        public ulong totalObjectSize;
 
         // Name of this C++ unity type.
-        public System.String name;
+        public string name;
 
-        const System.Int32 k_Version = 1;
+        /// <inheritdoc/>
+        string PackedMemorySnapshot.TypeForSubclassSearch.name => name;
 
-        public static readonly PackedNativeType invalid = new PackedNativeType()
-        {
-            name = "<invalid>",
-            nativeBaseTypeArrayIndex = -1,
-            nativeTypeArrayIndex = -1,
-            managedTypeArrayIndex = -1
-        };
+        /// <inheritdoc/>
+        PInt PackedMemorySnapshot.TypeForSubclassSearch.typeArrayIndex => nativeTypeArrayIndex;
+
+        /// <inheritdoc/>
+        Option<PInt> PackedMemorySnapshot.TypeForSubclassSearch.baseTypeArrayIndex => nativeBaseTypeArrayIndex;
+
+        const System.Int32 k_Version = 1;
 
         /// <summary>
         /// Writes a PackedNativeType array to the specified writer.
@@ -55,7 +57,7 @@ public static void Write(System.IO.BinaryWriter writer, PackedNativeType[] value
             for (int n = 0, nend = value.Length; n < nend; ++n)
             {
                 writer.Write(value[n].name);
-                writer.Write(value[n].nativeBaseTypeArrayIndex);
+                writer.Write(value[n].nativeBaseTypeArrayIndex.fold(-1, _ => _));
             }
         }
 
@@ -71,22 +73,25 @@ public static void Read(System.IO.BinaryReader reader, out PackedNativeType[] va
             if (version >= 1)
             {
                 var length = reader.ReadInt32();
-                stateString = string.Format("Loading {0} Native Types", length);
+                stateString = $"Loading {length} Native Types";
 
                 value = new PackedNativeType[length];
 
                 for (int n = 0, nend = value.Length; n < nend; ++n)
                 {
                     value[n].name = reader.ReadString();
-                    value[n].nativeBaseTypeArrayIndex = reader.ReadInt32();
-                    value[n].nativeTypeArrayIndex = n;
-                    value[n].managedTypeArrayIndex = -1;
+                    var nativeBaseTypeArrayIndex = reader.ReadInt32();
+                    value[n].nativeBaseTypeArrayIndex = 
+                        nativeBaseTypeArrayIndex == -1 ? None._ : Some(PInt.createOrThrow(nativeBaseTypeArrayIndex));
+                    value[n].nativeTypeArrayIndex = PInt.createOrThrow(n);
+                    value[n].managedTypeArrayIndex = None._;
                 }
             }
         }
 
-        public static PackedNativeType[] FromMemoryProfiler(UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot)
-        {
+        public static PackedNativeType[] FromMemoryProfiler(
+            UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
+        ) {
             var source = snapshot.nativeTypes;
             var value = new PackedNativeType[source.GetNumEntries()];
 
@@ -94,18 +99,21 @@ public static PackedNativeType[] FromMemoryProfiler(UnityEditor.Profiling.Memory
             source.typeName.GetEntries(0, source.typeName.GetNumEntries(), ref sourceTypeName);
 
             var sourceNativeBaseTypeArrayIndex = new int[source.nativeBaseTypeArrayIndex.GetNumEntries()];
-            source.nativeBaseTypeArrayIndex.GetEntries(0, source.nativeBaseTypeArrayIndex.GetNumEntries(), ref sourceNativeBaseTypeArrayIndex);
+            source.nativeBaseTypeArrayIndex.GetEntries(
+                0, source.nativeBaseTypeArrayIndex.GetNumEntries(), ref sourceNativeBaseTypeArrayIndex
+            );
 
-            for (int n = 0, nend = value.Length; n < nend; ++n)
-            {
+            for (int n = 0, nend = value.Length; n < nend; ++n) {
+                var nativeBaseTypeArrayIndex = sourceNativeBaseTypeArrayIndex[n];
                 value[n] = new PackedNativeType
                 {
                     name = sourceTypeName[n],
-                    nativeBaseTypeArrayIndex = sourceNativeBaseTypeArrayIndex[n],
-                    nativeTypeArrayIndex = n,
-                    managedTypeArrayIndex = -1,
+                    nativeBaseTypeArrayIndex = 
+                        nativeBaseTypeArrayIndex == -1 ? None._ : Some(PInt.createOrThrow(nativeBaseTypeArrayIndex)),
+                    nativeTypeArrayIndex = PInt.createOrThrow(n),
+                    managedTypeArrayIndex = None._,
                 };
-            };
+            }
 
             return value;
         }
diff --git a/Editor/Scripts/PackedTypes/PackedNativeUnityEngineObject.cs b/Editor/Scripts/PackedTypes/PackedNativeUnityEngineObject.cs
index 8978f69..fcf1a39 100644
--- a/Editor/Scripts/PackedTypes/PackedNativeUnityEngineObject.cs
+++ b/Editor/Scripts/PackedTypes/PackedNativeUnityEngineObject.cs
@@ -2,11 +2,9 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
 using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
 using UnityEditor.Profiling.Memory.Experimental;
 
 namespace HeapExplorer
@@ -16,29 +14,36 @@ namespace HeapExplorer
     public struct PackedNativeUnityEngineObject
     {
         // The memory address of the native C++ object. This matches the "m_CachedPtr" field of UnityEngine.Object.
-        public System.Int64 nativeObjectAddress;
+        public ulong nativeObjectAddress;
 
         // InstanceId of this object.
         public System.Int32 instanceId;
 
         // Size in bytes of this object.
-        public System.Int32 size;
+        public ulong size;
 
         // The index used to obtain the native C++ type description from the PackedMemorySnapshot.nativeTypes array.
-        public System.Int32 nativeTypesArrayIndex;
+        public PInt nativeTypesArrayIndex;
 
-        // The index of this element in the PackedMemorySnapshot.nativeObjects array.
+        /// <summary>
+        /// The index of this element in the <see cref="PackedMemorySnapshot.nativeObjects"/> array.
+        /// </summary>
         [NonSerialized]
-        public System.Int32 nativeObjectsArrayIndex;
+        public PInt nativeObjectsArrayIndex;
 
-        // The index of the C# counter-part in the PackedMemorySnapshot.managedObjects array or -1 if no C# object exists.
+        /// <summary>
+        /// The index of the C# counter-part in the <see cref="PackedMemorySnapshot.managedObjects"/> array or `None`
+        /// if no C# object exists.
+        /// </summary>
         [NonSerialized]
-        public System.Int32 managedObjectsArrayIndex;
+        public Option<PackedManagedObject.ArrayIndex> managedObjectsArrayIndex;
 
         // The hideFlags this native object has.
         public HideFlags hideFlags;
 
-        // Name of this object.
+        /// <summary>
+        /// Name of this object.
+        /// </summary>
         public System.String name;
 
         // Is this object persistent? (Assets are persistent, objects stored in scenes are persistent, dynamically created objects are not)
@@ -50,7 +55,7 @@ public struct PackedNativeUnityEngineObject
         // Is this native object an internal Unity manager object?
         public System.Boolean isManager;
 
-        const System.Int32 k_Version = 1;
+        const System.Int32 k_Version = 2;
 
         public static void Write(System.IO.BinaryWriter writer, PackedNativeUnityEngineObject[] value)
         {
@@ -80,7 +85,7 @@ public static void Read(System.IO.BinaryReader reader, out PackedNativeUnityEngi
             if (version >= 1)
             {
                 var length = reader.ReadInt32();
-                stateString = string.Format("Loading {0} Native Objects", length);
+                stateString = $"Loading {length} Native Objects";
                 value = new PackedNativeUnityEngineObject[length];
 
                 for (int n = 0, nend = value.Length; n < nend; ++n)
@@ -90,13 +95,11 @@ public static void Read(System.IO.BinaryReader reader, out PackedNativeUnityEngi
                     value[n].isManager = reader.ReadBoolean();
                     value[n].name = reader.ReadString();
                     value[n].instanceId = reader.ReadInt32();
-                    value[n].size = reader.ReadInt32();
-                    value[n].nativeTypesArrayIndex = reader.ReadInt32();
+                    value[n].size = version >= 2 ? reader.ReadUInt64() : reader.ReadPInt();
+                    value[n].nativeTypesArrayIndex = reader.ReadPInt();
                     value[n].hideFlags = (HideFlags)reader.ReadInt32();
-                    value[n].nativeObjectAddress = reader.ReadInt64();
-
-                    value[n].nativeObjectsArrayIndex = n;
-                    value[n].managedObjectsArrayIndex = -1;
+                    value[n].nativeObjectAddress = version >= 2 ? reader.ReadUInt64() : reader.ReadInt64().ToULongClamped();
+                    value[n].nativeObjectsArrayIndex = PInt.createOrThrow(n);
                 }
             }
         }
@@ -127,8 +130,10 @@ public static PackedNativeUnityEngineObject[] FromMemoryProfiler(UnityEditor.Pro
             var sourceNativeObjectAddress = new ulong[source.nativeObjectAddress.GetNumEntries()];
             source.nativeObjectAddress.GetEntries(0, source.nativeObjectAddress.GetNumEntries(), ref sourceNativeObjectAddress);
 
-            for (int n = 0, nend = value.Length; n < nend; ++n)
+            for (int n = 0, nend = value.Length; n < nend; ++n) 
             {
+                var nativeObjectAddress = sourceNativeObjectAddress[n];
+                var nativeTypesArrayIndex = sourceNativeTypeArrayIndex[n];
                 value[n] = new PackedNativeUnityEngineObject
                 {
                     isPersistent = (sourceFlags[n] & ObjectFlags.IsPersistent) != 0,
@@ -136,14 +141,22 @@ public static PackedNativeUnityEngineObject[] FromMemoryProfiler(UnityEditor.Pro
                     isManager = (sourceFlags[n] & ObjectFlags.IsManager) != 0,
                     name = sourceObjectNames[n],
                     instanceId = sourceInstanceIds[n],
-                    size = (int)sourceSizes[n], // TODO: should be ulong
-                    nativeTypesArrayIndex = sourceNativeTypeArrayIndex[n],
+                    size = sourceSizes[n],
+                    nativeTypesArrayIndex = PInt.createOrThrow(nativeTypesArrayIndex),
                     hideFlags = sourceHideFlags[n],
-                    nativeObjectAddress = (long)sourceNativeObjectAddress[n], // TODO: should be ulong
+                    nativeObjectAddress = nativeObjectAddress,
 
-                    nativeObjectsArrayIndex = n,
-                    managedObjectsArrayIndex = -1,
+                    nativeObjectsArrayIndex = PInt.createOrThrow(n),
                 };
+
+                if (nativeTypesArrayIndex < 0) 
+                {
+                    Debug.LogWarningFormat(
+                        "HeapExplorer: native object at {0:X} does not have an associated native type "
+                        + "(nativeTypesArrayIndex={1}). This should not happen.",
+                        nativeObjectAddress, nativeTypesArrayIndex
+                    );
+                }
             }
 
             return value;
diff --git a/Editor/Scripts/PackedTypes/PackedVirtualMachineInformation.cs b/Editor/Scripts/PackedTypes/PackedVirtualMachineInformation.cs
index d4db232..d5a756d 100644
--- a/Editor/Scripts/PackedTypes/PackedVirtualMachineInformation.cs
+++ b/Editor/Scripts/PackedTypes/PackedVirtualMachineInformation.cs
@@ -2,37 +2,71 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
 using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
+    /// <summary>Pointer size in bytes. We only support 32 and 64 bit architectures.</summary>
+    public enum PointerSize : byte { _32Bit = 4, _64Bit = 8 }
+
+    public static class PointerSize_ {
+        /// <summary>Converts the <see cref="PointerSize"/> from the byte count returning `None` on unsupported values.</summary>
+        public static Option<PointerSize> fromByteCount(int value) =>
+            value == 4 ? Some(PointerSize._32Bit)
+            : value == 8 ? Some(PointerSize._64Bit)
+            : None._;
+    }
+    
+    public static class PointerSizeExts {
+        /// <summary>Converts the <see cref="PointerSize"/> to byte count.</summary>
+        public static byte sizeInBytes(this PointerSize s) => (byte) s;
+        
+        /// <summary>
+        /// Reads a pointer value at <see cref="offset"/> from the <see cref="array"/> based on the pointer size.
+        /// </summary>
+        public static ulong readPointer(this PointerSize s, byte[] array, int offset) =>
+            s == PointerSize._64Bit
+                ? BitConverter.ToUInt64(array, offset)
+                : BitConverter.ToUInt32(array, offset);
+    }
+    
     // Information about a virtual machine that provided a memory snapshot.
     [Serializable]
     public struct PackedVirtualMachineInformation
     {
-        // Size in bytes of a pointer.
-        public System.Int32 pointerSize;
+        /// <inheritdoc cref="PointerSize"/>
+        public PointerSize pointerSize;
 
-        // Size in bytes of the header of each managed object.
-        public System.Int32 objectHeaderSize;
+        /// <summary>
+        /// Size in bytes of the header of each managed object.
+        /// </summary>
+        public PInt objectHeaderSize;
 
-        // Size in bytes of the header of an array object.
-        public System.Int32 arrayHeaderSize;
+        /// <summary>
+        /// Size in bytes of the header of an array object.
+        /// </summary>
+        public PInt arrayHeaderSize;
 
-        // Offset in bytes inside the object header of an array object where the bounds of the array is stored.
-        public System.Int32 arrayBoundsOffsetInHeader;
+        /// <summary>
+        /// Offset in bytes inside the object header of an array object where the bounds of the array is stored.
+        /// </summary>
+        public PInt arrayBoundsOffsetInHeader;
 
-        // Offset in bytes inside the object header of an array object where the size of the array is stored.
-        public System.Int32 arraySizeOffsetInHeader;
+        /// <summary>
+        /// Offset in bytes inside the object header of an array object where the size of the array is stored.
+        /// </summary>
+        public PInt arraySizeOffsetInHeader;
 
-        // Allocation granularity in bytes used by the virtual machine allocator.
-        public System.Int32 allocationGranularity;
+        /// <summary>
+        /// Allocation granularity in bytes used by the virtual machine allocator.
+        /// </summary>
+        public PInt allocationGranularity;
 
-        // A version number that will change when the object layout inside the managed heap will change.
+        /// <summary>
+        /// A version number that will change when the object layout inside the managed heap will change.
+        /// </summary>
         public System.Int32 heapFormatVersion;
 
         const System.Int32 k_Version = 1;
@@ -40,7 +74,8 @@ public struct PackedVirtualMachineInformation
         public static void Write(System.IO.BinaryWriter writer, PackedVirtualMachineInformation value)
         {
             writer.Write(k_Version);
-            writer.Write(value.pointerSize);
+            // Convert to `int` for backwards compatibility with older snapshot versions.
+            writer.Write((int) value.pointerSize.sizeInBytes());
             writer.Write(value.objectHeaderSize);
             writer.Write(value.arrayHeaderSize);
             writer.Write(value.arrayBoundsOffsetInHeader);
@@ -55,14 +90,17 @@ public static void Read(System.IO.BinaryReader reader, out PackedVirtualMachineI
             stateString = "Loading VM Information";
 
             var version = reader.ReadInt32();
-            if (version >= 1)
-            {
-                value.pointerSize = reader.ReadInt32();
-                value.objectHeaderSize = reader.ReadInt32();
-                value.arrayHeaderSize = reader.ReadInt32();
-                value.arrayBoundsOffsetInHeader = reader.ReadInt32();
-                value.arraySizeOffsetInHeader = reader.ReadInt32();
-                value.allocationGranularity = reader.ReadInt32();
+            if (version >= 1) {
+                var rawPointerSize = reader.ReadInt32();
+                if (!PointerSize_.fromByteCount(rawPointerSize).valueOut(out var pointerSize)) {
+                    throw new Exception($"unsupported pointer size: {rawPointerSize}");
+                }
+                value.pointerSize = pointerSize;
+                value.objectHeaderSize = PInt.createOrThrow(reader.ReadInt32());
+                value.arrayHeaderSize = PInt.createOrThrow(reader.ReadInt32());
+                value.arrayBoundsOffsetInHeader = PInt.createOrThrow(reader.ReadInt32());
+                value.arraySizeOffsetInHeader = PInt.createOrThrow(reader.ReadInt32());
+                value.allocationGranularity = PInt.createOrThrow(reader.ReadInt32());
                 value.heapFormatVersion = reader.ReadInt32();
             }
         }
@@ -71,14 +109,17 @@ public static PackedVirtualMachineInformation FromMemoryProfiler(UnityEditor.Pro
         {
             var source = snapshot.virtualMachineInformation;
 
+            if (!PointerSize_.fromByteCount(source.pointerSize).valueOut(out var pointerSize)) {
+                throw new Exception($"unsupported pointer size: {source.pointerSize}");
+            }
             var value = new PackedVirtualMachineInformation
             {
-                pointerSize = source.pointerSize,
-                objectHeaderSize = source.objectHeaderSize,
-                arrayHeaderSize = source.arrayHeaderSize,
-                arrayBoundsOffsetInHeader = source.arrayBoundsOffsetInHeader,
-                arraySizeOffsetInHeader = source.arraySizeOffsetInHeader,
-                allocationGranularity = source.allocationGranularity,
+                pointerSize = pointerSize,
+                objectHeaderSize = PInt.createOrThrow(source.objectHeaderSize),
+                arrayHeaderSize = PInt.createOrThrow(source.arrayHeaderSize),
+                arrayBoundsOffsetInHeader = PInt.createOrThrow(source.arrayBoundsOffsetInHeader),
+                arraySizeOffsetInHeader = PInt.createOrThrow(source.arraySizeOffsetInHeader),
+                allocationGranularity = PInt.createOrThrow(source.allocationGranularity),
                 heapFormatVersion = 2019,
             };
             return value;
diff --git a/Editor/Scripts/PropertyGrid/ArrayElementPropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ArrayElementPropertyGridItem.cs
index a911684..7141df0 100644
--- a/Editor/Scripts/PropertyGrid/ArrayElementPropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ArrayElementPropertyGridItem.cs
@@ -21,15 +21,15 @@ protected override void OnInitialize()
         {
             displayType = type.name;
             displayName = "element";
-            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type);
+            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type).getOrElse("<cannot read>");
             isExpandable = address > 0;
             icon = HeEditorStyles.GetTypeImage(m_Snapshot, type);
 
             if (type.isPointer)
             {
                 var pointer = m_MemoryReader.ReadPointer(address);
-                isExpandable = pointer > 0;
-                enabled = pointer > 0;
+                isExpandable = pointer.contains(_ => _ > 0);
+                enabled = pointer.contains(_ => _ > 0);
             }
         }
 
diff --git a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
index 6e1fae4..8e11635 100644
--- a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
@@ -21,9 +21,9 @@ public ArrayPropertyGridItem(PropertyGridControl owner, PackedMemorySnapshot sna
 
         protected override void OnInitialize()
         {
-            var pointer = m_MemoryReader.ReadPointer(address);
-            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex];
-            var dim0Length = address > 0 ? m_MemoryReader.ReadArrayLength(address, type, 0) : 0;
+            var pointer = m_MemoryReader.ReadPointer(address).getOrThrow();
+            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
+            var dim0Length = address > 0 ? m_MemoryReader.ReadArrayLength(address, type, 0).getOrThrow() : 0;
 
             displayType = type.name;
             displayValue = "null";
@@ -41,7 +41,7 @@ protected override void OnInitialize()
                     for (var n = 0; n < type.arrayRank; ++n)
                     {
                         var length = m_MemoryReader.ReadArrayLength(address, type, n);
-                        displayValue += string.Format("[{0}]", length);
+                        displayValue += $"[{length}]";
                     }
                 }
                 else
@@ -51,7 +51,7 @@ protected override void OnInitialize()
                     {
                         var length = m_MemoryReader.ReadArrayLength(address, type, n);
 
-                        displayValue += string.Format("{0}", length);
+                        displayValue += $"{length}";
                         if (n + 1 < type.arrayRank)
                             displayValue += ",";
                     }
@@ -71,8 +71,8 @@ protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add)
 
         void BuildOneDimArray(System.Action<BuildChildrenArgs> add)
         {
-            var arrayLength = m_MemoryReader.ReadArrayLength(address, type);
-            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex];
+            var arrayLength = m_MemoryReader.ReadArrayLength(address, type).getOrThrow();
+            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
 
             for (var n = 0; n < Mathf.Min(arrayLength, k_MaxItemsPerChunk); ++n)
             {
@@ -83,14 +83,14 @@ void BuildOneDimArray(System.Action<BuildChildrenArgs> add)
             {
                 var child = children[n] as PropertyGridItem;
                 if (child != null)
-                    child.displayName = string.Format("[{0}]", n);
+                    child.displayName = $"[{n}]";
             }
         }
 
         void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
         {
-            var arrayLength = m_MemoryReader.ReadArrayLength(address, type);
-            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex];
+            var arrayLength = m_MemoryReader.ReadArrayLength(address, type).getOrThrow();
+            var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
 
             for (var n = 0; n < Mathf.Min(arrayLength, k_MaxItemsPerChunk); ++n)
             {
@@ -100,7 +100,7 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
             // an understandable way to name elements of an two dimensional array
             if (type.arrayRank == 2)
             {
-                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1);
+                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1).getOrThrow();
 
                 var x = 0;
                 var y = 0;
@@ -109,7 +109,7 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
                 {
                     var child = children[n] as PropertyGridItem;
                     if (child != null)
-                        child.displayName = string.Format("[{0},{1}]", y, x);
+                        child.displayName = $"[{y},{x}]";
 
                     x++;
                     if (x >= arrayLength2)
@@ -123,8 +123,8 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
             // complicated way of naming elements of three and more dimensional arrays
             if (type.arrayRank == 3)
             {
-                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1);
-                var arrayLength3 = m_MemoryReader.ReadArrayLength(address, type, 2);
+                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1).getOrThrow();
+                var arrayLength3 = m_MemoryReader.ReadArrayLength(address, type, 2).getOrThrow();
 
                 var x = 0;
                 var y = 0;
@@ -134,7 +134,7 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
                 {
                     var child = children[n] as PropertyGridItem;
                     if (child != null)
-                        child.displayName = string.Format("[{0},{1},{2}]", z, y, x);
+                        child.displayName = $"[{z},{y},{x}]";
 
                     x++;
                     if (x >= arrayLength2)
@@ -155,7 +155,11 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
         {
             if (elementType.isArray)
             {
-                var pointer = m_MemoryReader.ReadPointer(address + (ulong)(elementIndex * m_Snapshot.virtualMachineInformation.pointerSize) + (ulong)m_Snapshot.virtualMachineInformation.arrayHeaderSize);
+                var pointer = m_MemoryReader.ReadPointer(
+                    address 
+                    + (ulong)(elementIndex * m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes()) 
+                    + m_Snapshot.virtualMachineInformation.arrayHeaderSize
+                ).getOrThrow();
                 var item = new ArrayPropertyGridItem(m_Owner, m_Snapshot, pointer, m_MemoryReader)
                 {
                     depth = this.depth + 1,
@@ -168,11 +172,15 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
             {
                 if (elementType.isPrimitive)
                 {
-                    var args = new BuildChildrenArgs();
-                    args.parent = this;
-                    args.type = elementType;
-                    args.address = address + (ulong)(elementIndex * elementType.size) + (ulong)m_Snapshot.virtualMachineInformation.arrayHeaderSize - (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize;
-                    args.memoryReader = new MemoryReader(m_Snapshot);
+                    var args = new BuildChildrenArgs {
+                        parent = this,
+                        type = elementType,
+                        address = address 
+                                  + (ulong)(elementIndex * elementType.size) 
+                                  + m_Snapshot.virtualMachineInformation.arrayHeaderSize 
+                                  - m_Snapshot.virtualMachineInformation.objectHeaderSize,
+                        memoryReader = new MemoryReader(m_Snapshot)
+                    };
                     add(args);
                 }
                 else
@@ -180,7 +188,9 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
                     // this is the container node for the array elements.
                     // if we don't add the container, all fields are simply added to the array node itself.
                     // however, we want each array element being groupped
-                    var pointer = address + (ulong)(elementIndex * elementType.size) + (ulong)m_Snapshot.virtualMachineInformation.arrayHeaderSize;
+                    var pointer = address 
+                                  + (ulong)(elementIndex * elementType.size) 
+                                  + m_Snapshot.virtualMachineInformation.arrayHeaderSize;
 
                     var item = new ArrayElementPropertyGridItem(m_Owner, m_Snapshot, pointer, new MemoryReader(m_Snapshot))
                     {
@@ -190,7 +200,11 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
                     item.Initialize();
                     this.AddChild(item);
 
-                    pointer = address + (ulong)(elementIndex * elementType.size) + (ulong)m_Snapshot.virtualMachineInformation.arrayHeaderSize - (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize;
+                    pointer = 
+                        address 
+                        + (ulong)(elementIndex * elementType.size) 
+                        + m_Snapshot.virtualMachineInformation.arrayHeaderSize 
+                        - m_Snapshot.virtualMachineInformation.objectHeaderSize;
 
                     var args = new BuildChildrenArgs();
                     args.parent = item;
@@ -203,12 +217,14 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
             else
             {
                 // address of element
-                var addressOfElement = address + (ulong)(elementIndex * m_Snapshot.virtualMachineInformation.pointerSize) + (ulong)m_Snapshot.virtualMachineInformation.arrayHeaderSize;
-                var pointer = m_MemoryReader.ReadPointer(addressOfElement);
+                var addressOfElement = 
+                    address 
+                    + (ulong)(elementIndex * m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes()) 
+                    + m_Snapshot.virtualMachineInformation.arrayHeaderSize;
+                var pointer = m_MemoryReader.ReadPointer(addressOfElement).getOrThrow();
                 if (pointer != 0)
                 {
-                    var i = m_Snapshot.FindManagedObjectTypeOfAddress(pointer);
-                    if (i != -1)
+                    if (m_Snapshot.FindManagedObjectTypeOfAddress(pointer).valueOut(out var i))
                         elementType = m_Snapshot.managedTypes[i];
                 }
 
diff --git a/Editor/Scripts/PropertyGrid/PrimitiveTypePropertyGridItem.cs b/Editor/Scripts/PropertyGrid/PrimitiveTypePropertyGridItem.cs
index 954b0da..2380750 100644
--- a/Editor/Scripts/PropertyGrid/PrimitiveTypePropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/PrimitiveTypePropertyGridItem.cs
@@ -29,12 +29,12 @@ protected override void OnInitialize()
                 displayType = "static " + displayType;
 
             displayName = field.name;
-            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type);
+            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type).getOrElse("<reading error>");
             isExpandable = false;
             icon = HeEditorStyles.GetTypeImage(m_Snapshot, type);
 
             if (type.isPointer)
-                enabled = m_MemoryReader.ReadPointer(address) > 0;
+                enabled = m_MemoryReader.ReadPointer(address).contains(_ => _ > 0);
         }
 
         protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add)
diff --git a/Editor/Scripts/PropertyGrid/PropertyGridControl.cs b/Editor/Scripts/PropertyGrid/PropertyGridControl.cs
index f5c5a25..0a879a5 100644
--- a/Editor/Scripts/PropertyGrid/PropertyGridControl.cs
+++ b/Editor/Scripts/PropertyGrid/PropertyGridControl.cs
@@ -67,7 +67,7 @@ public void Inspect(PackedMemorySnapshot snapshot, PackedManagedObject managedOb
             var m_type = snapshot.managedTypes[managedObject.managedTypesArrayIndex];
             var m_address = managedObject.address;
             if (m_type.isValueType)
-                m_address -= (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize;
+                m_address -= m_Snapshot.virtualMachineInformation.objectHeaderSize;
 
             var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
             if (m_Snapshot == null)
@@ -155,7 +155,7 @@ void TryCreateDataVisualizer(AbstractMemoryReader reader, PackedManagedType type
             //if (AbstractDataVisualizer.HasVisualizer(type.name))
             {
                 if (type.isPointer && resolveAddress)
-                    address = reader.ReadPointer(address);
+                    address = reader.ReadPointer(address).getOrThrow();
 
                 m_DataVisualizer = AbstractDataVisualizer.CreateVisualizer(type.name);
                 m_DataVisualizer.Initialize(m_Snapshot, reader, address, type);
@@ -221,7 +221,7 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
             // Add the base class, if any, to the tree.
             if (type.isDerivedReferenceType && !type.isArray)
             {
-                var baseType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex];
+                var baseType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
                 var isSystemObject = baseType.managedTypesArrayIndex == m_Snapshot.coreTypes.systemObject;
                 if (!isSystemObject && PackedManagedTypeUtility.HasTypeOrBaseAnyField(m_Snapshot, baseType, !addStatic, addStatic))
                 {
@@ -245,7 +245,7 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
                 // {
                 //   static readonly T[] _EmptyArray = new T[0];
                 // }
-                if (type.baseOrElementTypeIndex == -1)
+                if (type.baseOrElementTypeIndex.isNone)
                     return;
 
                 var pointer = address;
@@ -276,10 +276,10 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
                 // Array
                 if (fieldType.isArray)
                 {
-                    if (fieldType.baseOrElementTypeIndex == -1)
+                    if (fieldType.baseOrElementTypeIndex.isNone)
                         continue;
 
-                    var pointer = reader.ReadPointer(address + (ulong)type.fields[n].offset);
+                    var pointer = reader.ReadPointer(address + type.fields[n].offset).getOrThrow();
                     var item = new ArrayPropertyGridItem(this, m_Snapshot, pointer, new MemoryReader(m_Snapshot))
                     {
                         depth = target.depth + 1,
@@ -295,7 +295,7 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
                 // Primitive types and types derived from System.Enum
                 if (fieldType.isValueType && (fieldType.isPrimitive || m_Snapshot.IsEnum(fieldType)))
                 {
-                    var item = new PrimitiveTypePropertyGridItem(this, m_Snapshot, address + (ulong)type.fields[n].offset, reader)
+                    var item = new PrimitiveTypePropertyGridItem(this, m_Snapshot, address + type.fields[n].offset, reader)
                     {
                         depth = target.depth + 1,
                         field = type.fields[n]
@@ -309,7 +309,7 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
                 // Value types
                 if (fieldType.isValueType)
                 {
-                    var item = new ValueTypePropertyGridItem(this, m_Snapshot, address + (ulong)type.fields[n].offset, reader)
+                    var item = new ValueTypePropertyGridItem(this, m_Snapshot, address + type.fields[n].offset, reader)
                     {
                         depth = target.depth + 1,
                         field = type.fields[n]
@@ -323,7 +323,7 @@ void AddType(PropertyGridItem.BuildChildrenArgs args)
                 // Reference types
                 //if (fieldType.isPointer)
                 {
-                    var item = new ReferenceTypePropertyGridItem(this, m_Snapshot, address + (ulong)type.fields[n].offset, reader)
+                    var item = new ReferenceTypePropertyGridItem(this, m_Snapshot, address + type.fields[n].offset, reader)
                     {
                         depth = target.depth + 1,
                         field = type.fields[n]
diff --git a/Editor/Scripts/PropertyGrid/PropertyGridItem.cs b/Editor/Scripts/PropertyGrid/PropertyGridItem.cs
index 678086f..725bfd8 100644
--- a/Editor/Scripts/PropertyGrid/PropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/PropertyGridItem.cs
@@ -66,11 +66,8 @@ public void Initialize()
 
             OnInitialize();
 
-            var text = string.Format("{0} {1} = {2}\n\n{3}",
-                displayType,
-                displayName,
-                displayValue,
-                PackedManagedTypeUtility.GetInheritanceAsString(m_Snapshot, type.managedTypesArrayIndex));
+            var text =
+                $"{displayType} {displayName} = {displayValue}\n\n{PackedManagedTypeUtility.GetInheritanceAsString(m_Snapshot, type.managedTypesArrayIndex)}";
 
             this.tooltip = text.Trim();
         }
@@ -145,7 +142,7 @@ void TryDrawDataVisualizerButton(PropertyGridItem itm, ref Rect rect)
             {
                 var pointer = address;
                 if (type.isPointer)
-                    pointer = myMemoryReader.ReadPointer(address);
+                    pointer = myMemoryReader.ReadPointer(address).getOrThrow();
 
                 DataVisualizerWindow.CreateWindow(m_Snapshot, myMemoryReader, pointer, type);
             }
@@ -163,13 +160,13 @@ void TryDrawObjectButton(PropertyGridItem itm, ref Rect rect)
                 return;
 
             // If it points to null, it has no object
-            var pointer = itm.myMemoryReader.ReadPointer(itm.address);
+            var pointer = itm.myMemoryReader.ReadPointer(itm.address).getOrThrow();
             if (pointer == 0)
                 return;
 
             // Check if it is a managed object
-            var managedObjIndex = m_Snapshot.FindManagedObjectOfAddress(itm.type.isArray ? itm.address : pointer);
-            if (managedObjIndex != -1)
+            var maybeManagedObjIndex = m_Snapshot.FindManagedObjectOfAddress(itm.type.isArray ? itm.address : pointer);
+            if (maybeManagedObjIndex.valueOut(out var managedObjIndex))
             {
                 if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref rect, rect.height)))
                 {
@@ -178,8 +175,7 @@ void TryDrawObjectButton(PropertyGridItem itm, ref Rect rect)
             }
 
             // Check if it is a native object
-            var nativeObjIndex = m_Snapshot.FindNativeObjectOfAddress(pointer);
-            if (nativeObjIndex != -1)
+            if (m_Snapshot.FindNativeObjectOfAddress(pointer).valueOut(out var nativeObjIndex))
             {
                 if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref rect, rect.height)))
                 {
diff --git a/Editor/Scripts/PropertyGrid/PropertyGridView.cs b/Editor/Scripts/PropertyGrid/PropertyGridView.cs
index 1d1236c..c34f8ab 100644
--- a/Editor/Scripts/PropertyGrid/PropertyGridView.cs
+++ b/Editor/Scripts/PropertyGrid/PropertyGridView.cs
@@ -2,11 +2,12 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
+
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor.IMGUI.Controls;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -15,8 +16,7 @@ public class PropertyGridView : HeapExplorerView
         PropertyGridControl m_PropertyGrid;
         AbstractDataVisualizer m_DataVisualizer;
         Vector2 m_DataVisualizerScrollPos;
-        RichManagedObject m_ManagedObject;
-        RichManagedType m_ManagedType;
+        Option<RichManagedType> m_ManagedType;
         bool m_ShowAsHex;
         HexView m_HexView;
 
@@ -52,9 +52,7 @@ public override void OnGUI()
             {
                 using (new EditorGUILayout.HorizontalScope())
                 {
-                    var label = "Field(s)";
-                    if (m_ManagedType.isValid)
-                        label = m_ManagedType.name + " field(s)";
+                    var label = m_ManagedType.fold("Field(s)", managedType => managedType.name + " field(s)");
                     EditorGUILayout.LabelField(label, EditorStyles.boldLabel, GUILayout.ExpandWidth(true));
 
                     m_ShowAsHex = GUILayout.Toggle(m_ShowAsHex, new GUIContent(HeEditorStyles.eyeImage, "Show Memory"), EditorStyles.miniButton, GUILayout.Width(30), GUILayout.Height(17));
@@ -75,36 +73,39 @@ public override void OnGUI()
             }
         }
 
-        public void Inspect(PackedManagedObject managedObject)
-        {
-            m_ManagedObject = new RichManagedObject(snapshot, managedObject.managedObjectsArrayIndex);
-            m_ManagedType = m_ManagedObject.type;
-            m_PropertyGrid.Inspect(snapshot, m_ManagedObject.packed);
+        public void Inspect(PackedManagedObject managedObject) {
+            var richManagedObject = new RichManagedObject(snapshot, managedObject.managedObjectsArrayIndex);
+            m_ManagedType = Some(richManagedObject.type);
+            m_PropertyGrid.Inspect(snapshot, richManagedObject.packed);
 
             m_DataVisualizer = null;
-            if (AbstractDataVisualizer.HasVisualizer(m_ManagedObject.type.name))
+            if (AbstractDataVisualizer.HasVisualizer(richManagedObject.type.name))
             {
-                m_DataVisualizer = AbstractDataVisualizer.CreateVisualizer(m_ManagedObject.type.name);
-                m_DataVisualizer.Initialize(snapshot, new MemoryReader(snapshot), m_ManagedObject.address, m_ManagedObject.type.packed);
+                m_DataVisualizer = AbstractDataVisualizer.CreateVisualizer(richManagedObject.type.name);
+                m_DataVisualizer.Initialize(snapshot, new MemoryReader(snapshot), richManagedObject.address, richManagedObject.type.packed);
             }
 
-            m_HexView.Inspect(snapshot, managedObject.address, (ulong)managedObject.size);
+            m_HexView.Inspect(snapshot, managedObject.address, managedObject.size.getOrElse(0));
         }
 
         public void Inspect(RichManagedType managedType)
         {
-            m_ManagedObject = RichManagedObject.invalid;
-            m_ManagedType = managedType;
-            m_PropertyGrid.InspectStaticType(snapshot, m_ManagedType.packed);
-            m_HexView.Inspect(snapshot, 0, new ArraySegment64<byte>(managedType.packed.staticFieldBytes, 0, (ulong)managedType.packed.staticFieldBytes.LongLength));
+            m_ManagedType = Some(managedType);
+            m_PropertyGrid.InspectStaticType(snapshot, managedType.packed);
+            m_HexView.Inspect(
+                snapshot, 0, 
+                new ArraySegment64<byte>(
+                    managedType.packed.staticFieldBytes, 0, 
+                    managedType.packed.staticFieldBytes.LongLength.ToULongClamped()
+                )
+            );
 
             m_DataVisualizer = null;
         }
 
         public void Clear()
         {
-            m_ManagedObject = RichManagedObject.invalid;
-            m_ManagedType = RichManagedType.invalid;
+            m_ManagedType = None._;
             m_PropertyGrid.Clear();
             m_HexView.Clear();
             m_DataVisualizer = null;
diff --git a/Editor/Scripts/PropertyGrid/ReferenceTypePropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ReferenceTypePropertyGridItem.cs
index f19ff02..33e49bd 100644
--- a/Editor/Scripts/PropertyGrid/ReferenceTypePropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ReferenceTypePropertyGridItem.cs
@@ -22,7 +22,7 @@ public ReferenceTypePropertyGridItem(PropertyGridControl owner, PackedMemorySnap
 
         protected override void OnInitialize()
         {
-            m_Pointer = m_MemoryReader.ReadPointer(address);
+            m_Pointer = m_MemoryReader.ReadPointer(address).getOrThrow();
 
             // If it's a pointer, read the actual object type from the object in memory
             // and do not rely on the field type. The reason for this is that the field type
@@ -30,15 +30,14 @@ protected override void OnInitialize()
             var type = m_Snapshot.managedTypes[field.managedTypesArrayIndex];
             if (type.isPointer && m_Pointer != 0)
             {
-                var i = m_Snapshot.FindManagedObjectTypeOfAddress(m_Pointer);
-                if (i != -1)
+                if (m_Snapshot.FindManagedObjectTypeOfAddress(m_Pointer).valueOut(out var i))
                     type = m_Snapshot.managedTypes[i];
             }
 
             base.type = type;
             displayName = field.name;
             displayType = type.name;
-            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type);
+            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type).getOrElse("<cannot read>");
             isExpandable = false;// m_pointer > 0 && PackedManageTypeUtility.HasTypeOrBaseAnyInstanceField(m_snapshot, type);
             enabled = m_Pointer > 0;
             icon = HeEditorStyles.GetTypeImage(m_Snapshot, type);
diff --git a/Editor/Scripts/PropertyGrid/ValueTypePropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ValueTypePropertyGridItem.cs
index acb153a..365bc6e 100644
--- a/Editor/Scripts/PropertyGrid/ValueTypePropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ValueTypePropertyGridItem.cs
@@ -24,7 +24,7 @@ protected override void OnInitialize()
             type = m_Snapshot.managedTypes[field.managedTypesArrayIndex];
             displayName = field.name;
             displayType = type.name;
-            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type);
+            displayValue = m_MemoryReader.ReadFieldValueAsString(address, type).getOrElse("<cannot read>");
             isExpandable = false;
 
             if (field.isStatic)
@@ -55,10 +55,11 @@ protected override void OnInitialize()
 
         protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add)
         {
-            var args = new BuildChildrenArgs();
-            args.parent = this;
-            args.type = m_Snapshot.managedTypes[field.managedTypesArrayIndex];
-            args.address = address - (ulong)m_Snapshot.virtualMachineInformation.objectHeaderSize;
+            var args = new BuildChildrenArgs {
+                parent = this,
+                type = m_Snapshot.managedTypes[field.managedTypesArrayIndex],
+                address = address - m_Snapshot.virtualMachineInformation.objectHeaderSize
+            };
             args.memoryReader = field.isStatic ? (AbstractMemoryReader)(new StaticMemoryReader(m_Snapshot, args.type.staticFieldBytes)) : (AbstractMemoryReader)(new MemoryReader(m_Snapshot));// m_memoryReader;
             add(args);
         }
diff --git a/Editor/Scripts/RichTypes/RichGCHandle.cs b/Editor/Scripts/RichTypes/RichGCHandle.cs
index 84fdab2..748930e 100644
--- a/Editor/Scripts/RichTypes/RichGCHandle.cs
+++ b/Editor/Scripts/RichTypes/RichGCHandle.cs
@@ -2,98 +2,43 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
+
+using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
-    public struct RichGCHandle
+    /// <summary>
+    /// An <see cref="PackedGCHandle"/> index validated against a <see cref="PackedMemorySnapshot"/>.
+    /// </summary>
+    public readonly struct RichGCHandle
     {
-        PackedMemorySnapshot m_Snapshot;
-        int m_GCHandleArrayIndex;
-
-        public RichGCHandle(PackedMemorySnapshot snapshot, int gcHandlesArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_GCHandleArrayIndex = gcHandlesArrayIndex;
-        }
-
-        public PackedGCHandle packed
-        {
-            get
-            {
-                if (!isValid)
-                    return new PackedGCHandle() { gcHandlesArrayIndex = -1, managedObjectsArrayIndex = -1 };
-
-                return m_Snapshot.gcHandles[m_GCHandleArrayIndex];
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly int gcHandlesArrayIndex;
+
+        public RichGCHandle(PackedMemorySnapshot snapshot, int gcHandlesArrayIndex) {
+            if (gcHandlesArrayIndex >= snapshot.gcHandles.Length) {
+                throw new ArgumentOutOfRangeException(
+                    $"{gcHandlesArrayIndex} is out of bounds [0..{snapshot.gcHandles.Length})"
+                );
             }
+            
+            this.snapshot = snapshot;
+            this.gcHandlesArrayIndex = gcHandlesArrayIndex;
         }
 
-        public PackedMemorySnapshot snapshot
-        {
-            get
-            {
-                return m_Snapshot;
-            }
-        }
+        public PackedGCHandle packed => snapshot.gcHandles[gcHandlesArrayIndex];
 
-        public bool isValid
-        {
-            get
-            {
-                var value = m_Snapshot != null && m_GCHandleArrayIndex >= 0 && m_GCHandleArrayIndex < m_Snapshot.gcHandles.Length;
-                return value;
-            }
-        }
+        public Option<RichManagedObject> managedObject =>
+            packed.managedObjectsArrayIndex.valueOut(out var index)
+                ? Some(new RichManagedObject(snapshot, index))
+                : None._;
 
-        public RichManagedObject managedObject
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var gcHandle = m_Snapshot.gcHandles[m_GCHandleArrayIndex];
-                    if (gcHandle.managedObjectsArrayIndex >= 0)
-                        return new RichManagedObject(m_Snapshot, gcHandle.managedObjectsArrayIndex);
-                }
+        public Option<RichNativeObject> nativeObject => managedObject.flatMap(_ => _.nativeObject);
 
-                return RichManagedObject.invalid;
-            }
-        }
-
-        public RichNativeObject nativeObject
-        {
-            get
-            {
-                return managedObject.nativeObject;
-            }
-        }
-
-        public System.UInt64 managedObjectAddress
-        {
-            get
-            {
-                if (!isValid)
-                    return 0;
-
-                return m_Snapshot.gcHandles[m_GCHandleArrayIndex].target;
-            }
-        }
-
-        public int size
-        {
-            get
-            {
-                return m_Snapshot.virtualMachineInformation.pointerSize;
-            }
-        }
+        public ulong managedObjectAddress => packed.target;
 
-        public static readonly RichGCHandle invalid = new RichGCHandle()
-        {
-            m_Snapshot = null,
-            m_GCHandleArrayIndex = -1
-        };
+        public int size => snapshot.virtualMachineInformation.pointerSize.sizeInBytes();
     }
 }
diff --git a/Editor/Scripts/RichTypes/RichManagedObject.cs b/Editor/Scripts/RichTypes/RichManagedObject.cs
index dbae14e..eb4f09b 100644
--- a/Editor/Scripts/RichTypes/RichManagedObject.cs
+++ b/Editor/Scripts/RichTypes/RichManagedObject.cs
@@ -2,139 +2,59 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
+
+using System;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
-    public struct RichManagedObject
+    /// <summary>
+    /// An <see cref="PackedManagedObject.ArrayIndex"/> validated against a <see cref="PackedMemorySnapshot"/>.
+    /// </summary>
+    public readonly struct RichManagedObject
     {
-        public RichManagedObject(PackedMemorySnapshot snapshot, int managedObjectsArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_ManagedObjectArrayIndex = managedObjectsArrayIndex;
-        }
-
-        public PackedManagedObject packed
-        {
-            get
-            {
-                if (!isValid)
-                    return PackedManagedObject.New();
-
-                return m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-            }
-        }
-
-        public PackedMemorySnapshot snapshot
-        {
-            get
-            {
-                return m_Snapshot;
-            }
-        }
-
-        public bool isValid
-        {
-            get
-            {
-                var value = m_Snapshot != null && m_ManagedObjectArrayIndex >= 0 && m_ManagedObjectArrayIndex < m_Snapshot.managedObjects.Length;
-                return value;
-            }
-        }
-
-        public System.Int32 arrayIndex
-        {
-            get
-            {
-                return m_ManagedObjectArrayIndex;
-            }
-        }
-
-        public System.UInt64 address
-        {
-            get
-            {
-                if (!isValid)
-                    return 0;
-
-                var mo = m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-                return mo.address;
-            }
-        }
-
-        public System.Int32 size
+        public RichManagedObject(PackedMemorySnapshot snapshot, PackedManagedObject.ArrayIndex managedObjectsArrayIndex)
         {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-                    return mo.size;
-                }
-
-                return 0;
+            if (managedObjectsArrayIndex.isStatic)
+                throw new ArgumentException(
+                    $"{managedObjectsArrayIndex} is static while we're trying to create a {nameof(RichManagedObject)}!"
+                );
+            if (managedObjectsArrayIndex.index >= snapshot.managedObjects.Length) {
+                throw new ArgumentOutOfRangeException(
+                    $"{managedObjectsArrayIndex} is out of bounds [0..{snapshot.managedObjects.Length})"
+                );
             }
+            this.snapshot = snapshot;
+            this.managedObjectsArrayIndex = managedObjectsArrayIndex.index;
         }
 
-        public RichManagedType type
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-                    return new RichManagedType(m_Snapshot, mo.managedTypesArrayIndex);
-                }
-
-                return RichManagedType.invalid;
-            }
-        }
+        public PackedManagedObject packed => snapshot.managedObjects[managedObjectsArrayIndex];
 
-        public RichGCHandle gcHandle
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-                    if (mo.gcHandlesArrayIndex >= 0)
-                        return new RichGCHandle(m_Snapshot, mo.gcHandlesArrayIndex);
-                }
+        public ulong address => packed.address;
 
-                return RichGCHandle.invalid;
-            }
-        }
+        public uint size => packed.size.getOrElse(0);
 
-        public RichNativeObject nativeObject
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedObjects[m_ManagedObjectArrayIndex];
-                    if (mo.nativeObjectsArrayIndex >= 0)
-                        return new RichNativeObject(m_Snapshot, mo.nativeObjectsArrayIndex);
-                }
+        public RichManagedType type => new RichManagedType(snapshot, packed.managedTypesArrayIndex);
 
-                return RichNativeObject.invalid;
-            }
-        }
+        public Option<RichGCHandle> gcHandle =>
+            packed.gcHandlesArrayIndex.valueOut(out var index) 
+                ? Some(new RichGCHandle(snapshot, index)) 
+                : None._;
 
-        public override string ToString()
-        {
-            return string.Format("Valid: {0}, Addr: {1:X}, Type: {2}", isValid, address, type.name);
-        }
+        public Option<RichNativeObject> nativeObject =>
+            packed.nativeObjectsArrayIndex.valueOut(out var index) 
+                ? Some(new RichNativeObject(snapshot, index))
+                : None._;
 
-        public static readonly RichManagedObject invalid = new RichManagedObject()
-        {
-            m_Snapshot = null,
-            m_ManagedObjectArrayIndex = -1
-        };
+        public override string ToString() =>
+            // We output the address with '0x' prefix to make it comfortable to copy and paste it into an exact search
+            // field.
+            $"Addr: 0x{address:X}, Type: {type.name}";
 
-        PackedMemorySnapshot m_Snapshot;
-        int m_ManagedObjectArrayIndex;
+        public readonly PackedMemorySnapshot snapshot;
+        
+        /// <summary>Index into <see cref="PackedMemorySnapshot.managedObjects"/>.</summary>
+        public readonly int managedObjectsArrayIndex;
     }
 }
diff --git a/Editor/Scripts/RichTypes/RichManagedType.cs b/Editor/Scripts/RichTypes/RichManagedType.cs
index 4d44e53..bfba23d 100644
--- a/Editor/Scripts/RichTypes/RichManagedType.cs
+++ b/Editor/Scripts/RichTypes/RichManagedType.cs
@@ -2,95 +2,46 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
+
 using System;
+using UnityEngine;
 
 namespace HeapExplorer
 {
     /// <summary>
-    /// The RichManagedType provides high-level access to a managed type in a memory snapshot.
+    /// An <see cref="PackedManagedType"/> index validated against a <see cref="PackedMemorySnapshot"/>.
     /// </summary>
-    public struct RichManagedType
+    public readonly struct RichManagedType
     {
-        public RichManagedType(PackedMemorySnapshot snapshot, int managedTypesArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_ManagedTypesArrayIndex = managedTypesArrayIndex;
-        }
-
-        /// <summary>
-        /// Gets the underlaying low-level type.
-        /// </summary>
-        public PackedManagedType packed
-        {
-            get
-            {
-                if (!isValid)
-                    return new PackedManagedType() { baseOrElementTypeIndex = -1, managedTypesArrayIndex = -1 };
-
-                return m_Snapshot.managedTypes[m_ManagedTypesArrayIndex];
-            }
-        }
-
-        public PackedMemorySnapshot snapshot
-        {
-            get
-            {
-                return m_Snapshot;
-            }
+        public RichManagedType(PackedMemorySnapshot snapshot, int managedTypesArrayIndex) {
+            if (managedTypesArrayIndex < 0 || managedTypesArrayIndex >= snapshot.managedTypes.Length)
+                throw new ArgumentOutOfRangeException(
+                    $"managedTypesArrayIndex ({managedTypesArrayIndex}) was out of bounds [0..{snapshot.managedTypes.Length})"
+                );
+            
+            this.snapshot = snapshot;
+            this.managedTypesArrayIndex = managedTypesArrayIndex;
         }
 
         /// <summary>
-        /// Gets whether the RichManagedType instance is valid.
+        /// Gets the underlying low-level type.
         /// </summary>
-        public bool isValid
-        {
-            get
-            {
-                return m_Snapshot != null && m_ManagedTypesArrayIndex >= 0 && m_ManagedTypesArrayIndex < m_Snapshot.managedTypes.Length;
-            }
-        }
+        public PackedManagedType packed => snapshot.managedTypes[managedTypesArrayIndex];
 
         /// <summary>
         /// Gets the name of the type.
         /// </summary>
-        public string name
-        {
-            get
-            {
-                if (!isValid)
-                    return "<unknown type>";
-
-                return m_Snapshot.managedTypes[m_ManagedTypesArrayIndex].name;
-            }
-        }
+        public string name => packed.name;
 
         /// <summary>
         /// Gets the name of the assembly where this type is stored in.
         /// </summary>
-        public string assemblyName
-        {
-            get
-            {
-                if (!isValid)
-                    return "<unknown assembly>";
-
-                return m_Snapshot.managedTypes[m_ManagedTypesArrayIndex].assembly;
-            }
-        }
+        public string assemblyName => packed.assembly;
 
         public bool FindField(string name, out PackedManagedField packedManagedField)
         {
-            packedManagedField = new PackedManagedField();
-            if (!isValid)
-                return false;
-
             var guard = 0;
-            var me = m_Snapshot.managedTypes[m_ManagedTypesArrayIndex];
+            var me = packed;
             while (me.managedTypesArrayIndex != -1)
             {
                 for (var n=0; n<me.fields.Length; ++n)
@@ -102,14 +53,18 @@ public bool FindField(string name, out PackedManagedField packedManagedField)
                     }
                 }
 
-                if (++guard > 64)
+                if (++guard > 64) {
+                    Debug.LogError($"Guard hit in {nameof(FindField)}({name}) at type '{me.name}'");
                     break;
+                }
 
-                if (me.baseOrElementTypeIndex == -1)
+                if (!me.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
                     break;
 
-                me = m_Snapshot.managedTypes[me.baseOrElementTypeIndex];
+                me = snapshot.managedTypes[baseOrElementTypeIndex];
             }
+
+            packedManagedField = default;
             return false;
         }
 
@@ -118,38 +73,31 @@ public bool FindField(string name, out PackedManagedField packedManagedField)
         /// </summary>
         public bool IsSubclassOf(PackedManagedType t)
         {
-            if (!isValid || t.managedTypesArrayIndex == -1)
+            if (t.managedTypesArrayIndex == -1)
                 return false;
 
-            var me = m_Snapshot.managedTypes[m_ManagedTypesArrayIndex];
+            var me = packed;
             if (me.managedTypesArrayIndex == t.managedTypesArrayIndex)
                 return true;
 
             var guard = 0;
-            while (me.baseOrElementTypeIndex != -1)
+            while (me.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex))
             {
-                if (++guard > 64)
+                if (++guard > 64) {
+                    Debug.LogError($"Guard hit in {nameof(IsSubclassOf)}({t.name}) at type '{me.name}'");
                     break; // no inheritance should have more depths than this
+                }
 
-                if (me.baseOrElementTypeIndex == t.managedTypesArrayIndex)
+                if (baseOrElementTypeIndex == t.managedTypesArrayIndex)
                     return true;
 
-                me = m_Snapshot.managedTypes[me.baseOrElementTypeIndex];
+                me = snapshot.managedTypes[baseOrElementTypeIndex];
             }
 
             return false;
         }
 
-        /// <summary>
-        /// Gets an invalid managed type instance.
-        /// </summary>
-        public static readonly RichManagedType invalid = new RichManagedType()
-        {
-            m_Snapshot = null,
-            m_ManagedTypesArrayIndex = -1
-        };
-
-        PackedMemorySnapshot m_Snapshot;
-        int m_ManagedTypesArrayIndex;
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly int managedTypesArrayIndex;
     }
 }
diff --git a/Editor/Scripts/RichTypes/RichNativeObject.cs b/Editor/Scripts/RichTypes/RichNativeObject.cs
index d6b239c..8dbe2c4 100644
--- a/Editor/Scripts/RichTypes/RichNativeObject.cs
+++ b/Editor/Scripts/RichTypes/RichNativeObject.cs
@@ -2,198 +2,73 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
+
+using System;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
-    public struct RichNativeObject
+    /// <summary>
+    /// An <see cref="PackedNativeUnityEngineObject"/> index validated against a <see cref="PackedMemorySnapshot"/>.
+    /// </summary>
+    public readonly struct RichNativeObject
     {
         public RichNativeObject(PackedMemorySnapshot snapshot, int nativeObjectsArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_NativeObjectArrayIndex = nativeObjectsArrayIndex;
-        }
-
-        public PackedNativeUnityEngineObject packed
-        {
-            get
-            {
-                if (!isValid)
-                    return new PackedNativeUnityEngineObject() { nativeObjectsArrayIndex = -1, nativeTypesArrayIndex = -1, managedObjectsArrayIndex = -1 };
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex];
-            }
-        }
-
-        public PackedMemorySnapshot snapshot
         {
-            get
-            {
-                return m_Snapshot;
-            }
+            if (snapshot == null) throw new ArgumentNullException(nameof(snapshot));
+            if (nativeObjectsArrayIndex < 0 || nativeObjectsArrayIndex >= snapshot.nativeObjects.Length)
+                throw new ArgumentOutOfRangeException(
+                    $"nativeObjectsArrayIndex ({nativeObjectsArrayIndex})is out of bounds [0..{snapshot.nativeObjects.Length})"
+                );
+            this.snapshot = snapshot;
+            this.nativeObjectsArrayIndex = nativeObjectsArrayIndex;
         }
 
-        public bool isValid
-        {
-            get
-            {
-                return m_Snapshot != null && m_NativeObjectArrayIndex >= 0 && m_NativeObjectArrayIndex < m_Snapshot.nativeObjects.Length;
-            }
-        }
+        public override string ToString() =>
+            // We output the address with '0x' prefix to make it comfortable to copy and paste it into an exact search
+            // field.
+            $"Addr: 0x{address:X}, InstanceId: {instanceId}, Type: {type.name}, "
+            + $"GCHandle: {gcHandle}, ManagedObject: {managedObject}";
 
-        public RichNativeType type
-        {
-            get
-            {
-                if (!isValid)
-                    return RichNativeType.invalid;
-
-                var obj = m_Snapshot.nativeObjects[m_NativeObjectArrayIndex];
-                return new RichNativeType(m_Snapshot, obj.nativeTypesArrayIndex);
-            }
-        }
+        public PackedNativeUnityEngineObject packed => snapshot.nativeObjects[nativeObjectsArrayIndex];
 
-        public RichManagedObject managedObject
-        {
-            get
-            {
-                if (!isValid)
-                    return RichManagedObject.invalid;
+        public RichNativeType type => new RichNativeType(snapshot, packed.nativeTypesArrayIndex);
 
-                var native = m_Snapshot.nativeObjects[m_NativeObjectArrayIndex];
-                if (native.managedObjectsArrayIndex < 0)
-                    return RichManagedObject.invalid;
+        public Option<RichManagedObject> managedObject =>
+            packed.managedObjectsArrayIndex.valueOut(out var index)
+                ? Some(new RichManagedObject(snapshot, index))
+                : None._;
 
-                return new RichManagedObject(m_Snapshot, native.managedObjectsArrayIndex);
-            }
-        }
+        public Option<RichGCHandle> gcHandle => managedObject.flatMap(_ => _.gcHandle);
 
-        public RichGCHandle gcHandle
-        {
-            get
-            {
-                return managedObject.gcHandle;
-            }
-        }
+        public string name => packed.name;
 
-        public string name
-        {
-            get
-            {
-                if (!isValid)
-                    return "<invalid>";
+        public ulong address => packed.nativeObjectAddress;
 
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].name;
-            }
-        }
+        public HideFlags hideFlags => packed.hideFlags;
 
-        public System.UInt64 address
-        {
-            get
-            {
-                if (!isValid)
-                    return 0;
+        public int instanceId => packed.instanceId;
 
-                return (System.UInt64)m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].nativeObjectAddress;
-            }
-        }
+        public bool isDontDestroyOnLoad => packed.isDontDestroyOnLoad;
 
-        public HideFlags hideFlags
-        {
-            get
-            {
-                if (!isValid)
-                    return HideFlags.None;
+        public bool isManager => packed.isManager;
 
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].hideFlags;
-            }
-        }
+        public bool isPersistent => packed.isPersistent;
 
-        public int instanceId
-        {
-            get
-            {
-                if (!isValid)
-                    return 0;
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].instanceId;
-            }
-        }
-
-        public bool isDontDestroyOnLoad
-        {
-            get
-            {
-                if (!isValid)
-                    return true;
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].isDontDestroyOnLoad;
-            }
-        }
-
-        public bool isManager
-        {
-            get
-            {
-                if (!isValid)
-                    return false;
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].isManager;
-            }
-        }
-
-        public bool isPersistent
-        {
-            get
-            {
-                if (!isValid)
-                    return false;
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].isPersistent;
-            }
-        }
-
-        public int size
-        {
-            get
-            {
-                if (!isValid)
-                    return 0;
-
-                return m_Snapshot.nativeObjects[m_NativeObjectArrayIndex].size;
-            }
-        }
-
-        public void GetConnections(List<PackedConnection> references, List<PackedConnection> referencedBy)
-        {
-            if (!isValid)
-                return;
-
-            m_Snapshot.GetConnections(packed, references, referencedBy);
-        }
+        public ulong size => packed.size;
 
         public void GetConnectionsCount(out int referencesCount, out int referencedByCount)
         {
-            if (!isValid)
-            {
-                referencesCount = 0;
-                referencedByCount = 0;
-                return;
-            }
-
-            m_Snapshot.GetConnectionsCount(PackedConnection.Kind.Native, m_NativeObjectArrayIndex, out referencesCount, out referencedByCount);
+            snapshot.GetConnectionsCount(
+                new PackedConnection.Pair(PackedConnection.Kind.Native, nativeObjectsArrayIndex), 
+                out referencesCount, out referencedByCount
+            );
         }
 
-        public static readonly RichNativeObject invalid = new RichNativeObject()
-        {
-            m_Snapshot = null,
-            m_NativeObjectArrayIndex = -1
-        };
-
-        PackedMemorySnapshot m_Snapshot;
-        int m_NativeObjectArrayIndex;
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly int nativeObjectsArrayIndex;
     }
 }
diff --git a/Editor/Scripts/RichTypes/RichNativeType.cs b/Editor/Scripts/RichTypes/RichNativeType.cs
index 4d7bb2a..eebd110 100644
--- a/Editor/Scripts/RichTypes/RichNativeType.cs
+++ b/Editor/Scripts/RichTypes/RichNativeType.cs
@@ -2,94 +2,36 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using UnityEditor;
+
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
-    public struct RichNativeType
+    /// <summary>
+    /// An <see cref="PackedNativeType"/> index validated against a <see cref="PackedMemorySnapshot"/>.
+    /// </summary>
+    public readonly struct RichNativeType
     {
-        public RichNativeType(PackedMemorySnapshot snapshot, int nativeTypesArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_NativeTypesArrayIndex = nativeTypesArrayIndex;
-        }
-
-        public PackedNativeType packed
-        {
-            get
-            {
-                if (!isValid)
-                    return new PackedNativeType() { nativeTypeArrayIndex = -1, nativeBaseTypeArrayIndex = -1, name = "" };
-
-                return m_Snapshot.nativeTypes[m_NativeTypesArrayIndex];
-            }
-        }
-
-        public PackedMemorySnapshot snapshot
-        {
-            get
-            {
-                return m_Snapshot;
-            }
-        }
-
-        public bool isValid
-        {
-            get
-            {
-                return m_Snapshot != null && m_NativeTypesArrayIndex >= 0 && m_NativeTypesArrayIndex < m_Snapshot.nativeTypes.Length;
-            }
+        public RichNativeType(PackedMemorySnapshot snapshot, PInt nativeTypesArrayIndex) {
+            this.snapshot = snapshot;
+            this.nativeTypesArrayIndex = nativeTypesArrayIndex;
         }
 
-        public string name
-        {
-            get
-            {
-                if (!isValid)
-                    return "<unknown type>";
+        public PackedNativeType packed => snapshot.nativeTypes[nativeTypesArrayIndex];
+        public string name => packed.name;
 
-                var t = m_Snapshot.nativeTypes[m_NativeTypesArrayIndex];
-                return t.name;
-            }
-        }
-
-        public RichNativeType baseType
-        {
-            get
-            {
-                if (!isValid)
-                    return RichNativeType.invalid;
-
-                var t = m_Snapshot.nativeTypes[m_NativeTypesArrayIndex];
-                if (t.nativeBaseTypeArrayIndex < 0)
-                    return RichNativeType.invalid;
-
-                return new RichNativeType(m_Snapshot, t.nativeBaseTypeArrayIndex);
-            }
-        }
+        public Option<RichNativeType> baseType =>
+            packed.nativeBaseTypeArrayIndex.valueOut(out var index)
+                ? Some(new RichNativeType(snapshot, index))
+                : None._; 
 
         /// <summary>
         /// Gets whether this native type is a subclass of the specified baseType.
         /// </summary>
-        public bool IsSubclassOf(int baseTypeIndex)
-        {
-            if (!isValid || baseTypeIndex < 0)
-                return false;
-
-            return m_Snapshot.IsSubclassOf(m_Snapshot.nativeTypes[m_NativeTypesArrayIndex], baseTypeIndex);
-        }
-
-        public static readonly RichNativeType invalid = new RichNativeType()
-        {
-            m_Snapshot = null,
-            m_NativeTypesArrayIndex = -1
-        };
+        public bool IsSubclassOf(PInt baseTypeIndex) => snapshot.IsSubclassOf(packed, baseTypeIndex);
 
-        PackedMemorySnapshot m_Snapshot;
-        int m_NativeTypesArrayIndex;
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly PInt nativeTypesArrayIndex;
     }
 }
diff --git a/Editor/Scripts/RichTypes/RichStaticField.cs b/Editor/Scripts/RichTypes/RichStaticField.cs
index 0d74f30..acc04fb 100644
--- a/Editor/Scripts/RichTypes/RichStaticField.cs
+++ b/Editor/Scripts/RichTypes/RichStaticField.cs
@@ -2,104 +2,54 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
+
+using System;
 
 namespace HeapExplorer
 {
-    public struct RichStaticField
+    /// <summary>
+    /// An <see cref="PackedManagedStaticField"/> index validated against a <see cref="PackedMemorySnapshot"/>.
+    /// </summary>
+    public readonly struct RichStaticField
     {
-        public RichStaticField(PackedMemorySnapshot snapshot, int staticFieldsArrayIndex)
-            : this()
-        {
-            m_Snapshot = snapshot;
-            m_ManagedStaticFieldsArrayIndex = staticFieldsArrayIndex;
+        public RichStaticField(PackedMemorySnapshot snapshot, int staticFieldsArrayIndex) {
+            if (snapshot == null) throw new ArgumentNullException(nameof(snapshot));
+            if (staticFieldsArrayIndex < 0 || staticFieldsArrayIndex >= snapshot.managedStaticFields.Length)
+                throw new ArgumentOutOfRangeException(
+                    $"staticFieldsArrayIndex ({staticFieldsArrayIndex}) is out of bounds [0..{snapshot.managedStaticFields.Length})"
+                );
+
+            
+            this.snapshot = snapshot;
+            managedStaticFieldsArrayIndex = staticFieldsArrayIndex;
         }
 
-        public PackedManagedStaticField packed
-        {
-            get
-            {
-                if (!isValid)
-                {
-                    return new PackedManagedStaticField()
-                    {
-                        managedTypesArrayIndex = -1,
-                        fieldIndex = -1,
-                        staticFieldsArrayIndex = -1,
-                    };
-                }
-
-                return m_Snapshot.managedStaticFields[m_ManagedStaticFieldsArrayIndex];
-            }
-        }
+        public override string ToString() =>
+            $"Name: {staticField.name}, In Type: {classType.name}, Of Type: {fieldType.name}";
 
-        public PackedMemorySnapshot snapshot
-        {
-            get
-            {
-                return m_Snapshot;
-            }
-        }
+        public PackedManagedStaticField packed => snapshot.managedStaticFields[managedStaticFieldsArrayIndex];
 
-        public bool isValid
-        {
-            get
-            {
-                var value = m_Snapshot != null && m_ManagedStaticFieldsArrayIndex >= 0 && m_ManagedStaticFieldsArrayIndex < m_Snapshot.managedStaticFields.Length;
-                return value;
-            }
-        }
+        public PackedManagedField staticField {
+            get {
+                var mo = packed;
 
-        public System.Int32 arrayIndex
-        {
-            get
-            {
-                return m_ManagedStaticFieldsArrayIndex;
+                var staticClassType = snapshot.managedTypes[mo.managedTypesArrayIndex];
+                var staticField = staticClassType.fields[mo.fieldIndex];
+                return staticField;
             }
         }
 
-        public RichManagedType fieldType
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedStaticFields[m_ManagedStaticFieldsArrayIndex];
-
-                    var staticClassType = m_Snapshot.managedTypes[mo.managedTypesArrayIndex];
-                    var staticField = staticClassType.fields[mo.fieldIndex];
-                    var staticFieldType = m_Snapshot.managedTypes[staticField.managedTypesArrayIndex];
-
-                    return new RichManagedType(m_Snapshot, staticFieldType.managedTypesArrayIndex);
-                }
-
-                return RichManagedType.invalid;
-            }
-        }
-
-        public RichManagedType classType
-        {
-            get
-            {
-                if (isValid)
-                {
-                    var mo = m_Snapshot.managedStaticFields[m_ManagedStaticFieldsArrayIndex];
-                    return new RichManagedType(m_Snapshot, mo.managedTypesArrayIndex);
-                }
-
-                return RichManagedType.invalid;
+        public RichManagedType fieldType {
+            get {
+                var staticFieldType = snapshot.managedTypes[staticField.managedTypesArrayIndex];
+                return new RichManagedType(snapshot, staticFieldType.managedTypesArrayIndex);
             }
         }
 
-        public static readonly RichStaticField invalid = new RichStaticField()
-        {
-            m_Snapshot = null,
-            m_ManagedStaticFieldsArrayIndex = -1
-        };
+        public RichManagedType classType =>
+            new RichManagedType(snapshot, packed.managedTypesArrayIndex);
 
-        PackedMemorySnapshot m_Snapshot;
-        int m_ManagedStaticFieldsArrayIndex;
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly int managedStaticFieldsArrayIndex;
     }
 }
diff --git a/Editor/Scripts/RootPathView/RootPathControl.cs b/Editor/Scripts/RootPathView/RootPathControl.cs
index f6a10af..36f1850 100644
--- a/Editor/Scripts/RootPathView/RootPathControl.cs
+++ b/Editor/Scripts/RootPathView/RootPathControl.cs
@@ -2,80 +2,93 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
 using System.Linq;
+using System.Text;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
+using static HeapExplorer.ViewConsts;
 
 namespace HeapExplorer
 {
     public class ObjectProxy
     {
-        public PackedMemorySnapshot snapshot;
-        public RichNativeObject native;
-        public RichManagedObject managed;
-        public RichGCHandle gcHandle;
-        public RichStaticField staticField;
+        public readonly PackedMemorySnapshot snapshot;
+        public readonly Option<RichNativeObject> native;
+        public readonly Option<RichManagedObject> managed;
+        public readonly Option<RichGCHandle> gcHandle;
+        public readonly Option<RichStaticField> staticField;
+        
+        /// <summary>Field in the source object that points to this object, if available.</summary>
+        public readonly Option<PackedManagedField> sourceField;
 
         public System.Int64 id
         {
             get
             {
-                if (native.isValid)
-                    return (1 << 62) + native.packed.nativeObjectsArrayIndex;
+                if (native.isSome)
+                    return (1 << 62) + native.__unsafeGet.nativeObjectsArrayIndex;
 
-                if (managed.isValid)
-                    return (1 << 61) + managed.packed.managedObjectsArrayIndex;
+                if (managed.isSome)
+                    return (1 << 61) + managed.__unsafeGet.managedObjectsArrayIndex;
 
-                if (gcHandle.isValid)
-                    return (1 << 60) + gcHandle.packed.gcHandlesArrayIndex;
+                if (gcHandle.isSome)
+                    return (1 << 60) + gcHandle.__unsafeGet.gcHandlesArrayIndex;
 
-                if (staticField.isValid)
-                    return (1 << 59) + staticField.packed.staticFieldsArrayIndex;
+                if (staticField.isSome)
+                    return (1 << 59) + staticField.__unsafeGet.managedStaticFieldsArrayIndex;
 
                 return 0;
             }
         }
 
-        public ObjectProxy(PackedMemorySnapshot snp, PackedNativeUnityEngineObject packed)
+        public ObjectProxy(PackedMemorySnapshot snp, PackedNativeUnityEngineObject packed, Option<PackedManagedField> sourceField)
         {
             snapshot = snp;
-            native = new RichNativeObject(snp, packed.nativeObjectsArrayIndex);
+            native = Some(new RichNativeObject(snp, packed.nativeObjectsArrayIndex));
+            this.sourceField = sourceField;
         }
 
-        public ObjectProxy(PackedMemorySnapshot snp, PackedManagedObject packed)
+        public ObjectProxy(PackedMemorySnapshot snp, PackedManagedObject packed, Option<PackedManagedField> sourceField)
         {
             snapshot = snp;
-            managed = new RichManagedObject(snp, packed.managedObjectsArrayIndex);
+            managed = Some(new RichManagedObject(snp, packed.managedObjectsArrayIndex));
+            this.sourceField = sourceField;
         }
 
-        public ObjectProxy(PackedMemorySnapshot snp, PackedGCHandle packed)
+        public ObjectProxy(PackedMemorySnapshot snp, PackedGCHandle packed, Option<PackedManagedField> sourceField)
         {
             snapshot = snp;
-            gcHandle = new RichGCHandle(snp, packed.gcHandlesArrayIndex);
+            gcHandle = Some(new RichGCHandle(snp, packed.gcHandlesArrayIndex));
+            this.sourceField = sourceField;
         }
 
-        public ObjectProxy(PackedMemorySnapshot snp, PackedManagedStaticField packed)
+        public ObjectProxy(PackedMemorySnapshot snp, PackedManagedStaticField packed, Option<PackedManagedField> sourceField)
         {
             snapshot = snp;
-            staticField = new RichStaticField(snp, packed.staticFieldsArrayIndex);
+            staticField = Some(new RichStaticField(snp, packed.staticFieldsArrayIndex));
+            this.sourceField = sourceField;
         }
 
-        public override string ToString()
-        {
-            if (native.isValid)
-                return string.Format("Native, {0}", native);
+        public override string ToString() {
+            var fieldStr = sourceField.fold(
+                snapshot, "", (f, snapshot) => 
+                    $"\nSource Field: '{f.name}' of type '{snapshot.managedTypes[f.managedTypesArrayIndex].name}'"
+            );
+            if (native.isSome)
+                return $"Native, {native.__unsafeGet}{fieldStr}";
 
-            if (managed.isValid)
-                return string.Format("Managed, {0}", managed);
+            if (managed.isSome)
+                return $"Managed, {managed.__unsafeGet}{fieldStr}";
 
-            if (gcHandle.isValid)
-                return string.Format("GCHandle, {0}", gcHandle);
+            if (gcHandle.isSome)
+                return $"GCHandle, {gcHandle.__unsafeGet}{fieldStr}";
 
-            if (staticField.isValid)
-                return string.Format("StaticField, {0}", staticField);
+            if (staticField.isSome)
+                return $"StaticField, {staticField.__unsafeGet}{fieldStr}";
 
             return base.ToString();
         }
@@ -100,26 +113,15 @@ public enum RootPathReason
 
     public class RootPath : System.IComparable<RootPath>
     {
-        public int count
-        {
-            get
-            {
-                return m_Items.Length;
-            }
-        }
+        public int count => m_Items.Length;
 
-        public RootPathReason reason
-        {
-            get;
-            private set;
-        }
+        public readonly RootPathReason reason;
 
         public string reasonString
         {
             get
             {
-                switch(reason)
-                {
+                switch(reason) {
                     case RootPathReason.None:
                         return "";
                     case RootPathReason.AssetBundle:
@@ -138,28 +140,20 @@ public string reasonString
                         return "Static fields are global variables. Anything they reference will not be unloaded.";
                     case RootPathReason.Unknown:
                         return "This object is a root, but the memory profiler UI does not yet understand why";
+                    default:
+                        return "???";
                 }
-
-                return "???";
             }
         }
 
-        ObjectProxy[] m_Items = new ObjectProxy[0];
+        readonly ObjectProxy[] m_Items;
 
-        public RootPath(RootPathReason reason, ObjectProxy[] path)
-            : base()
-        {
+        public RootPath(RootPathReason reason, ObjectProxy[] path) {
             this.reason = reason;
             this.m_Items = path;
         }
 
-        public ObjectProxy this[int index]
-        {
-            get
-            {
-                return m_Items[index];
-            }
-        }
+        public ObjectProxy this[int index] => m_Items[index];
 
         public int CompareTo(RootPath other)
         {
@@ -176,7 +170,7 @@ public class RootPathUtility
         bool m_IsBusy;
         int m_ScanCount;
 
-        static int s_IterationLoopGuard = 1000000;
+        static readonly int s_IterationLoopGuard = 1000000;
 
         public bool isBusy
         {
@@ -283,8 +277,7 @@ public void Find(ObjectProxy obj)
                 var pop = queue.Dequeue();
                 var tip = pop.Last();
 
-                RootPathReason reason;
-                if (IsRoot(tip, out reason))
+                if (IsRoot(tip, out var reason))
                 {
                     m_Items.Add(new RootPath(reason, pop.ToArray()));
                     continue;
@@ -311,61 +304,66 @@ public void Find(ObjectProxy obj)
                 Debug.LogWarningFormat("{0} issues have been detected while finding root-paths. This is most likely related to an earlier bug in Heap Explorer, that started to occur with Unity 2019.3 and causes that (some) object connections are invalid. Please capture a new memory snapshot.", issues);
         }
 
-        List<ObjectProxy> GetReferencedBy(ObjectProxy obj, ref int issues)
+        static List<ObjectProxy> GetReferencedBy(ObjectProxy obj, ref int issues)
         {
             var referencedBy = new List<PackedConnection>(32);
 
-            if (obj.staticField.isValid)
-                obj.snapshot.GetConnections(obj.staticField.packed, null, referencedBy);
+            {if (obj.staticField.valueOut(out var staticField))
+                obj.snapshot.GetConnections(staticField.packed, null, referencedBy);}
 
-            if (obj.native.isValid)
-                obj.snapshot.GetConnections(obj.native.packed, null, referencedBy);
+            {if (obj.native.valueOut(out var nativeObject))
+                obj.snapshot.GetConnections(nativeObject.packed, null, referencedBy);}
 
-            if (obj.managed.isValid)
-                obj.snapshot.GetConnections(obj.managed.packed, null, referencedBy);
+            {if (obj.managed.valueOut(out var managedObject))
+                obj.snapshot.GetConnections(managedObject.packed, null, referencedBy);}
 
-            if (obj.gcHandle.isValid)
-                obj.snapshot.GetConnections(obj.gcHandle.packed, null, referencedBy);
+            {if (obj.gcHandle.valueOut(out var gcHandle))
+                obj.snapshot.GetConnections(gcHandle.packed, null, referencedBy);}
 
             var value = new List<ObjectProxy>(referencedBy.Count);
-            foreach (var c in referencedBy)
-            {
-                switch (c.fromKind)
-                {
+            foreach (var c in referencedBy) {
+                switch (c.from.pair.kind) {
                     case PackedConnection.Kind.Native:
-                        if (c.from < 0 || c.from >= obj.snapshot.nativeObjects.Length)
-                        {
+                        if (c.from.pair.index < 0 || c.from.pair.index >= obj.snapshot.nativeObjects.Length) {
                             issues++;
                             continue;
                         }
-                        value.Add(new ObjectProxy(obj.snapshot, obj.snapshot.nativeObjects[c.from]));
+                        value.Add(new ObjectProxy(
+                            obj.snapshot, obj.snapshot.nativeObjects[c.from.pair.index], c.from.field
+                        ));
                         break;
 
                     case PackedConnection.Kind.Managed:
-                        if (c.from < 0 || c.from >= obj.snapshot.managedObjects.Length)
+                        if (c.from.pair.index < 0 || c.from.pair.index >= obj.snapshot.managedObjects.Length)
                         {
                             issues++;
                             continue;
                         }
-                        value.Add(new ObjectProxy(obj.snapshot, obj.snapshot.managedObjects[c.from]));
+                        value.Add(new ObjectProxy(
+                            obj.snapshot, obj.snapshot.managedObjects[c.from.pair.index], c.from.field
+                        ));
                         break;
 
                     case PackedConnection.Kind.GCHandle:
-                        if (c.from < 0 || c.from >= obj.snapshot.gcHandles.Length)
+                        if (c.from.pair.index < 0 || c.from.pair.index >= obj.snapshot.gcHandles.Length)
                         {
                             issues++;
                             continue;
                         }
-                        value.Add(new ObjectProxy(obj.snapshot, obj.snapshot.gcHandles[c.from]));
+                        value.Add(new ObjectProxy(
+                            obj.snapshot, obj.snapshot.gcHandles[c.from.pair.index], c.from.field
+                        ));
                         break;
 
                     case PackedConnection.Kind.StaticField:
-                        if (c.from < 0 || c.from >= obj.snapshot.managedStaticFields.Length)
+                        if (c.from.pair.index < 0 || c.from.pair.index >= obj.snapshot.managedStaticFields.Length)
                         {
                             issues++;
                             continue;
                         }
-                        value.Add(new ObjectProxy(obj.snapshot, obj.snapshot.managedStaticFields[c.from]));
+                        value.Add(new ObjectProxy(
+                            obj.snapshot, obj.snapshot.managedStaticFields[c.from.pair.index], c.from.field
+                        ));
                         break;
                 }
             }
@@ -377,61 +375,61 @@ bool IsRoot(ObjectProxy thing, out RootPathReason reason)
         {
             reason = RootPathReason.None;
 
-            if (thing.staticField.isValid)
+            if (thing.staticField.isSome)
             {
                 reason = RootPathReason.Static;
                 return true;
             }
 
-            if (thing.managed.isValid)
+            if (thing.managed.isSome)
             {
                 return false;
             }
 
-            if (thing.gcHandle.isValid)
+            if (thing.gcHandle.isSome)
             {
                 return false;
             }
 
-            if (!thing.native.isValid)
+            if (!thing.native.valueOut(out var native))
                 throw new System.ArgumentException("Unknown type: " + thing.GetType());
 
-            if (thing.native.isManager)
+            if (native.isManager)
             {
                 reason = RootPathReason.UnityManager;
                 return true;
             }
 
-            if (thing.native.isDontDestroyOnLoad)
+            if (native.isDontDestroyOnLoad)
             {
                 reason = RootPathReason.DontDestroyOnLoad;
                 return true;
             }
 
-            if ((thing.native.hideFlags & HideFlags.DontUnloadUnusedAsset) != 0)
+            if ((native.hideFlags & HideFlags.DontUnloadUnusedAsset) != 0)
             {
                 reason = RootPathReason.DontUnloadUnusedAsset;
                 return true;
             }
 
-            if (thing.native.isPersistent)
+            if (native.isPersistent)
             {
                 return false;
             }
 
-            if (thing.native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeComponent))
+            if (native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeComponent))
             {
                 reason = RootPathReason.Component;
                 return true;
             }
 
-            if (thing.native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeGameObject))
+            if (native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeGameObject))
             {
                 reason = RootPathReason.GameObject;
                 return true;
             }
 
-            if (thing.native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeAssetBundle))
+            if (native.type.IsSubclassOf(thing.snapshot.coreTypes.nativeAssetBundle))
             {
                 reason = RootPathReason.AssetBundle;
                 return true;
@@ -454,17 +452,18 @@ enum Column
             Type,
             Name,
             Depth,
+            SourceField,
             Address,
         }
 
         public RootPathControl(HeapExplorerWindow window, string editorPrefsKey, TreeViewState state)
             : base(window, editorPrefsKey, state, new MultiColumnHeader(
-                new MultiColumnHeaderState(new[]
-                {
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Type"), width = 200, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("C++ Name"), width = 200, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Depth"), width = 80, autoResize = true },
-                new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Address"), width = 200, autoResize = true },
+                new MultiColumnHeaderState(new[] {
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Type"), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent(COLUMN_CPP_NAME, COLUMN_CPP_NAME_DESCRIPTION), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Depth"), width = 80, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent(COLUMN_SOURCE_FIELD, COLUMN_SOURCE_FIELD_DESCRIPTION), width = 200, autoResize = true },
+                    new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Address"), width = 200, autoResize = true },
                 })))
         {
             columnIndexForTreeFoldouts = 0;
@@ -525,14 +524,16 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, RootPathUtility pat
                     var obj = path[n];
                     Item newItem = null;
 
-                    if (obj.native.isValid)
-                        newItem = AddNativeUnityObject(parent, obj.native.packed);
-                    else if (obj.managed.isValid)
-                        newItem = AddManagedObject(parent, obj.managed.packed);
-                    else if (obj.gcHandle.isValid)
-                        newItem = AddGCHandle(parent, obj.gcHandle.packed);
-                    else if (obj.staticField.isValid)
-                        newItem = AddStaticField(parent, obj.staticField.packed);
+                    {
+                        if (obj.native.valueOut(out var nativeObject))
+                            newItem = AddNativeUnityObject(parent, nativeObject.packed, obj.sourceField);
+                        else if (obj.managed.valueOut(out var managedObject))
+                            newItem = AddManagedObject(parent, managedObject.packed, obj.sourceField);
+                        else if (obj.gcHandle.valueOut(out var gcHandle))
+                            newItem = AddGCHandle(parent, gcHandle.packed, obj.sourceField);
+                        else if (obj.staticField.valueOut(out var staticField))
+                            newItem = AddStaticField(parent, staticField.packed, obj.sourceField);
+                    }
 
                     if (parent == root)
                     {
@@ -545,12 +546,13 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot, RootPathUtility pat
             return root;
         }
 
-        Item AddGCHandle(TreeViewItem parent, PackedGCHandle gcHandle)
+        Item AddGCHandle(TreeViewItem parent, PackedGCHandle gcHandle, Option<PackedManagedField> field)
         {
             var item = new GCHandleItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name)
             };
 
             item.Initialize(this, m_Snapshot, gcHandle.gcHandlesArrayIndex);
@@ -558,12 +560,13 @@ Item AddGCHandle(TreeViewItem parent, PackedGCHandle gcHandle)
             return item;
         }
 
-        Item AddManagedObject(TreeViewItem parent, PackedManagedObject managedObject)
+        Item AddManagedObject(TreeViewItem parent, PackedManagedObject managedObject, Option<PackedManagedField> field)
         {
             var item = new ManagedObjectItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name)
             };
 
             item.Initialize(this, m_Snapshot, managedObject.managedObjectsArrayIndex);
@@ -571,12 +574,14 @@ Item AddManagedObject(TreeViewItem parent, PackedManagedObject managedObject)
             return item;
         }
 
-        Item AddNativeUnityObject(TreeViewItem parent, PackedNativeUnityEngineObject nativeObject)
-        {
+        Item AddNativeUnityObject(
+            TreeViewItem parent, PackedNativeUnityEngineObject nativeObject, Option<PackedManagedField> field
+        ) {
             var item = new NativeObjectItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name)
             };
 
             item.Initialize(this, m_Snapshot, nativeObject);
@@ -584,12 +589,13 @@ Item AddNativeUnityObject(TreeViewItem parent, PackedNativeUnityEngineObject nat
             return item;
         }
 
-        Item AddStaticField(TreeViewItem parent, PackedManagedStaticField staticField)
+        Item AddStaticField(TreeViewItem parent, PackedManagedStaticField staticField, Option<PackedManagedField> field)
         {
             var item = new ManagedStaticFieldItem
             {
                 id = m_UniqueId++,
                 depth = parent.depth + 1,
+                fieldName = field.fold("", _ => _.name)
             };
 
             item.Initialize(this, m_Snapshot, staticField.staticFieldsArrayIndex);
@@ -608,6 +614,7 @@ class Item : AbstractTreeViewItem
 
             protected RootPathControl m_Owner;
             protected string m_Value;
+            public string fieldName;
             protected System.UInt64 m_Address;
 
             public override void GetItemSearchString(string[] target, out int count, out string type, out string label)
@@ -650,11 +657,37 @@ public override void OnGUI(Rect position, int column)
                             HeEditorGUI.Address(position, m_Address);
                         break;
 
+                    case Column.SourceField:
+                        EditorGUI.LabelField(position, fieldName);
+                        break;
+
                     case Column.Depth:
                         if (rootPath != null)
                             EditorGUI.LabelField(position, rootPath.count.ToString());
                         break;
                 }
+
+                if (column == 0) {
+                    var e = Event.current;
+                    if (e.type == EventType.ContextClick && position.Contains(e.mousePosition))
+                    {
+                        var menu = new GenericMenu();
+                        menu.AddItem(new GUIContent("Copy path"), on: false, (GenericMenu.MenuFunction2)delegate (object userData)
+                        {
+                            var rootPath = (RootPath) userData;
+                            var text = new StringBuilder(rootPath.reasonString);
+                            text.Append("\n\n");
+                            var count = rootPath.count;
+                            for (var idx = 0; idx < count; idx++) {
+                                // Go backwards to mimic the view in Unity.
+                                var objectProxy = rootPath[count - idx - 1];
+                                text.AppendFormat("[{0}] {1}\n\n", idx, objectProxy);
+                            }
+                            EditorGUIUtility.systemCopyBuffer = text.ToString();
+                        }, rootPath);
+                        menu.ShowAsContext();
+                    }
+                }
             }
         }
 
@@ -672,7 +705,7 @@ public void Initialize(RootPathControl owner, PackedMemorySnapshot snapshot, int
                 m_GCHandle = new RichGCHandle(m_Snapshot, gcHandleArrayIndex);
 
                 displayName = "GCHandle";
-                m_Value = m_GCHandle.managedObject.isValid ? m_GCHandle.managedObject.type.name : "";
+                m_Value = m_GCHandle.managedObject.fold("", _ => _.type.name);
                 m_Address = m_GCHandle.managedObjectAddress;
             }
 
@@ -685,21 +718,19 @@ public override void OnGUI(Rect position, int column)
                         m_Owner.window.OnGoto(new GotoCommand(m_GCHandle));
                     }
 
-                    if (m_GCHandle.nativeObject.isValid)
-                    {
+                    {if (m_GCHandle.nativeObject.valueOut(out var nativeObject)) {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
-                    }
+                    }}
 
-                    if (m_GCHandle.managedObject.isValid)
-                    {
+                    {if (m_GCHandle.managedObject.valueOut(out var managedObject)) {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_GCHandle.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
@@ -712,14 +743,16 @@ class ManagedObjectItem : Item
         {
             RichManagedObject m_ManagedObject;
 
-            public void Initialize(RootPathControl owner, PackedMemorySnapshot snapshot, int arrayIndex)
+            public void Initialize(
+                RootPathControl owner, PackedMemorySnapshot snapshot, PackedManagedObject.ArrayIndex arrayIndex
+            )
             {
                 m_Owner = owner;
                 m_ManagedObject = new RichManagedObject(snapshot, arrayIndex);
 
                 displayName = m_ManagedObject.type.name;
                 m_Address = m_ManagedObject.address;
-                m_Value = m_ManagedObject.nativeObject.isValid ? m_ManagedObject.nativeObject.name : "";
+                m_Value = m_ManagedObject.nativeObject.fold("", _ => _.name);
             }
 
             public override void OnGUI(Rect position, int column)
@@ -731,21 +764,19 @@ public override void OnGUI(Rect position, int column)
                         m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject));
                     }
 
-                    if (m_ManagedObject.gcHandle.isValid)
-                    {
+                    {if (m_ManagedObject.gcHandle.valueOut(out var gcHandle)) {
                         if (HeEditorGUI.GCHandleButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject.gcHandle));
+                            m_Owner.window.OnGoto(new GotoCommand(gcHandle));
                         }
-                    }
+                    }}
 
-                    if (m_ManagedObject.nativeObject.isValid)
-                    {
+                    {if (m_ManagedObject.nativeObject.valueOut(out var nativeObject)) {
                         if (HeEditorGUI.CppButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_ManagedObject.nativeObject));
+                            m_Owner.window.OnGoto(new GotoCommand(nativeObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
@@ -809,11 +840,10 @@ public void Initialize(RootPathControl owner, PackedMemorySnapshot snapshot, Pac
                 // TODO: Move to separate method
                 if (m_NativeObject.type.IsSubclassOf(m_Snapshot.coreTypes.nativeMonoBehaviour) || m_NativeObject.type.IsSubclassOf(m_Snapshot.coreTypes.nativeScriptableObject))
                 {
-                    string monoScriptName;
-                    if (m_Snapshot.FindNativeMonoScriptType(m_NativeObject.packed.nativeObjectsArrayIndex, out monoScriptName) != -1)
+                    if (m_Snapshot.FindNativeMonoScriptType(m_NativeObject.packed.nativeObjectsArrayIndex).valueOut(out var tpl))
                     {
-                        if (!string.IsNullOrEmpty(monoScriptName))
-                            displayName = monoScriptName;
+                        if (!string.IsNullOrEmpty(tpl.monoScriptName))
+                            displayName = tpl.monoScriptName;
                     }
                 }
             }
@@ -827,21 +857,19 @@ public override void OnGUI(Rect position, int column)
                         m_Owner.window.OnGoto(new GotoCommand(m_NativeObject));
                     }
 
-                    if (m_NativeObject.gcHandle.isValid)
-                    {
+                    {if (m_NativeObject.gcHandle.valueOut(out var gcHandle)) {
                         if (HeEditorGUI.GCHandleButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_NativeObject.gcHandle));
+                            m_Owner.window.OnGoto(new GotoCommand(gcHandle));
                         }
-                    }
+                    }}
 
-                    if (m_NativeObject.managedObject.isValid)
-                    {
+                    {if (m_NativeObject.managedObject.valueOut(out var managedObject)) {
                         if (HeEditorGUI.CsButton(HeEditorGUI.SpaceR(ref position, position.height)))
                         {
-                            m_Owner.window.OnGoto(new GotoCommand(m_NativeObject.managedObject));
+                            m_Owner.window.OnGoto(new GotoCommand(managedObject));
                         }
-                    }
+                    }}
                 }
 
                 base.OnGUI(position, column);
diff --git a/Editor/Scripts/RootPathView/RootPathView.cs b/Editor/Scripts/RootPathView/RootPathView.cs
index 73bd84a..6454f02 100644
--- a/Editor/Scripts/RootPathView/RootPathView.cs
+++ b/Editor/Scripts/RootPathView/RootPathView.cs
@@ -2,8 +2,8 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
+
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -41,7 +41,7 @@ public override void OnGUI()
                 {
                     using (new EditorGUILayout.HorizontalScope())
                     {
-                        EditorGUILayout.LabelField(string.Format("{0} Path(s) to Root", m_RootPaths.count), EditorStyles.boldLabel);
+                        EditorGUILayout.LabelField($"{m_RootPaths.count} Path(s) to Root", EditorStyles.boldLabel);
                     }
 
                     GUILayout.Space(2);
@@ -62,7 +62,7 @@ public override void OnGUI()
                 r.width = 200;
                 r.y = r.center.y - 70;
                 r.height = 50;
-                GUI.Label(r, string.Format("Finding root paths, please wait.\n{0} objects scanned", m_RootPaths.scanned), HeEditorStyles.centeredWordWrapLabel);
+                GUI.Label(r, $"Finding root paths, please wait.\n{m_RootPaths.scanned} objects scanned", HeEditorStyles.centeredWordWrapLabel);
 
                 r = GUILayoutUtility.GetLastRect();
                 r.x = r.center.x - 60;
@@ -80,25 +80,29 @@ public override void OnGUI()
         public void Inspect(PackedNativeUnityEngineObject item)
         {
             m_Selected = null;
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedManagedObject item)
         {
             m_Selected = null;
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedManagedStaticField item)
         {
             m_Selected = null;
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Inspect(PackedGCHandle item)
         {
             m_Selected = null;
-            ScheduleJob(new ObjectProxy(snapshot, item));
+            // TODO: add `sourceField` data
+            ScheduleJob(new ObjectProxy(snapshot, item, sourceField: None._));
         }
 
         public void Clear()
diff --git a/Editor/Scripts/SearchTextParser.cs b/Editor/Scripts/SearchTextParser.cs
index 9540913..980c6fa 100644
--- a/Editor/Scripts/SearchTextParser.cs
+++ b/Editor/Scripts/SearchTextParser.cs
@@ -4,6 +4,7 @@
 //
 using System;
 using System.Collections.Generic;
+using UnityEngine;
 
 namespace HeapExplorer
 {
@@ -24,7 +25,14 @@ internal class ResultExpr
             public LogicalOperator Op;
             public bool Not;
             public bool Exact;
-            public string Text;
+            
+            /// <summary>
+            /// Lower-cased text.
+            /// <para/>
+            /// It is a lot cheaper to lower-case the string once than use
+            /// <see cref="StringComparison.OrdinalIgnoreCase"/> on every search. 
+            /// </summary>
+            public string LowerCasedText;
         }
 
         public class Result
@@ -57,13 +65,12 @@ public bool IsLabelMatch(string label)
                 return labels.Contains(label);
             }
 
-            public bool IsNameMatch(string text)
+            /// <param name="lowerCasedText">string that underwent transformation to lower-case.</param>
+            public bool IsNameMatch(string lowerCasedText)
             {
                 if (m_NamesExpr.Count == 0)
                     return true;
-                if (text == null || text.Length == 0)
-                    return true;
-
+                
                 var or_result = false;
                 var or_result_missing = true;
 
@@ -75,18 +82,18 @@ public bool IsNameMatch(string text)
                     {
                         if (expression.Not)
                         {
-                            if (!expression.Exact && text.IndexOf(expression.Text, StringComparison.OrdinalIgnoreCase) != -1)
+                            if (!expression.Exact && lowerCasedText.ContainsFast(expression.LowerCasedText))
                                 return false;
 
-                            if (expression.Exact && text.Equals(expression.Text, StringComparison.OrdinalIgnoreCase))
+                            if (expression.Exact && lowerCasedText.Equals(expression.LowerCasedText, StringComparison.Ordinal))
                                 return false;
                         }
                         else
                         {
-                            if (!expression.Exact && text.IndexOf(expression.Text, StringComparison.OrdinalIgnoreCase) == -1)
+                            if (!expression.Exact && !lowerCasedText.ContainsFast(expression.LowerCasedText))
                                 return false;
 
-                            if (expression.Exact && !text.Equals(expression.Text, StringComparison.OrdinalIgnoreCase))
+                            if (expression.Exact && !lowerCasedText.Equals(expression.LowerCasedText, StringComparison.Ordinal))
                                 return false;
                         }
                     }
@@ -94,18 +101,18 @@ public bool IsNameMatch(string text)
                     {
                         if (expression.Not)
                         {
-                            if (!expression.Exact && text.IndexOf(expression.Text, StringComparison.OrdinalIgnoreCase) == -1)
+                            if (!expression.Exact && !lowerCasedText.ContainsFast(expression.LowerCasedText))
                                 or_result = true;
 
-                            if (expression.Exact && !text.Equals(expression.Text, StringComparison.OrdinalIgnoreCase))
+                            if (expression.Exact && !lowerCasedText.Equals(expression.LowerCasedText, StringComparison.Ordinal))
                                 or_result = true;
                         }
                         else
                         {
-                            if (!expression.Exact && text.IndexOf(expression.Text, StringComparison.OrdinalIgnoreCase) != -1)
+                            if (!expression.Exact && lowerCasedText.ContainsFast(expression.LowerCasedText))
                                 or_result = true;
 
-                            if (expression.Exact && text.Equals(expression.Text, StringComparison.OrdinalIgnoreCase))
+                            if (expression.Exact && lowerCasedText.Equals(expression.LowerCasedText, StringComparison.Ordinal))
                                 or_result = true;
                         }
                         or_result_missing = false;
@@ -126,17 +133,24 @@ public static Result Parse(string text)
             var types = result.types;
             var labels = result.labels;
             var builder = new System.Text.StringBuilder(64);
-            var loopguard = 0;
             var condition = LogicalOperator.And;
             var not = false;
             var exact = false;
 
+            var lastN = -1;
             var n = 0;
-            while (n < text.Length)
-            {
-                if (++loopguard > 10000)
+            while (n < text.Length) {
+                if (n == lastN) {
+                    Debug.LogError(
+                        $"HeapExplorer: {nameof(SearchTextParser)}.{nameof(Parse)}() got stuck at n={n}, aborting. "
+                        + "This should not happen."
+                    );
                     break;
-
+                }
+                else {
+                    lastN = n;
+                }
+                
                 SkipWhiteSpace(text, ref n);
                 if (n + 1 < text.Length)
                 {
@@ -179,7 +193,7 @@ public static Result Parse(string text)
                                 names[names.Count - 1].Op = LogicalOperator.Or;
                             var r = new ResultExpr();
                             r.Op = condition;
-                            r.Text = builder.ToString().Trim();
+                            r.LowerCasedText = builder.ToString().Trim().ToLowerInvariant();
                             r.Not = not;
                             r.Exact = exact;
                             names.Add(r);
@@ -223,20 +237,19 @@ public static Result Parse(string text)
                 }
 
                 GetNextWord(text, ref n, builder);
-                if (builder.Length > 0)
-                {
+                if (builder.Length > 0) {
                     if (names.Count > 0 && condition == LogicalOperator.Or)
                         names[names.Count-1].Op = LogicalOperator.Or;
-                    var r = new ResultExpr();
-                    r.Op = condition;
-                    r.Text = builder.ToString().Trim();
-                    r.Not = not;
-                    r.Exact = exact;
+                    var r = new ResultExpr {
+                        Op = condition,
+                        LowerCasedText = builder.ToString().Trim().ToLowerInvariant(),
+                        Not = not,
+                        Exact = exact
+                    };
                     names.Add(r);
                     builder.Length = 0;
                     not = false;
                     condition = LogicalOperator.And;
-                    continue;
                 }
             }
 
@@ -249,7 +262,7 @@ public static Result Parse(string text)
             //    builder.AppendLine("Label: " + labels[n]);
 
             foreach (var v in result.m_NamesExpr)
-                result.names.Add(v.Text);
+                result.names.Add(v.LowerCasedText);
 
             // sort by operator. AND first, OR second
             var nam = new List<ResultExpr>();
@@ -267,45 +280,30 @@ public static Result Parse(string text)
             return result;
         }
 
-        static void SkipWhiteSpace(string text, ref int index)
-        {
-            int loopguard = 0;
-
-            while (index < text.Length)
-            {
-                var tc = text[index];
-                if (!char.IsWhiteSpace(tc))
+        static void SkipWhiteSpace(string text, ref int index) {
+            while (index < text.Length) {
+                var textChar = text[index];
+                if (!char.IsWhiteSpace(textChar))
                     return;
 
                 index++;
-
-                if (++loopguard > 10000)
-                    break;
             }
         }
 
         static void GetNextWord(string text, ref int index, System.Text.StringBuilder builder)
         {
-            int loopguard = 0;
-
-            while (index < text.Length)
-            {
+            while (index < text.Length) {
                 var tc = text[index];
                 if (char.IsWhiteSpace(tc))
                     return;
 
                 builder.Append(tc);
                 index++;
-
-                if (++loopguard > 10000)
-                    break;
             }
         }
 
         static void GetNextQuotedWord(string text, ref int index, System.Text.StringBuilder builder)
         {
-            int loopguard = 0;
-
             index++; // skip first quote
             while (index < text.Length)
             {
@@ -319,10 +317,15 @@ static void GetNextQuotedWord(string text, ref int index, System.Text.StringBuil
 
                 builder.Append(tc);
                 index++;
-
-                if (++loopguard > 10000)
-                    break;
             }
         }
+
+        /// <summary>
+        /// Makes sure to compare using the <see cref="StringComparison.Ordinal"/> which is the fastest way there is. 
+        /// </summary>
+        static bool ContainsFast(this string haystack, string needle) 
+        {
+            return haystack.IndexOf(needle, StringComparison.Ordinal) >= 0;
+        }
     }
 }
diff --git a/Editor/Scripts/StaticFieldsView/StaticFieldsControl.cs b/Editor/Scripts/StaticFieldsView/StaticFieldsControl.cs
index e2bcb4c..10873b3 100644
--- a/Editor/Scripts/StaticFieldsView/StaticFieldsControl.cs
+++ b/Editor/Scripts/StaticFieldsView/StaticFieldsControl.cs
@@ -2,17 +2,17 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
-using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class StaticFieldsControl : AbstractTreeView
     {
-        public System.Action<PackedManagedType?> onTypeSelected;
+        public System.Action<Option<PackedManagedType>> onTypeSelected;
 
         public int count
         {
@@ -89,7 +89,7 @@ protected override void OnSelectionChanged(TreeViewItem selectedItem)
             {
                 var item = selectedItem as StaticTypeItem;
                 if (onTypeSelected != null)
-                    onTypeSelected.Invoke(item.type);
+                    onTypeSelected.Invoke(Some(item.type));
                 return;
             }
         }
@@ -196,7 +196,10 @@ public int referencesCount
                         for (int n=0, nend = list.Count; n < nend; ++n)
                         {
                             int refCount, refByCount;
-                            m_Owner.m_Snapshot.GetConnectionsCount(PackedConnection.Kind.StaticField, list[n], out refCount, out refByCount);
+                            m_Owner.m_Snapshot.GetConnectionsCount(
+                                new PackedConnection.Pair(PackedConnection.Kind.StaticField, list[n]), 
+                                out refCount, out refByCount
+                            );
 
                             m_ReferencesCount += refCount;
                         }
diff --git a/Editor/Scripts/StaticFieldsView/StaticFieldsView.cs b/Editor/Scripts/StaticFieldsView/StaticFieldsView.cs
index 273937f..a63a54b 100644
--- a/Editor/Scripts/StaticFieldsView/StaticFieldsView.cs
+++ b/Editor/Scripts/StaticFieldsView/StaticFieldsView.cs
@@ -2,17 +2,18 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
     public class StaticFieldsView : HeapExplorerView
     {
-        RichManagedType m_Selected;
+        Option<RichManagedType> m_Selected;
         StaticFieldsControl m_StaticFieldsControl;
         HeSearchField m_SearchField;
         PropertyGridView m_PropertyGridView;
@@ -43,10 +44,10 @@ public override void OnToolbarGUI()
             {
                 var menu = new GenericMenu();
 
-                if (!m_Selected.isValid)
-                    menu.AddDisabledItem(new GUIContent("Save selected field as file..."));
+                if (m_Selected.valueOut(out var selected))
+                    menu.AddItem(new GUIContent("Save selected field as file..."), false, () => OnSaveAsFile(selected));
                 else
-                    menu.AddItem(new GUIContent("Save selected field as file..."), false, OnSaveAsFile);
+                    menu.AddDisabledItem(new GUIContent("Save selected field as file..."));
 
                 menu.DropDown(m_ToolbarButtonRect);
             }
@@ -55,15 +56,15 @@ public override void OnToolbarGUI()
                 m_ToolbarButtonRect = GUILayoutUtility.GetLastRect();
         }
 
-        void OnSaveAsFile()
+        void OnSaveAsFile(RichManagedType selected)
         {
-            var filePath = EditorUtility.SaveFilePanel("Save", "", m_Selected.name.Replace('.', '_'), "mem");
+            var filePath = EditorUtility.SaveFilePanel("Save", "", selected.name.Replace('.', '_'), "mem");
             if (string.IsNullOrEmpty(filePath))
                 return;
 
             using (var fileStream = new System.IO.FileStream(filePath, System.IO.FileMode.OpenOrCreate))
             {
-                var bytes = m_Selected.packed.staticFieldBytes;
+                var bytes = selected.packed.staticFieldBytes;
                 fileStream.Write(bytes, 0, bytes.Length);
             }
         }
@@ -101,13 +102,8 @@ protected override void OnHide()
             EditorPrefs.SetFloat(GetPrefsKey(() => m_SplitterVert), m_SplitterVert);
         }
 
-        public override GotoCommand GetRestoreCommand()
-        {
-            if (m_Selected.isValid)
-                return new GotoCommand(m_Selected);
-
-            return base.GetRestoreCommand();
-        }
+        public override GotoCommand GetRestoreCommand() => 
+            m_Selected.valueOut(out var selected) ? new GotoCommand(selected) : base.GetRestoreCommand();
 
         public override void OnGUI()
         {
@@ -121,7 +117,8 @@ public override void OnGUI()
                     {
                         using (new EditorGUILayout.HorizontalScope())
                         {
-                            var text = string.Format("{0} static fields in {1} types", snapshot.managedStaticFields.Length, snapshot.managedStaticTypes.Length);
+                            var text =
+                                $"{snapshot.managedStaticFields.Length} static fields in {snapshot.managedStaticTypes.Length} types";
                             window.SetStatusbarString(text);
                             EditorGUILayout.LabelField(titleContent, EditorStyles.boldLabel);
                             if (m_SearchField.OnToolbarGUI())
@@ -151,7 +148,7 @@ public override void OnGUI()
 
         public override int CanProcessCommand(GotoCommand command)
         {
-            if (command.toStaticField.isValid || command.toManagedType.isValid)
+            if (command.toStaticField.isSome || command.toManagedType.isSome)
                 return 10;
 
             return base.CanProcessCommand(command);
@@ -159,30 +156,29 @@ public override int CanProcessCommand(GotoCommand command)
 
         public override void RestoreCommand(GotoCommand command)
         {
-            if (command.toStaticField.isValid)
-            {
-                m_StaticFieldsControl.Select(command.toStaticField.classType.packed);
+            {if (command.toStaticField.valueOut(out var staticField)) {
+                m_StaticFieldsControl.Select(staticField.classType.packed);
                 return;
-            }
+            }}
 
-            if (command.toManagedType.isValid)
-            {
-                m_StaticFieldsControl.Select(command.toManagedType.packed);
-            }
+            {if (command.toManagedType.valueOut(out var managedType)) {
+                m_StaticFieldsControl.Select(managedType.packed);
+            }}
         }
 
-        void OnListViewTypeSelected(PackedManagedType? type)
+        void OnListViewTypeSelected(Option<PackedManagedType> maybeType)
         {
-            if (!type.HasValue)
+            if (!maybeType.valueOut(out var type))
             {
-                m_Selected = RichManagedType.invalid;
+                m_Selected = None._;
                 m_ConnectionsView.Clear();
                 m_PropertyGridView.Clear();
                 return;
             }
 
-            m_Selected = new RichManagedType(snapshot, type.Value.managedTypesArrayIndex);
-            var staticClass = m_Selected.packed;
+            var selected = new RichManagedType(snapshot, type.managedTypesArrayIndex);
+            m_Selected = Some(selected);
+            var staticClass = selected.packed;
             var staticFields = new List<PackedManagedStaticField>();
 
             // Find all static fields of selected type
@@ -193,7 +189,7 @@ void OnListViewTypeSelected(PackedManagedType? type)
             }
             m_ConnectionsView.Inspect(staticFields.ToArray());
 
-            m_PropertyGridView.Inspect(m_Selected);
+            m_PropertyGridView.Inspect(selected);
         }
     }
 }
diff --git a/Editor/Scripts/Utilities.meta b/Editor/Scripts/Utilities.meta
new file mode 100644
index 0000000..2aae5f2
--- /dev/null
+++ b/Editor/Scripts/Utilities.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ee66fe03e98c459eb31fbb32508f7512
+timeCreated: 1674032530
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/CycleTracker.cs b/Editor/Scripts/Utilities/CycleTracker.cs
new file mode 100644
index 0000000..b46ce86
--- /dev/null
+++ b/Editor/Scripts/Utilities/CycleTracker.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace HeapExplorer.Utilities {
+  /// <summary>
+  /// Allows you to track if you're in a cycle.
+  /// </summary>
+  /// <typeparam name="A">Type of the item we're tracking.</typeparam>
+  /// <example><code><![CDATA[
+  /// var cycleTracker = new CycleTracker<int>();
+  /// do {
+  ///   if (cycleTracker.markIteration(type.managedTypesArrayIndex)) {
+  ///     cycleTracker.report(
+  ///       $"{nameof(HasTypeOrBaseAnyField)}()", type.managedTypesArrayIndex,
+  ///       idx => snapshot.managedTypes[idx].ToString()
+  ///     );
+  ///     break;
+  ///   }
+  ///
+  ///   // your logic
+  /// } while (/* your condition */)
+  /// ]]></code></example>
+  public class CycleTracker<A> {
+    /// <summary>
+    /// Marks seen items and the depth (number of <see cref="markIteration"/> invocations) at which we have seen them.
+    /// </summary>
+    public readonly Dictionary<A, int> itemToDepth = new Dictionary<A, int>();
+
+    const int INITIAL_DEPTH = -1;
+    int depth = INITIAL_DEPTH;
+
+    /// <summary>
+    /// Prepares for iterating.
+    /// </summary>
+    public void markStartOfSearch() {
+      itemToDepth.Clear();
+      depth = INITIAL_DEPTH;
+    }
+
+    /// <summary>
+    /// Marks one iteration in which we see <see cref="item"/>.
+    /// </summary>
+    /// <returns>true if a cycle has been detected</returns>
+    public bool markIteration(A item) {
+      depth++;
+      if (itemToDepth.ContainsKey(item)) {
+        return true;
+      }
+      else {
+        itemToDepth.Add(item, depth);
+        return false;
+      }
+    }
+    
+    /// <summary>
+    /// Reports that a cycle has occured to the Unity console.
+    /// </summary>
+    /// <param name="description">Description of the invoking method.</param>
+    /// <param name="item">The item on which the cycle occured.</param>
+    /// <param name="itemToString">Converts items to human-readable strings.</param>
+    public void reportCycle(
+      string description,
+      A item,
+      Func<A, string> itemToString
+    ) {
+      var itemsStr = string.Join("\n", 
+        itemToDepth.OrderBy(kv => kv.Value).Select(kv => $"[{kv.Value:D3}] {itemToString(kv.Key)}")
+      );
+      var text = 
+        $"HeapExplorer: hit cycle guard at depth {itemToDepth.Count + 1} in {description} for {itemToString(item)}:\n"
+        + itemsStr;
+      Debug.LogWarning(text);
+    }
+  }
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/CycleTracker.cs.meta b/Editor/Scripts/Utilities/CycleTracker.cs.meta
new file mode 100644
index 0000000..e818fa3
--- /dev/null
+++ b/Editor/Scripts/Utilities/CycleTracker.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5e2d89dcad6840fe8ad6c643f1e2e24f
+timeCreated: 1674032629
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Option.cs b/Editor/Scripts/Utilities/Option.cs
new file mode 100644
index 0000000..8418255
--- /dev/null
+++ b/Editor/Scripts/Utilities/Option.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace HeapExplorer.Utilities {
+  /// <summary>
+  /// Represents a value which may be there (the `Some` case) or may not be there (the `None` case).
+  /// <para/>
+  /// Think of type-safe nullable reference types or <see cref="Nullable{T}"/> but it works for both reference and value
+  /// types.
+  /// </summary>
+  public readonly struct Option<A> : IEquatable<Option<A>> {
+    /// <summary>The stored value.</summary>
+    public readonly A __unsafeGet;
+  
+    /// <summary>Whether <see cref="__unsafeGet"/> contains a value.</summary>
+    public readonly bool isSome;
+
+    /// <summary>Whether <see cref="__unsafeGet"/> does not contain a value.</summary>
+    public bool isNone => !isSome;
+
+    public Option(A value) {
+      __unsafeGet = value;
+      isSome = true;
+    }
+
+    public override string ToString() => isSome ? $"Some({__unsafeGet})" : "None";
+
+    /// <example><code><![CDATA[
+    /// void process(Option<Foo> maybeFoo) {
+    ///   if (maybeFoo.valueOut(out var foo)) {
+    ///     // use foo
+    ///   }
+    /// }
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool valueOut(out A value) {
+      value = __unsafeGet;
+      return isSome;
+    }
+
+    /// <summary>Returns <see cref="__unsafeGet"/> if this is `Some` or <see cref="ifNoValue"/> otherwise.</summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public A getOrElse(A ifNoValue) =>
+      isSome ? __unsafeGet : ifNoValue;
+
+    /// <summary>Returns <see cref="__unsafeGet"/> if this is `Some`, throws an exception otherwise.</summary>
+    public A getOrThrow(string message = null) {
+      if (isSome) return __unsafeGet;
+      else throw new Exception(message ?? $"Expected Option<{typeof(A).FullName}> to be `Some` but it was `None`");
+    }
+  
+    /// <summary>
+    /// Returns <see cref="ifNoValue"/> when this is `None` or runs the <see cref="ifHasValue"/> if this is `Some`.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// m_Value = m_GCHandle.managedObject.fold("", _ => _.type.name);
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public B fold<B>(B ifNoValue, Func<A, B> ifHasValue) => 
+      isSome ? ifHasValue(__unsafeGet) : ifNoValue;
+  
+    /// <summary>
+    /// <see cref="fold{B}"/> that allows to not allocate a closure.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// m_Value = m_GCHandle.managedObject.fold(data, "", (_, data) => _.type.computeName(data));
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public B fold<Data, B>(Data data, B ifNoValue, Func<A, Data, B> ifHasValue) => 
+      isSome ? ifHasValue(__unsafeGet, data) : ifNoValue;
+    
+    /// <summary>
+    /// Runs the <see cref="mapper"/> if this is `Some` or else returns `None`.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// Bar? process(Foo? maybeFoo) => maybeFoo.map(foo => foo.toBar());
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Option<B> map<B>(Func<A, B> mapper) => 
+      isSome ? new Option<B>(mapper(__unsafeGet)) : new Option<B>();
+    
+    /// <summary><see cref="map{B}"/> operation that allows to avoid a closure allocation.</summary>
+    /// <example><code><![CDATA[
+    /// Option<Bar> process(Option<Foo> maybeFoo) => maybeFoo.map(data, (foo, data) => foo.toBar(data));
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Option<B> map<Data, B>(Data data, Func<A, Data, B> mapper) => 
+      isSome ? new Option<B>(mapper(__unsafeGet, data)) : new Option<B>();
+    
+    /// <summary>
+    /// Runs the <see cref="mapper"/> if this is `Some` or else returns `None`.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// Option<Bar> process(Option<Foo> maybeFoo) => maybeFoo.map(foo => foo.toMaybeBar());
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Option<B> flatMap<B>(Func<A, Option<B>> mapper) => 
+      isSome ? mapper(__unsafeGet) : new Option<B>();
+    
+    /// <summary><see cref="flatMap{B}"/> operation that allows to avoid a closure allocation.</summary>
+    /// <example><code><![CDATA[
+    /// Option<Bar> process(Option<Foo> maybeFoo) => maybeFoo.map(data, (foo, data) => foo.toMaybeBar(data));
+    /// ]]></code></example>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Option<B> flatMap<Data, B>(Data data, Func<A, Data, Option<B>> mapper) => 
+      isSome ? mapper(__unsafeGet, data) : new Option<B>();
+
+    /// <summary>Returns true if this is `Some` and the value inside satisfies the <see cref="predicate"/>.</summary>
+    public bool contains(Func<A, bool> predicate) =>
+      isSome && predicate(__unsafeGet);
+
+    /// <inheritdoc cref="None._"/>
+    public static implicit operator Option<A>(None _) => new Option<A>();
+  
+    public static bool operator true(Option<A> opt) => opt.isSome;
+    
+    /// <summary>
+    /// Required by |.
+    /// <para/>
+    /// http://stackoverflow.com/questions/686424/what-are-true-and-false-operators-in-c#comment43525525_686473
+    /// <para/>
+    /// <![CDATA[
+    /// The only situation where operator false matters, seems to be if MyClass also overloads
+    /// the operator &, in a suitable way. So you can say MyClass conj = GetMyClass1() & GetMyClass2();.
+    /// Then with operator false you can short-circuit and say
+    /// `MyClass conj = GetMyClass1() && GetMyClass2();`, using && instead of &. That will only
+    /// evaluate the second operand if the first one is not "false".
+    /// ]]>
+    /// </summary>
+    public static bool operator false(Option<A> opt) => opt.isNone;
+    
+    /// <summary>Allows doing <code>var otherOption = option1 || option2</code></summary>
+    public static Option<A> operator |(Option<A> o1, Option<A> o2) => o1 ? o1 : o2;
+  
+    #region Equality
+
+    public bool Equals(Option<A> other) {
+      if (isSome == other.isSome) {
+        return isNone || EqualityComparer<A>.Default.Equals(__unsafeGet, other.__unsafeGet);
+      }
+      else return false;
+    }
+
+    public override bool Equals(object obj) => obj is Option<A> other && Equals(other);
+
+    public override int GetHashCode() {
+      unchecked {
+        return (EqualityComparer<A>.Default.GetHashCode(__unsafeGet) * 397) ^ isSome.GetHashCode();
+      }
+    }
+
+    public static bool operator ==(Option<A> left, Option<A> right) => left.Equals(right);
+    public static bool operator !=(Option<A> left, Option<A> right) => !left.Equals(right);
+
+    #endregion
+  }
+
+  public static class Option {
+    /// <summary>Creates a `Some` variant of the <see cref="Option{A}"/>.</summary>
+    public static Option<A> Some<A>(A a) => new Option<A>(a);
+  }
+
+  public readonly struct None {
+    /// <summary>
+    /// Allows you to easily return a `None` case for <see cref="Option{A}"/>.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// Option<int> find() {
+    ///   // ...
+    ///   if (someCondition()) return None._;
+    ///   // ...
+    /// }
+    /// ]]></code></example>
+    public static readonly None _ = new None();
+  }
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Option.cs.meta b/Editor/Scripts/Utilities/Option.cs.meta
new file mode 100644
index 0000000..4c1ac2b
--- /dev/null
+++ b/Editor/Scripts/Utilities/Option.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: aef18b1706354532869663b5780f68e0
+timeCreated: 1673812872
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/PInt.cs b/Editor/Scripts/Utilities/PInt.cs
new file mode 100644
index 0000000..b6b98f5
--- /dev/null
+++ b/Editor/Scripts/Utilities/PInt.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using static HeapExplorer.Utilities.Option;
+
+namespace HeapExplorer.Utilities {
+  /// <summary>
+  /// Positive integer.
+  /// <para/>
+  /// Similar to <see cref="uint"/> but is still backed by an <see cref="int"/> so the math behaves the same.
+  /// </summary>
+  public readonly struct PInt : IEquatable<PInt> {
+    public readonly int asInt;
+
+    PInt(int asInt) {
+      this.asInt = asInt;
+    }
+
+    public static PInt _0 => new PInt(0);
+    public static PInt _1 => new PInt(1);
+
+    public override string ToString() => asInt.ToString();
+
+    /// <summary>Safely casts the <see cref="int"/> value to <see cref="uint"/>.</summary>
+    public uint asUInt {
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      get => (uint) asInt;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static PInt operator +(PInt p1, PInt p2) => new PInt(p1.asInt + p2.asInt);
+    
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static PInt operator ++(PInt p1) => new PInt(p1.asInt + 1);
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static implicit operator int(PInt pInt) => pInt.asInt;
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static implicit operator long(PInt pInt) => pInt.asInt;
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static implicit operator uint(PInt pInt) => pInt.asUInt;
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static implicit operator ulong(PInt pInt) => pInt.asUInt;
+  
+    /// <summary>Creates a value, throwing if the supplied <see cref="int"/> is negative.</summary>
+    public static PInt createOrThrow(int value) {
+      if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), value, "value can't be negative");
+      return new PInt(value);
+    }
+  
+    /// <summary>Creates a value, returning `None` if the supplied <see cref="int"/> is negative.</summary>
+    public static Option<PInt> create(int value) => 
+      value < 0 ? new Option<PInt>() : Some(new PInt(value));
+
+    #region Equality
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public bool Equals(PInt other) => asInt == other.asInt;
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public override bool Equals(object obj) => obj is PInt other && Equals(other);
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public override int GetHashCode() => asInt;
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool operator ==(PInt left, PInt right) => left.Equals(right);
+  
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static bool operator !=(PInt left, PInt right) => !left.Equals(right);
+
+    #endregion
+  }
+
+  public static class PIntExts {
+    /// <summary><see cref="Array.Length"/> as <see cref="PInt"/>.</summary>
+    public static PInt LengthP<A>(this A[] array) => PInt.createOrThrow(array.Length);
+    
+    /// <summary><see cref="ICollection{T}.Count"/> as <see cref="PInt"/>.</summary>
+    public static PInt CountP<A>(this ICollection<A> list) => PInt.createOrThrow(list.Count);
+
+    public static PInt ReadPInt(this BinaryReader reader) => PInt.createOrThrow(reader.ReadInt32());
+  }
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/PInt.cs.meta b/Editor/Scripts/Utilities/PInt.cs.meta
new file mode 100644
index 0000000..12c9771
--- /dev/null
+++ b/Editor/Scripts/Utilities/PInt.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9487bc47daa1450e90b6725dede08234
+timeCreated: 1673885442
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Utils.cs b/Editor/Scripts/Utilities/Utils.cs
new file mode 100644
index 0000000..af2237d
--- /dev/null
+++ b/Editor/Scripts/Utilities/Utils.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using UnityEngine;
+using static HeapExplorer.Utilities.Option;
+
+namespace HeapExplorer.Utilities {
+  public static class Utils {
+    public static Option<A> zeroAddressAccessError<A>(string paramName) {
+      // throw new ArgumentOutOfRangeException(paramName, 0, "address 0 should not be accessed!");
+      Debug.LogError("address 0 should not be accessed!");
+      return new Option<A>();
+    }
+
+    /// <summary>
+    /// Returns the <see cref="uint"/> value as <see cref="int"/>, clamping it if it doesn't fit. 
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static int ToIntClamped(this uint value, bool doLog = true) {
+      if (value > int.MaxValue) {
+        if (doLog)
+          Debug.LogWarningFormat(
+            "HeapExplorer: clamping uint value {0} to int value {1}, this shouldn't happen.",
+            value, int.MaxValue
+          );
+        return int.MaxValue;
+      }
+
+      return (int) value;
+    }
+
+    /// <summary>
+    /// Returns the <see cref="ulong"/> value as <see cref="long"/>, clamping it if it doesn't fit. 
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static long ToLongClamped(this ulong value, bool doLog = true) {
+      if (value > long.MaxValue) {
+        if (doLog)
+          Debug.LogWarningFormat(
+            "HeapExplorer: clamping ulong value {0} to long value {1}, this shouldn't happen.",
+            value, long.MaxValue
+          );
+        return long.MaxValue;
+      }
+
+      return (long) value;
+    }
+
+    /// <summary>
+    /// Returns the <see cref="int"/> value as <see cref="uint"/>, clamping it if it is less than 0. 
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static uint ToUIntClamped(this int value, bool doLog = true) {
+      if (value < 0) {
+        if (doLog)
+          Debug.LogWarningFormat(
+            "HeapExplorer: clamping int value {0} to uint 0, this shouldn't happen.", value
+          );
+        return 0;
+      }
+
+      return (uint) value;
+    }
+
+    /// <summary>
+    /// Returns the <see cref="long"/> value as <see cref="ulong"/>, clamping it if it is less than 0. 
+    /// </summary>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static ulong ToULongClamped(this long value, bool doLog = true) {
+      if (value < 0) {
+        if (doLog)
+          Debug.LogWarningFormat(
+            "HeapExplorer: clamping long value {0} to ulong 0, this shouldn't happen.", value
+          );
+        return 0;
+      }
+
+      return (ulong) value;
+    }
+
+    /// <summary>
+    /// <see cref="IDictionary{TKey,TValue}.TryGetValue"/> but returns an <see cref="Option{A}"/> instead.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// var maybeList = m_ConnectionsFrom.get(key);
+    /// ]]></code></example>
+    public static Option<V> get<K, V>(this IDictionary<K, V> dictionary, K key) => 
+      dictionary.TryGetValue(key, out var value) ? Some(value) : new Option<V>();
+
+    /// <summary>
+    /// Gets a value by the key from the dictionary. If a key is not stored yet it is created using the
+    /// <see cref="ifMissing"/> function and then stored in the dictionary. 
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// var list = m_ConnectionsFrom.getOrUpdate(key, _ => new List<int>());
+    /// ]]></code></example>
+    public static V getOrUpdate<K, V>(this IDictionary<K, V> dictionary, K key, Func<K, V> ifMissing) {
+      if (!dictionary.TryGetValue(key, out var value)) {
+        value = dictionary[key] = ifMissing(key);
+      }
+
+      return value;
+    }
+
+    /// <summary>
+    /// Gets a value by the key from the dictionary. If a key is not stored yet it is created using the
+    /// <see cref="ifMissing"/> function and then stored in the dictionary. 
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// var list = m_ConnectionsFrom.getOrUpdate(key, data, (_, data) => new List<int>());
+    /// ]]></code></example>
+    public static V getOrUpdate<K, Data, V>(this IDictionary<K, V> dictionary, K key, Data data, Func<K, Data, V> ifMissing) {
+      if (!dictionary.TryGetValue(key, out var value)) {
+        value = dictionary[key] = ifMissing(key, data);
+      }
+
+      return value;
+    }
+
+    /// <summary>
+    /// Gets a value by the key from the dictionary. If a key is not stored yet it returns <see cref="ifMissing"/>
+    /// instead.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// var list = m_ConnectionsFrom.getOrUpdate(key, _ => new List<int>());
+    /// ]]></code></example>
+    public static V getOrElse<K, V>(this IDictionary<K, V> dictionary, K key, V ifMissing) => 
+      dictionary.TryGetValue(key, out var value) ? value : ifMissing;
+
+    /// <summary>
+    /// Groups entries into groups of <see cref="groupSize"/>.
+    /// </summary>
+    /// <example><code><![CDATA[
+    /// [1, 2, 3, 4, 5].groupedIn(2) == [[1, 2], [3, 4], [5]]
+    /// ]]></code></example>
+    public static IEnumerable<List<A>> groupedIn<A>(this IEnumerable<A> enumerable, PInt groupSize) {
+      var group = new List<A>(groupSize);
+      foreach (var a in enumerable) {
+        // Yield if a group is full.
+        if (group.Count == groupSize) {
+          yield return group;
+          group = new List<A>(groupSize);
+        }
+        
+        group.Add(a);
+      }
+
+      // Yield the last group, which may not be full.
+      yield return group;
+    }
+  }
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Utils.cs.meta b/Editor/Scripts/Utilities/Utils.cs.meta
new file mode 100644
index 0000000..5964f42
--- /dev/null
+++ b/Editor/Scripts/Utilities/Utils.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6760d31531ea41758eadc35d47ae19da
+timeCreated: 1673531261
\ No newline at end of file
diff --git a/Editor/Scripts/ViewConsts.cs b/Editor/Scripts/ViewConsts.cs
new file mode 100644
index 0000000..61127d3
--- /dev/null
+++ b/Editor/Scripts/ViewConsts.cs
@@ -0,0 +1,11 @@
+namespace HeapExplorer {
+  public static class ViewConsts {
+    public const string COLUMN_CPP_NAME = "C++ Name";
+    public const string COLUMN_CPP_NAME_DESCRIPTION =
+      "If the C# object has a C++ counterpart, display its C++ object name in this column.";
+
+    public const string COLUMN_SOURCE_FIELD = "Source Field";
+    public const string COLUMN_SOURCE_FIELD_DESCRIPTION =
+      "Field name in the referencing object, if that is available.";
+  }
+}
diff --git a/Editor/Scripts/ViewConsts.cs.meta b/Editor/Scripts/ViewConsts.cs.meta
new file mode 100644
index 0000000..7eb1a77
--- /dev/null
+++ b/Editor/Scripts/ViewConsts.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c9a7659dc06547bdada8d673ffb1f5c9
+timeCreated: 1674040708
\ No newline at end of file
diff --git a/Editor/Scripts/WelcomeView/WelcomeView.cs b/Editor/Scripts/WelcomeView/WelcomeView.cs
index 4bab770..18cf9c8 100644
--- a/Editor/Scripts/WelcomeView/WelcomeView.cs
+++ b/Editor/Scripts/WelcomeView/WelcomeView.cs
@@ -39,7 +39,7 @@ public override void OnGUI()
             base.OnGUI();
 
             GUILayout.Space(4);
-            GUILayout.Label(string.Format("{0} {1} for Unity", HeGlobals.k_Title, HeGlobals.k_Version), HeEditorStyles.heading1);
+            GUILayout.Label($"{HeGlobals.k_Title} {HeGlobals.k_Version} for Unity", HeEditorStyles.heading1);
             GUILayout.Label("Created by Peter Schraut (www.console-dev.de)");
             GUILayout.Space(16);
 
@@ -129,7 +129,7 @@ void DrawMRU()
                         {
                             var path = HeMruFiles.GetPath(n);
 
-                            GUILayout.Label(string.Format("{0,2:##}", n + 1), GUILayout.Width(20));
+                            GUILayout.Label($"{n + 1,2:##}", GUILayout.Width(20));
 
                             if (GUILayout.Button(new GUIContent(HeEditorStyles.deleteImage, "Remove entry from list"), HeEditorStyles.iconStyle, GUILayout.Width(16), GUILayout.Height(16)))
                             {
@@ -137,7 +137,7 @@ void DrawMRU()
                                 break;
                             }
 
-                            if (GUILayout.Button(new GUIContent(string.Format("{0}", path)), HeEditorStyles.hyperlink))
+                            if (GUILayout.Button(new GUIContent($"{path}"), HeEditorStyles.hyperlink))
                             {
                                 window.LoadFromFile(path);
                             }

From 53954da672f3ac8468abdc10300fc8d08a0f4524 Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Sun, 22 Jan 2023 10:25:00 +0200
Subject: [PATCH 2/9] Fix reading old data files

The old data files contain fields serialized with `-1` offsets. We know these are invalid and should be skipped during reading.
---
 .../Scripts/PackedTypes/PackedConnection.cs   |  4 +-
 .../Scripts/PackedTypes/PackedManagedField.cs | 43 +++++++++++++------
 .../Scripts/PackedTypes/PackedManagedType.cs  |  3 +-
 3 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/Editor/Scripts/PackedTypes/PackedConnection.cs b/Editor/Scripts/PackedTypes/PackedConnection.cs
index 4339f0a..1b41f86 100644
--- a/Editor/Scripts/PackedTypes/PackedConnection.cs
+++ b/Editor/Scripts/PackedTypes/PackedConnection.cs
@@ -137,7 +137,9 @@ public static void Read(System.IO.BinaryReader reader, out PackedConnection[] va
                     Option<PackedManagedField> field;
                     if (version >= 3) {
                         var hasField = reader.ReadBoolean();
-                        field = hasField ? Option.Some(PackedManagedField.Read(reader)) : None._;
+                        field = hasField
+                            ? Option.Some(PackedManagedField.Read(reader).getOrThrow("this should never fail")) 
+                            : None._;
                     }
                     else {
                         field = None._;
diff --git a/Editor/Scripts/PackedTypes/PackedManagedField.cs b/Editor/Scripts/PackedTypes/PackedManagedField.cs
index 0472af9..d048172 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedField.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedField.cs
@@ -3,7 +3,9 @@
 // https://github.com/pschraut/UnityHeapExplorer/
 //
 using System;
+using System.Collections.Generic;
 using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -62,34 +64,47 @@ public static void Write(System.IO.BinaryWriter writer, in PackedManagedField va
             writer.Write(value.isStatic);
         }
 
-        public static void Read(System.IO.BinaryReader reader, out PackedManagedField[] value)
+        public static void Read(System.IO.BinaryReader reader, out PackedManagedField[] values)
         {
-            value = new PackedManagedField[0];
-
             var version = reader.ReadInt32();
             if (version >= 1)
             {
                 var length = reader.ReadInt32();
-                value = new PackedManagedField[length];
+                var list = new List<PackedManagedField>(capacity: length);
 
-                for (int n = 0, nend = value.Length; n < nend; ++n) {
-                    value[n] = Read(reader);
+                for (var n = 0; n < length; ++n) {
+                    if (Read(reader).valueOut(out var value)) list.Add(value);
                 }
+
+                values = list.ToArray();
+            }
+            else {
+                throw new Exception($"Unknown {nameof(PackedManagedField)} version {version}.");
             }
         }
 
-        public static PackedManagedField Read(System.IO.BinaryReader reader) {
+        /// <returns>`None` if it's incompatible data from an old format.</returns>
+        public static Option<PackedManagedField> Read(System.IO.BinaryReader reader) {
             var name = reader.ReadString();
-            var offset = PInt.createOrThrow(reader.ReadInt32());
+            var rawOffset = reader.ReadInt32();
             var managedTypesArrayIndex = PInt.createOrThrow(reader.ReadInt32());
             var isStatic = reader.ReadBoolean();
-            return new PackedManagedField(
-                name: name,
-                offset: offset,
-                managedTypesArrayIndex: managedTypesArrayIndex,
-                isStatic: isStatic
-            );
+            if (isThreadStatic(isStatic, rawOffset)) return None._;
+            else {
+                var offset = PInt.createOrThrow(rawOffset);
+                return Some(new PackedManagedField(
+                    name: name,
+                    offset: offset,
+                    managedTypesArrayIndex: managedTypesArrayIndex,
+                    isStatic: isStatic
+                ));
+            }
         }
+        
+        /// <summary>
+        /// Offset will be -1 if the field is a static field with `[ThreadStatic]` attached to it.
+        /// </summary>
+        public static bool isThreadStatic(bool isStatic, int rawOffset) => isStatic && rawOffset == -1;
 
         public override string ToString()
         {
diff --git a/Editor/Scripts/PackedTypes/PackedManagedType.cs b/Editor/Scripts/PackedTypes/PackedManagedType.cs
index ebbdc54..fec8dc9 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedType.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedType.cs
@@ -545,8 +545,7 @@ UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
                 .Select(tpl => tpl.idx)
                 .ToArray();
             if (importFailureIndexes.Length > 0) {
-                // Offset will be -1 if the field is a static field with `[ThreadStatic]` attached to it.
-                bool isThreadStatic(int idx) => fieldStatic[idx] && fieldOffset[idx] == -1;
+                bool isThreadStatic(int idx) => PackedManagedField.isThreadStatic(fieldStatic[idx], fieldOffset[idx]);
                 
                 var threadStatics = importFailureIndexes.Where(isThreadStatic).ToArray();
                 reportFailures(

From d96fbe107b88a3ed88035e15bd8e85a5d0fda29d Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Sun, 22 Jan 2023 10:33:36 +0200
Subject: [PATCH 3/9] Fix tests compilation.

---
 Editor/Scripts/Utilities/Option.cs |  2 ++
 Tests/Editor/Test_Editor.cs        | 27 +++++++++++++++++----------
 2 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/Editor/Scripts/Utilities/Option.cs b/Editor/Scripts/Utilities/Option.cs
index 8418255..9ec3ef9 100644
--- a/Editor/Scripts/Utilities/Option.cs
+++ b/Editor/Scripts/Utilities/Option.cs
@@ -24,6 +24,8 @@ public Option(A value) {
       isSome = true;
     }
 
+    public static Option<A> None => new Option<A>();
+
     public override string ToString() => isSome ? $"Some({__unsafeGet})" : "None";
 
     /// <example><code><![CDATA[
diff --git a/Tests/Editor/Test_Editor.cs b/Tests/Editor/Test_Editor.cs
index b894bac..a07541f 100644
--- a/Tests/Editor/Test_Editor.cs
+++ b/Tests/Editor/Test_Editor.cs
@@ -11,6 +11,8 @@
 using NUnit.Framework;
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
+using static HeapExplorer.Utilities.Option;
 
 namespace HeapExplorer
 {
@@ -60,27 +62,29 @@ public IEnumerator Capture()
         void RunTest()
         {
             // Find the test type
-            RichManagedType classType = RichManagedType.invalid;
+            var maybeClassType = Option<RichManagedType>.None;
             foreach (var type in m_snapshot.managedTypes)
             {
                 if (type.name != "HeapExplorer.Test_Editor")
                     continue;
 
-                classType = new RichManagedType(m_snapshot, type.managedTypesArrayIndex);
+                maybeClassType = Some(new RichManagedType(m_snapshot, type.managedTypesArrayIndex));
                 break;
             }
-            Assert.IsTrue(classType.isValid);
+            Assert.IsTrue(maybeClassType.isSome);
+            var classType = maybeClassType.getOrThrow();
 
             // Find the test object instance
-            RichManagedObject managedObject = RichManagedObject.invalid;
+            var maybeManagedObject = Option<RichManagedObject>.None;
             foreach (var obj in m_snapshot.managedObjects)
             {
                 if (obj.managedTypesArrayIndex != classType.packed.managedTypesArrayIndex)
                     continue;
 
-                managedObject = new RichManagedObject(m_snapshot, obj.managedObjectsArrayIndex);
+                maybeManagedObject = Some(new RichManagedObject(m_snapshot, obj.managedObjectsArrayIndex));
             }
-            Assert.IsTrue(managedObject.isValid);
+            Assert.IsTrue(maybeManagedObject.isSome);
+            var managedObject = maybeManagedObject.getOrThrow();
 
             AssertInt("m_intOne", 1, managedObject);
             AssertInt("m_intTwo", 2, managedObject);
@@ -188,7 +192,9 @@ void AssertMatrix4x4(string fieldName, Matrix4x4 value, RichManagedObject manage
             {
                 for (var x = 0; x < 4; ++x)
                 {
-                    matrix[y, x] = memory.ReadSingle((uint)field.offset + (uint)(sizeOfSingle * element) + managedObject.address);
+                    matrix[y, x] = 
+                        memory.ReadSingle((uint)field.offset + (uint)(sizeOfSingle * element) + managedObject.address)
+                            .getOrThrow();
                     element++;
                 }
             }
@@ -201,14 +207,15 @@ void AssertDateTime(string fieldName, DateTime value, RichManagedObject managedO
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            var ticks = memory.ReadInt64(0 + (uint)field.offset + managedObject.address);
+            var ticks = memory.ReadInt64(0 + (uint)field.offset + managedObject.address).getOrThrow();
             Assert.AreEqual(value, new DateTime(ticks));
         }
 
         void OnSnapshotReceived(string path, bool captureResult)
         {
-            var args = new MemorySnapshotProcessingArgs();
-            args.source = UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path);
+            var args = new MemorySnapshotProcessingArgs(
+                UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot.Load(path)
+            );
 
             m_snapshot = PackedMemorySnapshot.FromMemoryProfiler(args);
         }

From 4fecbdc2c7a15daa3f061be48f3868d2a5bbdc24 Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Sun, 22 Jan 2023 10:33:44 +0200
Subject: [PATCH 4/9] Mark field as readonly.

---
 Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
index 07b6e55..c32791b 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
@@ -255,7 +255,7 @@ public bool isOrContainsReferenceType(int managedTypeIndex) =>
     // Specifies how an Unity MemorySnapshot must be converted to HeapExplorer format.
     public class MemorySnapshotProcessingArgs {
         public readonly UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot source;
-        public PackedMemorySnapshot.UpdateUnityUI maybeUpdateUI;
+        public readonly PackedMemorySnapshot.UpdateUnityUI maybeUpdateUI;
 
         public MemorySnapshotProcessingArgs(
             UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot source, 

From d0636594c21cd248222f7348254732105704cb02 Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Fri, 3 Feb 2023 12:05:19 +0200
Subject: [PATCH 5/9] Fixed an exception when taking a snapshot because
 `arrayRank` was being read for non-array types.

Now instead of having `bool isArray` and `PInt arrayRank` we have one field `Option<PInt> arrayRank` which makes sure we are not capable of representing the illegal state.
---
 Editor/Scripts/MemoryReader.cs                | 21 ++++----
 .../PackedTypes/PackedManagedObjectCrawler.cs | 15 +++---
 .../Scripts/PackedTypes/PackedManagedType.cs  | 48 +++++++++++--------
 .../PropertyGrid/ArrayPropertyGridItem.cs     | 38 ++++++++-------
 4 files changed, 65 insertions(+), 57 deletions(-)

diff --git a/Editor/Scripts/MemoryReader.cs b/Editor/Scripts/MemoryReader.cs
index b410cd9..63542d0 100644
--- a/Editor/Scripts/MemoryReader.cs
+++ b/Editor/Scripts/MemoryReader.cs
@@ -371,15 +371,14 @@ public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescript
         {
             // System.Array
             // Do not display its pointer-size, but the actual size of its content.
-            if (typeDescription.isArray)
-            {
+            {if (typeDescription.arrayRank.valueOut(out var arrayRank)) {
                 if (
                     !typeDescription.baseOrElementTypeIndex.valueOut(out var baseOrElementTypeIndex) 
                     || baseOrElementTypeIndex >= m_Snapshot.managedTypes.Length
                 ) {
                     var details = "";
-                    details = "arrayRank=" + typeDescription.arrayRank + ", " +
-                        "isArray=" + typeDescription.isArray + ", " +
+                    details = 
+                        "arrayRank=" + arrayRank + ", " +
                         "typeInfoAddress=" + typeDescription.typeInfoAddress.ToString("X") + ", " +
                         "address=" + address.ToString("X") + ", " +
                         "memoryreader=" + GetType().Name + ", " +
@@ -389,14 +388,14 @@ public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescript
                     return Some(PInt._1);
                 }
 
-                if (!ReadArrayLength(address, typeDescription).valueOut(out var arrayLength)) return None._;
+                if (!ReadArrayLength(address, arrayRank).valueOut(out var arrayLength)) return None._;
                 var elementType = m_Snapshot.managedTypes[baseOrElementTypeIndex];
                 var elementSize = elementType.isValueType ? elementType.size.asInt : m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes();
 
                 var size = m_Snapshot.virtualMachineInformation.arrayHeaderSize.asInt;
                 size += elementSize * arrayLength;
                 return Some(PInt.createOrThrow(size));
-            }
+            }}
 
             // System.String
             if (typeDescription.managedTypesArrayIndex == m_Snapshot.coreTypes.systemString)
@@ -413,7 +412,7 @@ public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescript
             return Some(typeDescription.size);
         }
 
-        public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType)
+        public Option<int> ReadArrayLength(ulong address, PInt arrayRank)
         {
             var vm = m_Snapshot.virtualMachineInformation;
 
@@ -422,7 +421,7 @@ public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType)
                 return ReadPointer(address + vm.arraySizeOffsetInHeader).map(v => (int)v);
 
             int length = 1;
-            for (int i = 0; i != arrayType.arrayRank; i++)
+            for (int i = 0; i != arrayRank; i++)
             {
                 var ptr = bounds + (ulong)(i * vm.pointerSize.sizeInBytes());
                 if (!ReadPointer(ptr).valueOut(out var value)) return None._;
@@ -431,11 +430,11 @@ public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType)
             return Some(length);
         }
 
-        public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType, int dimension)
+        public Option<int> ReadArrayLength(ulong address, PackedManagedType arrayType, PInt arrayRank, int dimension)
         {
-            if (dimension >= arrayType.arrayRank) {
+            if (dimension >= arrayRank) {
                 Debug.LogError(
-                    $"Trying to read dimension {dimension} while the array rank is {arrayType.arrayRank} for array at "
+                    $"Trying to read dimension {dimension} while the array rank is {arrayRank} for array at "
                     + $"address {address:X} of type '{arrayType.name}'. Returning `None`."
                 );
                 return None._;
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
index ca1fc42..c2dae3e 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
@@ -171,15 +171,14 @@ void CrawlManagedObjects(List<PackedManagedObject> managedObjects)
                     {if (obj.obj.staticBytes.valueOut(out var staticBytes))
                         memoryReader = new StaticMemoryReader(m_Snapshot, staticBytes);}
 
-                    if (type.isArray) {
-                        handleArrayType(obj, type, memoryReader);
+                    {if (type.arrayRank.valueOut(out var arrayRank)) {
+                        handleArrayType(obj, type, arrayRank, memoryReader);
                         break;
-                    }
-                    else {
+                    } else {
                         var shouldBreak = handleNonArrayType(obj.obj, type, memoryReader, typeIndex: typeIndex);
                         if (shouldBreak) break;
                         else maybeTypeIndex = type.baseOrElementTypeIndex;
-                    }
+                    }}
                 }}
             }
 
@@ -188,7 +187,7 @@ void CrawlManagedObjects(List<PackedManagedObject> managedObjects)
             );
             
             void handleArrayType(
-                PendingObject pendingObject, PackedManagedType type, AbstractMemoryReader memoryReader
+                PendingObject pendingObject, PackedManagedType type, PInt arrayRank, AbstractMemoryReader memoryReader
             ) {
                 var mo = pendingObject.obj;
                 if (
@@ -213,7 +212,7 @@ void handleArrayType(
 
                 int dim0Length;
                 if (mo.address > 0) {
-                    if (!memoryReader.ReadArrayLength(mo.address, type).valueOut(out dim0Length)) {
+                    if (!memoryReader.ReadArrayLength(mo.address, arrayRank).valueOut(out dim0Length)) {
                         m_Snapshot.Error($"Can't determine array length for array at {mo.address:X}");
                         return;
                     }
@@ -224,7 +223,7 @@ void handleArrayType(
                 //if (dim0Length > 1024 * 1024)
                 if (dim0Length > (32*1024) * (32*1024)) {
                     m_Snapshot.Error(
-                        $"HeapExplorer: Array (rank={type.arrayRank}) found at address '{mo.address:X} with "
+                        $"HeapExplorer: Array (rank={arrayRank}) found at address '{mo.address:X} with "
                         + $"'{dim0Length}' elements, that doesn't seem right."
                     );
                     return;
diff --git a/Editor/Scripts/PackedTypes/PackedManagedType.cs b/Editor/Scripts/PackedTypes/PackedManagedType.cs
index fec8dc9..d9eb398 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedType.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedType.cs
@@ -29,14 +29,14 @@ public class PackedManagedType : PackedMemorySnapshot.TypeForSubclassSearch
         /// <summary>Is this type a reference type? (if it's not a reference type, it's a value type)</summary>
         public bool isReferenceType => !isValueType;
 
-        /// <summary>Is this type an array?</summary>
-        public readonly bool isArray;
-
         /// <summary>
-        /// If this is an arrayType, this will return the rank of the array. (1 for a 1-dimensional array, 2 for a
-        /// 2-dimensional array, etc)
+        /// `None` if this type is not an array, `Some(arrayRank)` if it is an array.
+        /// <para/>
+        /// The rank of the array is 1 for a 1-dimensional array, 2 for a 2-dimensional array, etc.
         /// </summary>
-        public readonly PInt arrayRank;
+        public readonly Option<PInt> arrayRank;
+
+        public bool isArray => arrayRank.isSome;
 
         /// <summary>
         /// Name of this type.
@@ -129,12 +129,11 @@ public class PackedManagedType : PackedMemorySnapshot.TypeForSubclassSearch
         public bool containsFieldOfReferenceTypeInInheritanceChain;
 
         public PackedManagedType(
-            bool isValueType, bool isArray, PInt arrayRank, string name, string assembly, PackedManagedField[] fields, 
+            bool isValueType, Option<PInt> arrayRank, string name, string assembly, PackedManagedField[] fields, 
             byte[] staticFieldBytes, Option<PInt> baseOrElementTypeIndex, PInt size, ulong typeInfoAddress, 
             PInt managedTypesArrayIndex
         ) {
             this.isValueType = isValueType;
-            this.isArray = isArray;
             this.arrayRank = arrayRank;
             this.name = name;
             this.assembly = assembly;
@@ -357,8 +356,8 @@ public static void Write(System.IO.BinaryWriter writer, PackedManagedType[] valu
             for (int n = 0, nend = value.Length; n < nend; ++n)
             {
                 writer.Write(value[n].isValueType);
-                writer.Write(value[n].isArray);
-                writer.Write(value[n].arrayRank);
+                writer.Write(value[n].arrayRank.isSome);
+                writer.Write(value[n].arrayRank.fold(0, _ => _.asInt));
                 writer.Write(value[n].name);
                 writer.Write(value[n].assembly);
 
@@ -412,8 +411,7 @@ public static void Read(System.IO.BinaryReader reader, out PackedManagedType[] v
 
                     value[n] = new PackedManagedType(
                         isValueType: isValueType,
-                        isArray: isArray,
-                        arrayRank: PInt.createOrThrow(arrayRank),
+                        arrayRank: isArray ? Some(PInt.createOrThrow(arrayRank)) : None._,
                         name: name,
                         assembly: assembly,
                         staticFieldBytes: staticFieldBytes,
@@ -497,7 +495,7 @@ UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
             // A cache for the temporary fields as we don't know how many of them are valid.
             var fieldsList = new List<PackedManagedField>();
             for (int n = 0, nend = managedTypes.Length; n < nend; ++n) {
-                var baseOrElementTypeIndex = sourceBaseOrElementTypeIndex[n];
+                var rawBaseOrElementTypeIndex = sourceBaseOrElementTypeIndex[n];
                 var sourceFieldIndicesForValue = sourceFieldIndices[n];
 
                 // Assign fields.
@@ -521,19 +519,27 @@ UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
                 var name = sourceName[n];
                 // namespace-less types have a preceding dot, which we remove here
                 if (name != null && name.Length > 0 && name[0] == '.') name = name.Substring(1);
+
+                var isValueType = (sourceFlags[n] & TypeFlags.kValueType) != 0;
+                var isArray =
+                    (sourceFlags[n] & TypeFlags.kArray) != 0
+                        ? Some(PInt.createOrThrow((int) (sourceFlags[n] & TypeFlags.kArrayRankMask) >> 16))
+                        : None._;
+                var baseOrElementTypeIndex =
+                    rawBaseOrElementTypeIndex == -1 ? None._ : Some(PInt.createOrThrow(rawBaseOrElementTypeIndex));
+                var size = PInt.createOrThrow(sourceSize[n]);
+                var managedTypesArrayIndex = PInt.createOrThrow(sourceTypeIndex[n]);
                 
                 managedTypes[n] = new PackedManagedType(
-                    isValueType: (sourceFlags[n] & TypeFlags.kValueType) != 0,
-                    isArray: (sourceFlags[n] & TypeFlags.kArray) != 0,
-                    arrayRank: PInt.createOrThrow((int)(sourceFlags[n] & TypeFlags.kArrayRankMask)>>16),
+                    isValueType: isValueType,
+                    arrayRank: isArray,
                     name: name,
                     assembly: sourceAssembly[n],
                     staticFieldBytes: sourceStaticFieldBytes[n],
-                    baseOrElementTypeIndex: 
-                        baseOrElementTypeIndex == -1 ? None._ : Some(PInt.createOrThrow(baseOrElementTypeIndex)),
-                    size: PInt.createOrThrow(sourceSize[n]),
+                    baseOrElementTypeIndex: baseOrElementTypeIndex,
+                    size: size,
                     typeInfoAddress: sourceTypeInfoAddress[n],
-                    managedTypesArrayIndex: PInt.createOrThrow(sourceTypeIndex[n]),
+                    managedTypesArrayIndex: managedTypesArrayIndex,
                     fields: fields
                 );
             }
@@ -588,7 +594,7 @@ void reportFailures(string description, int[] failureIndexes) {
 
         public override string ToString()
         {
-            var text = $"name: {name}, isValueType: {isValueType}, isArray: {isArray}, size: {size}";
+            var text = $"name: {name}, isValueType: {isValueType}, isArray: {arrayRank}, size: {size}";
             return text;
         }
     }
diff --git a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
index 8e11635..bdfb850 100644
--- a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
@@ -4,6 +4,7 @@
 //
 using System.Collections;
 using System.Collections.Generic;
+using HeapExplorer.Utilities;
 using UnityEngine;
 using UnityEditor.IMGUI.Controls;
 using UnityEditor;
@@ -13,6 +14,8 @@ namespace HeapExplorer
     public class ArrayPropertyGridItem : PropertyGridItem
     {
         const int k_MaxItemsPerChunk = 1024*32;
+        
+        PInt arrayRank => type.arrayRank.getOrThrow("this should be only invoked for arrays!");
 
         public ArrayPropertyGridItem(PropertyGridControl owner, PackedMemorySnapshot snapshot, System.UInt64 address, AbstractMemoryReader memoryReader)
             : base(owner, snapshot, address, memoryReader)
@@ -23,7 +26,8 @@ protected override void OnInitialize()
         {
             var pointer = m_MemoryReader.ReadPointer(address).getOrThrow();
             var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
-            var dim0Length = address > 0 ? m_MemoryReader.ReadArrayLength(address, type, 0).getOrThrow() : 0;
+            var arrayRank = this.arrayRank;
+            var dim0Length = address > 0 ? m_MemoryReader.ReadArrayLength(address, type, arrayRank, 0).getOrThrow() : 0;
 
             displayType = type.name;
             displayValue = "null";
@@ -38,21 +42,21 @@ protected override void OnInitialize()
                 var isJagged = type.name.IndexOf("[][]") != -1;
                 if (isJagged)
                 {
-                    for (var n = 0; n < type.arrayRank; ++n)
+                    for (var n = 0; n < arrayRank; ++n)
                     {
-                        var length = m_MemoryReader.ReadArrayLength(address, type, n);
+                        var length = m_MemoryReader.ReadArrayLength(address, type, arrayRank, n);
                         displayValue += $"[{length}]";
                     }
                 }
                 else
                 {
                     displayValue += "[";
-                    for (var n = 0; n < type.arrayRank; ++n)
+                    for (var n = 0; n < arrayRank; ++n)
                     {
-                        var length = m_MemoryReader.ReadArrayLength(address, type, n);
+                        var length = m_MemoryReader.ReadArrayLength(address, type, arrayRank, n);
 
                         displayValue += $"{length}";
-                        if (n + 1 < type.arrayRank)
+                        if (n + 1 < arrayRank)
                             displayValue += ",";
                     }
                     displayValue += "]";
@@ -62,16 +66,16 @@ protected override void OnInitialize()
 
         protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add)
         {
-            if (type.arrayRank == 1)
+            if (arrayRank == 1)
                 BuildOneDimArray(add);
 
-            if (type.arrayRank > 1)
+            if (arrayRank > 1)
                 BuildMultiDimArray(add);
         }
 
         void BuildOneDimArray(System.Action<BuildChildrenArgs> add)
         {
-            var arrayLength = m_MemoryReader.ReadArrayLength(address, type).getOrThrow();
+            var arrayLength = m_MemoryReader.ReadArrayLength(address, arrayRank).getOrThrow();
             var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
 
             for (var n = 0; n < Mathf.Min(arrayLength, k_MaxItemsPerChunk); ++n)
@@ -87,9 +91,9 @@ void BuildOneDimArray(System.Action<BuildChildrenArgs> add)
             }
         }
 
-        void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
-        {
-            var arrayLength = m_MemoryReader.ReadArrayLength(address, type).getOrThrow();
+        void BuildMultiDimArray(System.Action<BuildChildrenArgs> add) {
+            var arrayRank = this.arrayRank;
+            var arrayLength = m_MemoryReader.ReadArrayLength(address, arrayRank).getOrThrow();
             var elementType = m_Snapshot.managedTypes[type.baseOrElementTypeIndex.getOrThrow()];
 
             for (var n = 0; n < Mathf.Min(arrayLength, k_MaxItemsPerChunk); ++n)
@@ -98,9 +102,9 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
             }
 
             // an understandable way to name elements of an two dimensional array
-            if (type.arrayRank == 2)
+            if (arrayRank == 2)
             {
-                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1).getOrThrow();
+                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, arrayRank, 1).getOrThrow();
 
                 var x = 0;
                 var y = 0;
@@ -121,10 +125,10 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add)
             }
 
             // complicated way of naming elements of three and more dimensional arrays
-            if (type.arrayRank == 3)
+            if (arrayRank == 3)
             {
-                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, 1).getOrThrow();
-                var arrayLength3 = m_MemoryReader.ReadArrayLength(address, type, 2).getOrThrow();
+                var arrayLength2 = m_MemoryReader.ReadArrayLength(address, type, arrayRank, 1).getOrThrow();
+                var arrayLength3 = m_MemoryReader.ReadArrayLength(address, type, arrayRank, 2).getOrThrow();
 
                 var x = 0;
                 var y = 0;

From 538b42938d6e7b92bf305c79ab7e61849523030a Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Fri, 3 Feb 2023 13:11:14 +0200
Subject: [PATCH 6/9] Try to recover when Unity gives us a negative size for
 the type :|

---
 .../ManagedTypesView/ManagedTypesControl.cs   |  8 +-
 Editor/Scripts/MemoryReader.cs                | 57 ++++++++---
 .../PackedTypes/PackedManagedObjectCrawler.cs | 25 +++--
 .../Scripts/PackedTypes/PackedManagedType.cs  | 20 ++--
 .../PackedTypes/PackedMemorySnapshot.cs       |  6 ++
 .../PackedTypes/PackedMemorySnapshotEx.cs     |  6 ++
 .../PropertyGrid/ArrayPropertyGridItem.cs     | 94 +++++++++----------
 Editor/Scripts/Utilities/Either.cs            | 38 ++++++++
 Editor/Scripts/Utilities/Either.cs.meta       |  3 +
 Editor/Scripts/Utilities/PInt.cs              |  4 +
 Editor/Scripts/Utilities/Unit.cs              |  8 ++
 Editor/Scripts/Utilities/Unit.cs.meta         |  3 +
 Editor/Scripts/Utilities/Utils.cs             | 16 +++-
 13 files changed, 204 insertions(+), 84 deletions(-)
 create mode 100644 Editor/Scripts/Utilities/Either.cs
 create mode 100644 Editor/Scripts/Utilities/Either.cs.meta
 create mode 100644 Editor/Scripts/Utilities/Unit.cs
 create mode 100644 Editor/Scripts/Utilities/Unit.cs.meta

diff --git a/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs b/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
index 4635b47..d5f3dc4 100644
--- a/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
+++ b/Editor/Scripts/ManagedTypesView/ManagedTypesControl.cs
@@ -207,13 +207,7 @@ public override string typeName
                 }
             }
 
-            public override long size
-            {
-                get
-                {
-                    return m_Type.packed.size;
-                }
-            }
+            public override long size => m_Type.packed.size.fold(v => v, v => v);
 
             public override string assemblyName
             {
diff --git a/Editor/Scripts/MemoryReader.cs b/Editor/Scripts/MemoryReader.cs
index 63542d0..ce96440 100644
--- a/Editor/Scripts/MemoryReader.cs
+++ b/Editor/Scripts/MemoryReader.cs
@@ -3,6 +3,7 @@
 // https://github.com/pschraut/UnityHeapExplorer/
 //
 
+using System.Collections.Concurrent;
 using System.Globalization;
 using HeapExplorer.Utilities;
 using UnityEngine;
@@ -55,9 +56,9 @@ protected override Option<int> TryBeginRead(ulong address)
 
     public class StaticMemoryReader : AbstractMemoryReader
     {
-        public StaticMemoryReader(PackedMemorySnapshot snapshot, byte[] staticBytes)
-            : base(snapshot)
-        {
+        public StaticMemoryReader(
+            PackedMemorySnapshot snapshot, byte[] staticBytes
+        ) : base(snapshot) {
             m_Bytes = staticBytes;
         }
 
@@ -94,6 +95,7 @@ abstract public class AbstractMemoryReader
         protected int m_RecursionGuard;
         protected System.Text.StringBuilder m_StringBuilder = new System.Text.StringBuilder(128);
         protected System.Security.Cryptography.MD5 m_Hasher;
+        ConcurrentDictionary<string, Unit> reportedErrors => m_Snapshot.reportedErrors;
 
         protected AbstractMemoryReader(PackedMemorySnapshot snapshot)
         {
@@ -115,7 +117,11 @@ public Option<float> ReadSingle(ulong address) =>
             TryBeginRead(address).map(m_Bytes, (offset, bytes) => System.BitConverter.ToSingle(bytes, offset));
 
         public Option<Quaternion> ReadQuaternion(ulong address) {
-            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
+            var singleType = m_Snapshot.typeOfSingle;
+            if (!singleType.size.valueOut(out var sizeOfSingle)) {
+                Utils.reportInvalidSizeError(singleType, reportedErrors);
+                return None._;
+            }
 
             if (!ReadSingle(address + (uint) (sizeOfSingle * 0)).valueOut(out var x)) return None._;
             if (!ReadSingle(address + (uint) (sizeOfSingle * 1)).valueOut(out var y)) return None._;
@@ -127,7 +133,11 @@ public Option<Quaternion> ReadQuaternion(ulong address) {
         }
 
         public Option<Color> ReadColor(ulong address) {
-            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
+            var singleType = m_Snapshot.typeOfSingle;
+            if (!singleType.size.valueOut(out var sizeOfSingle)) {
+                Utils.reportInvalidSizeError(singleType, reportedErrors);
+                return None._;
+            }
 
             if (!ReadSingle(address + (uint) (sizeOfSingle * 0)).valueOut(out var r)) return None._;
             if (!ReadSingle(address + (uint) (sizeOfSingle * 1)).valueOut(out var g)) return None._;
@@ -139,7 +149,11 @@ public Option<Color> ReadColor(ulong address) {
         }
 
         public Option<Color32> ReadColor32(ulong address) {
-            var sizeOfByte = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemByte].size;
+            var byteType = m_Snapshot.typeOfSingle;
+            if (!byteType.size.valueOut(out var sizeOfByte)) {
+                Utils.reportInvalidSizeError(byteType, reportedErrors);
+                return None._;
+            }
 
             if (!ReadByte(address + (uint) (sizeOfByte * 0)).valueOut(out var r)) return None._;
             if (!ReadByte(address + (uint) (sizeOfByte * 1)).valueOut(out var g)) return None._;
@@ -150,11 +164,15 @@ public Option<Color32> ReadColor32(ulong address) {
             return Some(value);
         }
 
-        public Option<Matrix4x4> ReadMatrix4x4(ulong address)
-        {
+        public Option<Matrix4x4> ReadMatrix4x4(ulong address) {
+            var singleType = m_Snapshot.typeOfSingle;
+            if (!singleType.size.valueOut(out var sizeOfSingle)) {
+                Utils.reportInvalidSizeError(singleType, reportedErrors);
+                return None._;
+            }
+            
             var value = new Matrix4x4();
 
-            var sizeOfSingle = m_Snapshot.managedTypes[m_Snapshot.coreTypes.systemSingle].size;
             var element = 0;
             for (var y = 0; y < 4; ++y)
             {
@@ -367,8 +385,9 @@ public Option<Hash128> ComputeObjectHash(ulong address, PackedManagedType type)
             return Some(segment);
         }
 
-        public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescription)
-        {
+        public Option<PInt> ReadObjectSize(
+            ulong address, PackedManagedType typeDescription
+        ) {
             // System.Array
             // Do not display its pointer-size, but the actual size of its content.
             {if (typeDescription.arrayRank.valueOut(out var arrayRank)) {
@@ -390,7 +409,18 @@ public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescript
 
                 if (!ReadArrayLength(address, arrayRank).valueOut(out var arrayLength)) return None._;
                 var elementType = m_Snapshot.managedTypes[baseOrElementTypeIndex];
-                var elementSize = elementType.isValueType ? elementType.size.asInt : m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes();
+                int elementSize;
+                if (elementType.isValueType) {
+                    if (!elementType.size.valueOut(out var pElementSize)) {
+                        Utils.reportInvalidSizeError(elementType, reportedErrors);
+                        return None._;
+                    }
+
+                    elementSize = pElementSize;
+                }
+                else {
+                    elementSize = m_Snapshot.virtualMachineInformation.pointerSize.sizeInBytes();
+                }
 
                 var size = m_Snapshot.virtualMachineInformation.arrayHeaderSize.asInt;
                 size += elementSize * arrayLength;
@@ -409,7 +439,8 @@ public Option<PInt> ReadObjectSize(ulong address, PackedManagedType typeDescript
                 return Some(PInt.createOrThrow(size));
             }
 
-            return Some(typeDescription.size);
+            if (typeDescription.size.isLeft) Utils.reportInvalidSizeError(typeDescription, reportedErrors);
+            return typeDescription.size.rightOption;
         }
 
         public Option<int> ReadArrayLength(ulong address, PInt arrayRank)
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
index c2dae3e..1a27d5f 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
@@ -9,6 +9,7 @@
 using System.Collections.Generic;
 using UnityEngine;
 using System;
+using System.Collections.Concurrent;
 using HeapExplorer.Utilities;
 using static HeapExplorer.Utilities.Option;
 
@@ -126,8 +127,9 @@ void InitializeCachedPtr()
             );
         }
 
-        void CrawlManagedObjects(List<PackedManagedObject> managedObjects)
-        {
+        void CrawlManagedObjects(
+            List<PackedManagedObject> managedObjects
+        ) {
             var virtualMachineInformation = m_Snapshot.virtualMachineInformation;
             var nestedStructsIgnored = 0;
 
@@ -293,12 +295,19 @@ Option<ulong> determineElementAddress() {
                         // Artūras Šlajus: Not sure why these checks are done in this order but I am too scared to
                         // switch the order.
                         if (elementType.isArray) return readPtr();
-                        if (elementType.isValueType) return Some(
-                            mo.address
-                            + (ulong) (k * elementType.size)
-                            + virtualMachineInformation.arrayHeaderSize
-                            - virtualMachineInformation.objectHeaderSize
-                        );
+                        if (elementType.isValueType) {
+                            if (elementType.size.valueOut(out var elementTypeSize))
+                                return Some(
+                                    mo.address
+                                    + (ulong) (k * elementTypeSize)
+                                    + virtualMachineInformation.arrayHeaderSize
+                                    - virtualMachineInformation.objectHeaderSize
+                                );
+                            else {
+                                Utils.reportInvalidSizeError(elementType, m_Snapshot.reportedErrors);
+                                return None._;
+                            }
+                        }
                         else return readPtr();
 
                         Option<ulong> readPtr() {
diff --git a/Editor/Scripts/PackedTypes/PackedManagedType.cs b/Editor/Scripts/PackedTypes/PackedManagedType.cs
index d9eb398..d3de0f8 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedType.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedType.cs
@@ -71,12 +71,17 @@ public class PackedManagedType : PackedMemorySnapshot.TypeForSubclassSearch
             baseOrElementTypeIndex.valueOut(out var idx)
             ? idx == managedTypesArrayIndex ? None._ : Some(idx)
             : None._;
-
+        
         /// <summary>
         /// Size in bytes of an instance of this type. If this type is an array type, this describes the amount of
         /// bytes a single element in the array will take up.
         /// </summary>
-        public readonly PInt size;
+        /// <note>
+        /// This is an <see cref="Either{A,B}"/> because sometimes Unity returns a negative number for size, which
+        /// obviously makes no sense. We have `Left` here with the raw value on failure and `Right` value on success
+        /// and then we try to fall-back gracefully as much as we can when this happens.
+        /// </note>
+        public readonly Either<int, PInt> size;
 
         /// <summary>
         /// The address in memory that contains the description of this type inside the virtual machine.
@@ -130,7 +135,7 @@ public class PackedManagedType : PackedMemorySnapshot.TypeForSubclassSearch
 
         public PackedManagedType(
             bool isValueType, Option<PInt> arrayRank, string name, string assembly, PackedManagedField[] fields, 
-            byte[] staticFieldBytes, Option<PInt> baseOrElementTypeIndex, PInt size, ulong typeInfoAddress, 
+            byte[] staticFieldBytes, Option<PInt> baseOrElementTypeIndex, Either<int, PInt> size, ulong typeInfoAddress, 
             PInt managedTypesArrayIndex
         ) {
             this.isValueType = isValueType;
@@ -364,7 +369,7 @@ public static void Write(System.IO.BinaryWriter writer, PackedManagedType[] valu
                 writer.Write(value[n].staticFieldBytes.Length);
                 writer.Write(value[n].staticFieldBytes);
                 writer.Write(value[n].baseOrElementTypeIndex.fold(-1, _ => _));
-                writer.Write(value[n].size);
+                writer.Write(value[n].size.fold(v => v, v => v));
                 writer.Write(value[n].typeInfoAddress);
                 writer.Write(value[n].managedTypesArrayIndex);
 
@@ -416,7 +421,7 @@ public static void Read(System.IO.BinaryReader reader, out PackedManagedType[] v
                         assembly: assembly,
                         staticFieldBytes: staticFieldBytes,
                         baseOrElementTypeIndex: baseOrElementTypeIndex,
-                        size: PInt.createOrThrow(size),
+                        size: PInt.createEither(size),
                         typeInfoAddress: typeInfoAddress,
                         managedTypesArrayIndex: PInt.createOrThrow(managedTypesArrayIndex),
                         fields: fields
@@ -527,8 +532,9 @@ UnityEditor.Profiling.Memory.Experimental.PackedMemorySnapshot snapshot
                         : None._;
                 var baseOrElementTypeIndex =
                     rawBaseOrElementTypeIndex == -1 ? None._ : Some(PInt.createOrThrow(rawBaseOrElementTypeIndex));
-                var size = PInt.createOrThrow(sourceSize[n]);
-                var managedTypesArrayIndex = PInt.createOrThrow(sourceTypeIndex[n]);
+                var rawManagedTypesArrayIndex = sourceTypeIndex[n];
+                var size = PInt.createEither(sourceSize[n]);
+                var managedTypesArrayIndex = PInt.createOrThrow(rawManagedTypesArrayIndex);
                 
                 managedTypes[n] = new PackedManagedType(
                     isValueType: isValueType,
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
index c32791b..6267643 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
@@ -47,6 +47,12 @@ public partial class PackedMemorySnapshot
         /// </summary>
         public PackedVirtualMachineInformation virtualMachineInformation;
 
+        /// <summary>Type of <see cref="System.Single"/>.</summary>
+        public PackedManagedType typeOfSingle => managedTypes[coreTypes.systemSingle];
+
+        /// <summary>Type of <see cref="System.Byte"/>.</summary>
+        public PackedManagedType typeOfByte => managedTypes[coreTypes.systemByte];
+
         /// <summary>
         /// Allows you to update the Unity progress bar with given <see cref="stepName"/>.
         /// </summary>
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
index ccf38ae..0873d93 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshotEx.cs
@@ -7,6 +7,7 @@
 using System.Collections.Generic;
 using UnityEngine;
 using System;
+using System.Collections.Concurrent;
 using System.Linq;
 using System.Threading;
 using HeapExplorer.Utilities;
@@ -56,6 +57,11 @@ public partial class PackedMemorySnapshot
         /// <inheritdoc cref="_coreTypes"/>
         public PackedCoreTypes coreTypes => _coreTypes.getOrThrow("core types not initialized");
 
+        /// <summary>
+        /// Used to prevent from constantly spamming the Unit log with errors about same things. 
+        /// </summary>
+        public ConcurrentDictionary<string, Unit> reportedErrors = new ConcurrentDictionary<string, Unit>();
+
         /// <summary>
         /// Write to busyString while processing, such as loading a memory snapshot, causes heap explorer
         /// to display the busyString in the main window.
diff --git a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
index bdfb850..229f904 100644
--- a/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
+++ b/Editor/Scripts/PropertyGrid/ArrayPropertyGridItem.cs
@@ -2,12 +2,8 @@
 // Heap Explorer for Unity. Copyright (c) 2019-2020 Peter Schraut (www.console-dev.de). See LICENSE.md
 // https://github.com/pschraut/UnityHeapExplorer/
 //
-using System.Collections;
-using System.Collections.Generic;
 using HeapExplorer.Utilities;
 using UnityEngine;
-using UnityEditor.IMGUI.Controls;
-using UnityEditor;
 
 namespace HeapExplorer
 {
@@ -64,8 +60,8 @@ protected override void OnInitialize()
             }
         }
 
-        protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add)
-        {
+        protected override void OnBuildChildren(System.Action<BuildChildrenArgs> add) {
+            
             if (arrayRank == 1)
                 BuildOneDimArray(add);
 
@@ -155,8 +151,9 @@ void BuildMultiDimArray(System.Action<BuildChildrenArgs> add) {
             }
         }
 
-        void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Action<BuildChildrenArgs> add)
-        {
+        void AddArrayElement(
+            PackedManagedType elementType, int elementIndex, System.Action<BuildChildrenArgs> add
+        ) {
             if (elementType.isArray)
             {
                 var pointer = m_MemoryReader.ReadPointer(
@@ -174,48 +171,51 @@ void AddArrayElement(PackedManagedType elementType, int elementIndex, System.Act
             }
             else if (elementType.isValueType)
             {
-                if (elementType.isPrimitive)
-                {
-                    var args = new BuildChildrenArgs {
-                        parent = this,
-                        type = elementType,
-                        address = address 
-                                  + (ulong)(elementIndex * elementType.size) 
-                                  + m_Snapshot.virtualMachineInformation.arrayHeaderSize 
-                                  - m_Snapshot.virtualMachineInformation.objectHeaderSize,
-                        memoryReader = new MemoryReader(m_Snapshot)
-                    };
-                    add(args);
-                }
-                else
-                {
-                    // this is the container node for the array elements.
-                    // if we don't add the container, all fields are simply added to the array node itself.
-                    // however, we want each array element being groupped
-                    var pointer = address 
-                                  + (ulong)(elementIndex * elementType.size) 
-                                  + m_Snapshot.virtualMachineInformation.arrayHeaderSize;
+                if (elementType.size.valueOut(out var elementTypeSize)) {
+                    if (elementType.isPrimitive) {
+                        var args = new BuildChildrenArgs {
+                            parent = this,
+                            type = elementType,
+                            address = address
+                                      + (ulong) (elementIndex * elementTypeSize)
+                                      + m_Snapshot.virtualMachineInformation.arrayHeaderSize
+                                      - m_Snapshot.virtualMachineInformation.objectHeaderSize,
+                            memoryReader = new MemoryReader(m_Snapshot)
+                        };
+                        add(args);
+                    }
+                    else {
+                        // this is the container node for the array elements.
+                        // if we don't add the container, all fields are simply added to the array node itself.
+                        // however, we want each array element being groupped
+                        var pointer = address
+                                      + (ulong) (elementIndex * elementTypeSize)
+                                      + m_Snapshot.virtualMachineInformation.arrayHeaderSize;
 
-                    var item = new ArrayElementPropertyGridItem(m_Owner, m_Snapshot, pointer, new MemoryReader(m_Snapshot))
-                    {
-                        depth = this.depth + 1,
-                        type = elementType
-                    };
-                    item.Initialize();
-                    this.AddChild(item);
+                        var item = new ArrayElementPropertyGridItem(m_Owner, m_Snapshot, pointer,
+                            new MemoryReader(m_Snapshot)) {
+                            depth = this.depth + 1,
+                            type = elementType
+                        };
+                        item.Initialize();
+                        this.AddChild(item);
 
-                    pointer = 
-                        address 
-                        + (ulong)(elementIndex * elementType.size) 
-                        + m_Snapshot.virtualMachineInformation.arrayHeaderSize 
-                        - m_Snapshot.virtualMachineInformation.objectHeaderSize;
+                        pointer =
+                            address
+                            + (ulong) (elementIndex * elementTypeSize)
+                            + m_Snapshot.virtualMachineInformation.arrayHeaderSize
+                            - m_Snapshot.virtualMachineInformation.objectHeaderSize;
 
-                    var args = new BuildChildrenArgs();
-                    args.parent = item;
-                    args.type = elementType;
-                    args.address = pointer;
-                    args.memoryReader = new MemoryReader(m_Snapshot);
-                    add(args);
+                        var args = new BuildChildrenArgs();
+                        args.parent = item;
+                        args.type = elementType;
+                        args.address = pointer;
+                        args.memoryReader = new MemoryReader(m_Snapshot);
+                        add(args);
+                    }
+                }
+                else {
+                    Utils.reportInvalidSizeError(elementType, m_Snapshot.reportedErrors);
                 }
             }
             else
diff --git a/Editor/Scripts/Utilities/Either.cs b/Editor/Scripts/Utilities/Either.cs
new file mode 100644
index 0000000..b633ddd
--- /dev/null
+++ b/Editor/Scripts/Utilities/Either.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace HeapExplorer.Utilities; 
+
+/// <summary>
+/// Holds either the `Left` value of type <see cref="A"/> or the `Right` value of type <see cref="B"/>, but not the
+/// both at the same type.
+/// </summary>
+public readonly struct Either<A, B> {
+  public readonly A __unsafeLeft;
+  public readonly B __unsafeRight;
+  public readonly bool isRight;
+  public bool isLeft => !isRight;
+
+  public Either(A value) {
+    __unsafeLeft = value;
+    __unsafeRight = default;
+    isRight = false;
+  }
+  
+  public Either(B value) {
+    __unsafeLeft = default;
+    __unsafeRight = value;
+    isRight = true;
+  }
+
+  public R fold<R>(Func<A, R> onLeft, Func<B, R> onRight) =>
+    isRight ? onRight(__unsafeRight) : onLeft(__unsafeLeft);
+
+  /// <summary>Right-biased value extractor.</summary>
+  public bool valueOut(out B o) {
+    o = __unsafeRight;
+    return isRight;
+  }
+
+  public Option<A> leftOption => isLeft ? Option.Some(__unsafeLeft) : Option<A>.None;
+  public Option<B> rightOption => isRight ? Option.Some(__unsafeRight) : Option<B>.None;
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Either.cs.meta b/Editor/Scripts/Utilities/Either.cs.meta
new file mode 100644
index 0000000..b48ec36
--- /dev/null
+++ b/Editor/Scripts/Utilities/Either.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c1e70c135e6c496989f8212c497a49b3
+timeCreated: 1675422079
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/PInt.cs b/Editor/Scripts/Utilities/PInt.cs
index b6b98f5..8be12c9 100644
--- a/Editor/Scripts/Utilities/PInt.cs
+++ b/Editor/Scripts/Utilities/PInt.cs
@@ -55,6 +55,10 @@ public static PInt createOrThrow(int value) {
     /// <summary>Creates a value, returning `None` if the supplied <see cref="int"/> is negative.</summary>
     public static Option<PInt> create(int value) => 
       value < 0 ? new Option<PInt>() : Some(new PInt(value));
+  
+    /// <summary>Creates a value, returning `Left(value)` if the supplied <see cref="int"/> is negative.</summary>
+    public static Either<int, PInt> createEither(int value) => 
+      value < 0 ? new Either<int, PInt>(value) : new Either<int, PInt>(new PInt(value));
 
     #region Equality
   
diff --git a/Editor/Scripts/Utilities/Unit.cs b/Editor/Scripts/Utilities/Unit.cs
new file mode 100644
index 0000000..fb5f131
--- /dev/null
+++ b/Editor/Scripts/Utilities/Unit.cs
@@ -0,0 +1,8 @@
+namespace HeapExplorer.Utilities;
+
+/// <summary>
+/// A type that carries no information (similarly to `void`) but you can use it in generic type/method definitions.
+/// </summary>
+public readonly struct Unit {
+  public static Unit _ => new Unit();
+}
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Unit.cs.meta b/Editor/Scripts/Utilities/Unit.cs.meta
new file mode 100644
index 0000000..62c5c50
--- /dev/null
+++ b/Editor/Scripts/Utilities/Unit.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 08caa1fd67b74720aa416922e40225c6
+timeCreated: 1675420670
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Utils.cs b/Editor/Scripts/Utilities/Utils.cs
index af2237d..ce3916a 100644
--- a/Editor/Scripts/Utilities/Utils.cs
+++ b/Editor/Scripts/Utilities/Utils.cs
@@ -1,6 +1,6 @@
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.Linq;
 using System.Runtime.CompilerServices;
 using UnityEngine;
 using static HeapExplorer.Utilities.Option;
@@ -9,10 +9,22 @@ namespace HeapExplorer.Utilities {
   public static class Utils {
     public static Option<A> zeroAddressAccessError<A>(string paramName) {
       // throw new ArgumentOutOfRangeException(paramName, 0, "address 0 should not be accessed!");
-      Debug.LogError("address 0 should not be accessed!");
+      Debug.LogError("HeapExplorer: address 0 should not be accessed!");
       return new Option<A>();
     }
 
+    public static void reportInvalidSizeError(
+      PackedManagedType type, ConcurrentDictionary<string, Unit> reported
+    ) {
+      if (reported.TryAdd(type.name, Unit._)) {
+        var size = type.size.fold(v => v, v => v);
+        Debug.LogError(
+          $"HeapExplorer: Unity reported invalid size {size} for type '{type.name}', this is a Unity bug! "
+          + $"We can't continue the current operation because of that, data will most likely be incomplete!"
+        );
+      }
+    }
+
     /// <summary>
     /// Returns the <see cref="uint"/> value as <see cref="int"/>, clamping it if it doesn't fit. 
     /// </summary>

From 7bfb34f52ad205e25c28074cb375dc674d74092a Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Sun, 12 Feb 2023 14:30:05 +0200
Subject: [PATCH 7/9] Fix compilation for older C# compiler that does not
 support file-scoped namespaces yet.

---
 Editor/Scripts/Utilities/Either.cs | 60 +++++++++++++++---------------
 Editor/Scripts/Utilities/Unit.cs   | 14 +++----
 2 files changed, 37 insertions(+), 37 deletions(-)

diff --git a/Editor/Scripts/Utilities/Either.cs b/Editor/Scripts/Utilities/Either.cs
index b633ddd..9ce4e0b 100644
--- a/Editor/Scripts/Utilities/Either.cs
+++ b/Editor/Scripts/Utilities/Either.cs
@@ -1,38 +1,38 @@
 using System;
 
-namespace HeapExplorer.Utilities; 
+namespace HeapExplorer.Utilities {
+  /// <summary>
+  /// Holds either the `Left` value of type <see cref="A"/> or the `Right` value of type <see cref="B"/>, but not the
+  /// both at the same type.
+  /// </summary>
+  public readonly struct Either<A, B> {
+    public readonly A __unsafeLeft;
+    public readonly B __unsafeRight;
+    public readonly bool isRight;
+    public bool isLeft => !isRight;
 
-/// <summary>
-/// Holds either the `Left` value of type <see cref="A"/> or the `Right` value of type <see cref="B"/>, but not the
-/// both at the same type.
-/// </summary>
-public readonly struct Either<A, B> {
-  public readonly A __unsafeLeft;
-  public readonly B __unsafeRight;
-  public readonly bool isRight;
-  public bool isLeft => !isRight;
-
-  public Either(A value) {
-    __unsafeLeft = value;
-    __unsafeRight = default;
-    isRight = false;
-  }
+    public Either(A value) {
+      __unsafeLeft = value;
+      __unsafeRight = default;
+      isRight = false;
+    }
   
-  public Either(B value) {
-    __unsafeLeft = default;
-    __unsafeRight = value;
-    isRight = true;
-  }
+    public Either(B value) {
+      __unsafeLeft = default;
+      __unsafeRight = value;
+      isRight = true;
+    }
 
-  public R fold<R>(Func<A, R> onLeft, Func<B, R> onRight) =>
-    isRight ? onRight(__unsafeRight) : onLeft(__unsafeLeft);
+    public R fold<R>(Func<A, R> onLeft, Func<B, R> onRight) =>
+      isRight ? onRight(__unsafeRight) : onLeft(__unsafeLeft);
 
-  /// <summary>Right-biased value extractor.</summary>
-  public bool valueOut(out B o) {
-    o = __unsafeRight;
-    return isRight;
-  }
+    /// <summary>Right-biased value extractor.</summary>
+    public bool valueOut(out B o) {
+      o = __unsafeRight;
+      return isRight;
+    }
 
-  public Option<A> leftOption => isLeft ? Option.Some(__unsafeLeft) : Option<A>.None;
-  public Option<B> rightOption => isRight ? Option.Some(__unsafeRight) : Option<B>.None;
+    public Option<A> leftOption => isLeft ? Option.Some(__unsafeLeft) : Option<A>.None;
+    public Option<B> rightOption => isRight ? Option.Some(__unsafeRight) : Option<B>.None;
+  }
 }
\ No newline at end of file
diff --git a/Editor/Scripts/Utilities/Unit.cs b/Editor/Scripts/Utilities/Unit.cs
index fb5f131..45b5cd7 100644
--- a/Editor/Scripts/Utilities/Unit.cs
+++ b/Editor/Scripts/Utilities/Unit.cs
@@ -1,8 +1,8 @@
-namespace HeapExplorer.Utilities;
-
-/// <summary>
-/// A type that carries no information (similarly to `void`) but you can use it in generic type/method definitions.
-/// </summary>
-public readonly struct Unit {
-  public static Unit _ => new Unit();
+namespace HeapExplorer.Utilities {
+  /// <summary>
+  /// A type that carries no information (similarly to `void`) but you can use it in generic type/method definitions.
+  /// </summary>
+  public readonly struct Unit {
+    public static Unit _ => new Unit();
+  }
 }
\ No newline at end of file

From 9979cb7f615e08f48a092e96dc6a4f08c02b769a Mon Sep 17 00:00:00 2001
From: Evaldas Ciakas <evaldas757@gmail.com>
Date: Mon, 20 Feb 2023 23:13:01 +0200
Subject: [PATCH 8/9] Fix for Unity 2022.2

---
 Editor/Scripts/HeapExplorerWindow.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Editor/Scripts/HeapExplorerWindow.cs b/Editor/Scripts/HeapExplorerWindow.cs
index 0798273..f7a18cb 100644
--- a/Editor/Scripts/HeapExplorerWindow.cs
+++ b/Editor/Scripts/HeapExplorerWindow.cs
@@ -9,7 +9,11 @@
 using UnityEditor;
 using System;
 using System.Threading;
+#if UNITY_2022_2_OR_NEWER
+using Unity.Profiling.Memory;
+#else
 using UnityEngine.Profiling.Memory.Experimental;
+#endif
 
 namespace HeapExplorer
 {

From c2be5a963d31f674bc15d7ed7d717d2a0518af96 Mon Sep 17 00:00:00 2001
From: Arturas Slajus <x11@arturaz.net>
Date: Tue, 28 Feb 2023 08:35:48 +0200
Subject: [PATCH 9/9] Fix compilation and tests for Unity 2019.

Tested with Unity 2019.4.40f1.
---
 .../Scripts/GCHandlesView/GCHandlesControl.cs |  6 ++--
 .../PackedTypes/PackedManagedObjectCrawler.cs |  4 +--
 .../Scripts/PackedTypes/PackedManagedType.cs  |  8 +++---
 .../PackedTypes/PackedMemorySnapshot.cs       |  6 ++--
 Editor/Scripts/Utilities/Either.cs            |  3 ++
 Tests/Editor/Test_Editor.cs                   | 28 +++++++++----------
 6 files changed, 29 insertions(+), 26 deletions(-)

diff --git a/Editor/Scripts/GCHandlesView/GCHandlesControl.cs b/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
index 7ee3404..8c93897 100644
--- a/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
+++ b/Editor/Scripts/GCHandlesView/GCHandlesControl.cs
@@ -113,10 +113,10 @@ public TreeViewItem BuildTree(PackedMemorySnapshot snapshot)
                     break;
 
                 var gcHandle = m_Snapshot.gcHandles[n];
-                var maybeManagedTypeIndex = gcHandle.managedObjectsArrayIndex.map(m_Snapshot, (idx, snapshot) =>
+                var maybeManagedTypeIndex = gcHandle.managedObjectsArrayIndex.map(m_Snapshot, (idx, ss) =>
                     idx.isStatic
-                        ? snapshot.managedStaticFields[idx.index].managedTypesArrayIndex
-                        : snapshot.managedObjects[idx.index].managedTypesArrayIndex
+                        ? ss.managedStaticFields[idx.index].managedTypesArrayIndex
+                        : ss.managedObjects[idx.index].managedTypesArrayIndex
                 );
 
                 var targetItem = root;
diff --git a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
index 1a27d5f..36dec66 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedObjectCrawler.cs
@@ -433,9 +433,9 @@ List<PackedManagedObject> managedObjects
             var managedTypes = m_Snapshot.managedTypes;
             var staticFields = new List<PackedManagedStaticField>(10 * 1024);
             var staticManagedTypes = new List<int>(1024);
-            collectStaticFields(staticFields, staticManagedTypes);
+            collectStaticFields();
 
-            void collectStaticFields(List<PackedManagedStaticField> staticFields, List<int> staticManagedTypes) {
+            void collectStaticFields() {
                 // Unity BUG: (Case 984330) PackedMemorySnapshot: Type contains staticFieldBytes, but has no static fields
                 for (int n = 0, nend = managedTypes.Length; n < nend; ++n) {
                     // Some static classes have no staticFieldBytes. As I understand this, the staticFieldBytes
diff --git a/Editor/Scripts/PackedTypes/PackedManagedType.cs b/Editor/Scripts/PackedTypes/PackedManagedType.cs
index d3de0f8..9ac7f71 100644
--- a/Editor/Scripts/PackedTypes/PackedManagedType.cs
+++ b/Editor/Scripts/PackedTypes/PackedManagedType.cs
@@ -580,12 +580,12 @@ void reportFailures(string description, int[] failureIndexes) {
 
                     for (int idx = 0, idxEnd = groupedFailures.Length; idx < idxEnd; idx++) {
                         var group = groupedFailures[idx];
-                        var str = string.Join("\n\n", group.Select(idx => {
-                            var managedTypesArrayIndex = fieldTypeIndex[idx];
+                        var str = string.Join("\n\n", group.Select(_idx => {
+                            var managedTypesArrayIndex = fieldTypeIndex[_idx];
                             var typeName = managedTypes[managedTypesArrayIndex].name;
                             var typeAssembly = managedTypes[managedTypesArrayIndex].assembly;
-                            return $"Field[index={idx}, name={fieldName[idx]}, static={fieldStatic[idx]}, "
-                                   + $"offset={fieldOffset[idx]}, managedTypesArrayIndex={managedTypesArrayIndex}"
+                            return $"Field[index={_idx}, name={fieldName[_idx]}, static={fieldStatic[_idx]}, "
+                                   + $"offset={fieldOffset[_idx]}, managedTypesArrayIndex={managedTypesArrayIndex}"
                                    + "]\n"
                                    + $"@ [assembly '{typeAssembly}'] [type '{typeName}']";
                         }));
diff --git a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
index 6267643..77e3fc7 100644
--- a/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
+++ b/Editor/Scripts/PackedTypes/PackedMemorySnapshot.cs
@@ -219,8 +219,8 @@ public bool isOrContainsReferenceType(int managedTypeIndex) =>
             _containsReferenceTypeCache.getOrUpdate(
                 managedTypeIndex, 
                 this, 
-                (managedTypeIndex, self) => {
-                    var type = self.managedTypes[managedTypeIndex];
+                (_managedTypeIndex, self) => {
+                    var type = self.managedTypes[_managedTypeIndex];
                     if (type.isReferenceType)
                         return true;
                     
@@ -241,7 +241,7 @@ public bool isOrContainsReferenceType(int managedTypeIndex) =>
                             continue;
                         }
 
-                        if (fieldTypeIndex == managedTypeIndex) {
+                        if (fieldTypeIndex == _managedTypeIndex) {
                             self.Error(
                                 $"HeapExplorer: '{type.name}' field '{instanceFields[n].name}' is a value type that "
                                 + "contains itself, that should be impossible!."
diff --git a/Editor/Scripts/Utilities/Either.cs b/Editor/Scripts/Utilities/Either.cs
index 9ce4e0b..c66035d 100644
--- a/Editor/Scripts/Utilities/Either.cs
+++ b/Editor/Scripts/Utilities/Either.cs
@@ -31,6 +31,9 @@ public bool valueOut(out B o) {
       o = __unsafeRight;
       return isRight;
     }
+    
+    public B rightOrThrow => 
+      isRight ? __unsafeRight : throw new InvalidOperationException($"Either is Left({__unsafeLeft})");
 
     public Option<A> leftOption => isLeft ? Option.Some(__unsafeLeft) : Option<A>.None;
     public Option<B> rightOption => isRight ? Option.Some(__unsafeRight) : Option<B>.None;
diff --git a/Tests/Editor/Test_Editor.cs b/Tests/Editor/Test_Editor.cs
index a07541f..4435ae1 100644
--- a/Tests/Editor/Test_Editor.cs
+++ b/Tests/Editor/Test_Editor.cs
@@ -122,7 +122,7 @@ void AssertDecimal(string fieldName, System.Decimal value, RichManagedObject man
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value, memory.ReadDecimal((uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value), memory.ReadDecimal((uint)field.offset + managedObject.address));
         }
 
         void AssertInt(string fieldName, int value, RichManagedObject managedObject)
@@ -130,7 +130,7 @@ void AssertInt(string fieldName, int value, RichManagedObject managedObject)
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value, memory.ReadInt32((uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value), memory.ReadInt32((uint)field.offset + managedObject.address));
         }
 
         void AssertLong(string fieldName, long value, RichManagedObject managedObject)
@@ -138,7 +138,7 @@ void AssertLong(string fieldName, long value, RichManagedObject managedObject)
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value, memory.ReadInt64((uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value), memory.ReadInt64((uint)field.offset + managedObject.address));
         }
 
         void AssertULong(string fieldName, ulong value, RichManagedObject managedObject)
@@ -146,7 +146,7 @@ void AssertULong(string fieldName, ulong value, RichManagedObject managedObject)
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value, memory.ReadUInt64((uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value), memory.ReadUInt64((uint)field.offset + managedObject.address));
         }
 
         void AssertVector2(string fieldName, Vector2 value, RichManagedObject managedObject)
@@ -154,8 +154,8 @@ void AssertVector2(string fieldName, Vector2 value, RichManagedObject managedObj
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value.x, memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.y, memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.x), memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.y), memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
         }
 
         void AssertVector3(string fieldName, Vector3 value, RichManagedObject managedObject)
@@ -163,9 +163,9 @@ void AssertVector3(string fieldName, Vector3 value, RichManagedObject managedObj
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value.x, memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.y, memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.z, memory.ReadSingle(8 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.x), memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.y), memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.z), memory.ReadSingle(8 + (uint)field.offset + managedObject.address));
         }
 
         void AssertQuaternion(string fieldName, Quaternion value, RichManagedObject managedObject)
@@ -173,10 +173,10 @@ void AssertQuaternion(string fieldName, Quaternion value, RichManagedObject mana
             var memory = new MemoryReader(m_snapshot);
 
             var field = GetField(fieldName, managedObject);
-            Assert.AreEqual(value.x, memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.y, memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.z, memory.ReadSingle(8 + (uint)field.offset + managedObject.address));
-            Assert.AreEqual(value.w, memory.ReadSingle(12 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.x), memory.ReadSingle(0 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.y), memory.ReadSingle(4 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.z), memory.ReadSingle(8 + (uint)field.offset + managedObject.address));
+            Assert.AreEqual(Some(value.w), memory.ReadSingle(12 + (uint)field.offset + managedObject.address));
         }
 
         void AssertMatrix4x4(string fieldName, Matrix4x4 value, RichManagedObject managedObject)
@@ -186,7 +186,7 @@ void AssertMatrix4x4(string fieldName, Matrix4x4 value, RichManagedObject manage
             var field = GetField(fieldName, managedObject);
 
             Matrix4x4 matrix = new Matrix4x4();
-            int sizeOfSingle = m_snapshot.managedTypes[m_snapshot.coreTypes.systemSingle].size;
+            int sizeOfSingle = m_snapshot.managedTypes[m_snapshot.coreTypes.systemSingle].size.rightOrThrow;
             int element = 0;
             for (var y = 0; y < 4; ++y)
             {