Skip to content

Instantly share code, notes, and snippets.

@spraints
Created July 30, 2009 17:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save spraints/158795 to your computer and use it in GitHub Desktop.
Save spraints/158795 to your computer and use it in GitHub Desktop.
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
Copy link

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