Created
July 30, 2009 17:29
-
-
Save spraints/158795 to your computer and use it in GitHub Desktop.
Ruby's inspect for .NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ""); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + ">"); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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!