Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Ruby's inspect for .NET
using System;
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
namespace SEP.Extensions
{
public static class InspectExtensions
{
public static string Inspect(this object obj)
{
return Inspect(obj, d => d.Inspect(), e => e.Inspect(), o => InspectWithProperties(o));
}
public static string Inspect(this char o)
{
return "'" + o + "'";
}
public static string Inspect(this string o)
{
if (o == null) return Inspect((object)o);
return "\"" + o + "\"";
}
public static string Inspect(this IDictionary o)
{
if (o == null) return Inspect((object)o);
var entries = o.Keys.Cast<object>().Select(key => InspectSimple(key) + " => " + InspectSimple(o[key]));
return "{" + String.Join(", ", entries.ToArray()) + "}";
}
public static string Inspect(this IEnumerable o)
{
if (o == null) return Inspect((object)o);
return "[" + String.Join(", ", o.Cast<object>().Select(obj => InspectSimple(obj)).ToArray()) + "]";
}
private static string InspectWithProperties(object o)
{
var inspected = "#<";
inspected += InspectType(o.GetType());
inspected += String.Join(",",
o.GetType().GetProperties().Select(p => " " + p.Name + "=" + InspectSimple(p.GetValue(o, null)))
.ToArray());
inspected += ">";
return inspected;
}
private static string InspectSimple(object obj)
{
return Inspect(obj, d => "{...}", e => "[...]",
o => "#<" + InspectType(o.GetType()) + ":0x" + o.GetHashCode().ToString("x") + ">");
}
private static string Inspect(object o,
Func<IDictionary, string> inspectDictionary,
Func<IEnumerable, string> inspectEnumerable,
Func<object, string> inspectObject)
{
if (o == null) return "null";
if (ShouldUseToString(o)) return o.ToString();
if (o is Enum) return o.ToString().Replace(", ", "|");
if (o is String) return ((String) o).Inspect();
if (o is char) return ((char) o).Inspect();
if (CanInspect(o)) return CallInspect(o);
if (o is IDictionary) return inspectDictionary((IDictionary) o);
if (o is IEnumerable) return inspectEnumerable((IEnumerable)o);
return inspectObject(o);
}
private static bool ShouldUseToString(object o)
{
return o is int ||
o is long ||
o is float ||
o is double ||
o is decimal ||
o is byte ||
o is DateTime;
}
private static bool CanInspect(object o)
{
var inspectMethod = o.GetType().GetMethod("Inspect", new Type[0]);
return inspectMethod != null && inspectMethod.ReturnType == typeof (String);
}
private static string CallInspect(object o)
{
var inspectMethod = o.GetType().GetMethod("Inspect", new Type[0]);
return (string) inspectMethod.Invoke(o, null);
}
private static string InspectType(Type type)
{
return IsAnonymousType(type) ? InspectAnonymousType(type) : type.IsGenericType ? InspectGenericType(type) : type.FullName;
}
private static bool IsAnonymousType(Type type)
{
return new Regex("^<>f__AnonymousType").IsMatch(type.FullName);
}
private static string InspectAnonymousType(Type type)
{
return "anon";
}
private static string InspectGenericType(Type type)
{
var name = Undecorate(type.FullName);
name += "<";
name += String.Join(",", type.GetGenericArguments().Select(t => InspectType(t)).ToArray());
name += ">";
return name;
}
private static string Undecorate(string genericTypeName)
{
var decorationMatcher = new Regex(@"`\d+\[\[[^\]]+\](,\[[^\]]+\])*\]");
return decorationMatcher.Replace(genericTypeName, "");
}
}
}
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SEP.Extensions.Tests
{
[TestClass]
public class InspectExtensionTests
{
[TestMethod]
public void ShouldInspectNull()
{
object o = null;
Assert.AreEqual("null", o.Inspect());
}
[TestMethod]
public void ShouldInspectBytes()
{
byte b = 0x80;
Assert.AreEqual("128", b.Inspect());
}
[TestMethod]
public void ShouldInspectChars()
{
Assert.AreEqual("'a'", 'a'.Inspect());
}
[TestMethod]
public void ShouldInspectIntegers()
{
Assert.AreEqual("99", 99.Inspect());
}
[TestMethod]
public void ShouldInspectLongs()
{
Assert.AreEqual("9999", 9999L.Inspect());
}
[TestMethod]
public void ShouldInspectStrings()
{
Assert.AreEqual("\"hello!\"", "hello!".Inspect());
}
[TestMethod]
public void ShouldInspectFloats()
{
Assert.AreEqual("1.5", 1.5F.Inspect());
}
[TestMethod]
public void ShouldInspectDoubles()
{
Assert.AreEqual("1.5", 1.5.Inspect());
}
[TestMethod]
public void ShouldInspectDecimals()
{
Assert.AreEqual("1.5", new Decimal(1.5).Inspect());
}
[TestMethod]
public void ShouldInspectDateTime()
{
Assert.AreEqual("1/1/2009 12:00:00 AM", new DateTime(2009, 1, 1).Inspect());
}
[TestMethod]
public void ShouldInspectStringPretendingToBeAnObject()
{
object o = "hello!";
Assert.AreEqual("\"hello!\"", o.Inspect());
}
[TestMethod]
public void ShouldInspectArray()
{
Assert.AreEqual("[1, 2, 3]", new object[] {1, 2, 3}.Inspect());
}
[TestMethod]
public void ShouldInspectList()
{
Assert.AreEqual("[1, 2, 3]", new List<object>() {1, 2, 3}.Inspect());
}
[TestMethod]
public void ShouldInspectDictionary()
{
var items = new Dictionary<string, int>() {{"key", 1}};
Assert.AreEqual("{\"key\" => 1}", items.Inspect());
}
[TestMethod]
public void ShouldReportProperties()
{
Assert.AreEqual("#<anon Name=\"Matt\", Level=4>", new {Name = "Matt", Level = 4}.Inspect());
}
[TestMethod]
public void ShouldNotRecurseIntoItemsInList()
{
var list = new List<object>();
list.Add(new List<string>());
Assert.AreEqual("[[...]]", list.Inspect());
}
[TestMethod]
public void ShouldNotRecurseIntoItemsInDictionary()
{
var dictionary = new Dictionary<string, object>();
dictionary["test"] = new Dictionary<string, int>();
AssertMatch("{\"test\" => {...}}",
dictionary.Inspect());
}
public class TestClass
{
public object Thing { get; set; }
internal object InternalThing { get; set; }
protected object ProtectedThing { get; set; }
private object PrivateThing { get; set; }
}
[TestMethod]
public void ShouldNotRecurseIntoItemsInObject()
{
var x = new TestClass();
x.Thing = new TestClass();
Assert.AreEqual(
"#<SEP.Extensions.Tests.InspectExtensionTests+TestClass Thing=#<SEP.Extensions.Tests.InspectExtensionTests+TestClass:0x11a2ccb>>",
x.Inspect());
}
public class TestGenericClass<T>
{
}
[TestMethod]
public void ShouldReportGenericTypes()
{
Assert.AreEqual("#<SEP.Extensions.Tests.InspectExtensionTests+TestGenericClass<System.Int32>>",
new TestGenericClass<int>().Inspect());
}
public class TestInspectableClass
{
public string Inspect()
{
return "inspection!";
}
}
[TestMethod]
public void ShouldInspectInspectable()
{
Assert.AreEqual("inspection!", new TestInspectableClass().Inspect());
}
[TestMethod]
public void ShouldInspectInspectableInList()
{
var list = new List<object>();
list.Add(new TestInspectableClass());
Assert.AreEqual("[inspection!]", list.Inspect());
}
[Flags]
public enum TestEnum
{
A = 0x01,
B = 0x02,
C = 0x04
}
[TestMethod]
public void ShouldInspectEnum()
{
Assert.AreEqual("A|B", (TestEnum.A | TestEnum.B).Inspect());
}
private void AssertMatch(string expectedPattern, string actual)
{
AssertMatch(new Regex(expectedPattern), actual);
}
private void AssertMatch(Regex expectedRegex, string actual)
{
Assert.IsTrue(expectedRegex.IsMatch(actual), "Expected to match: <" + expectedRegex + "> but was <" + actual + ">");
}
}
}
@JohnMurray

This comment has been minimized.

Copy link

JohnMurray commented Nov 21, 2012

Heck yes! This is fantastic! I get so used to this in Ruby, I forget how much I use it when I have to work in .NET :(
This will look great with my logging code! lol

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.